雖然如今基本不怎麼使用jQuery
了,但jQuery
流行10多年
的JS庫
,仍是有必要學習它的源碼的。也能夠學着打造屬於本身的js
類庫,求職面試時能夠增色很多。html
本文章學習的是v3.4.1
版本。unpkg.com
源碼地址:https://unpkg.com/jquery@3.4....前端
jQuery
github
倉庫node
(function(global, factory){ })(typeof window !== "underfined" ? window: this, function(window, noGlobal){ });
外界訪問不到裏面的變量和函數,裏面能夠訪問到外界的變量,但裏面定義了本身的變量,則不會訪問外界的變量。
匿名函數將代碼包裹在裏面,防止與其餘代碼衝突和污染全局環境。
關於自執行函數不是很瞭解的讀者能夠參看這篇文章。
[[譯] JavaScript:當即執行函數表達式(IIFE)](https://segmentfault.com/a/11...jquery
瀏覽器環境下,最後把$
和 jQuery
函數掛載到window
上,因此在外界就能夠訪問到$
和jQuery
了。linux
if ( !noGlobal ) { window.jQuery = window.$ = jQuery; } // 其中`noGlobal`參數只有在這裏用到。
commonjs
實現 主要表明 nodejs
git
// global是全局變量,factory 是函數 ( function( global, factory ) { // 使用嚴格模式 "use strict"; // Commonjs 或者 CommonJS-like 環境 if ( typeof module === "object" && typeof module.exports === "object" ) { // 若是存在global.document 則返回factory(global, true); module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { factory( global ); } // Pass this if window is not defined yet // 第一個參數判斷window,存在返回window,不存在返回this } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {});
if ( typeof define === "function" && define.amd ) { define( "jquery", [], function() { return jQuery; } ); }
很遺憾,jQuery
源碼裏沒有暴露對seajs
的支持。但網上也有一些方案。這裏就不具體提了。畢竟如今基本不用seajs
了。github
實際上也是能夠 new
的,由於jQuery
是函數。並且和不用new
效果是同樣的。
new顯示返回對象,因此和直接調用jQuery
函數做用效果是同樣的。
若是對new
操做符具體作了什麼不明白。能夠參看我以前寫的文章。面試
源碼:ubuntu
var version = "3.4.1", // Define a local copy of jQuery jQuery = function( selector, context ) { // 返回new以後的對象 return new jQuery.fn.init( selector, context ); }; jQuery.fn = jQuery.prototype = { // jQuery當前版本 jquery: version, // 修正構造器爲jQuery constructor: jQuery, length: 0, }; init = jQuery.fn.init = function( selector, context, root ) { // ... if ( !selector ) { return this; } // ... }; init.prototype = jQuery.fn;
jQuery.fn === jQuery.prototype; // true init = jQuery.fn.init; init.prototype = jQuery.fn; // 也就是 jQuery.fn.init.prototype === jQuery.fn; // true jQuery.fn.init.prototype === jQuery.prototype; // true
關於這個筆者畫了一張jQuery
原型關係圖,所謂一圖勝千言。
<sciprt src="https://unpkg.com/jquery@3.4.1/dist/jquery.js"> </script> console.log({jQuery}); // 在谷歌瀏覽器控制檯,能夠看到jQuery函數下掛載了不少靜態屬性和方法,在jQuery.fn 上也掛着不少屬性和方法。
Vue
源碼中,也跟jQuery
相似,執行的是Vue.prototype._init
方法。
function Vue (options) { if (!(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); } initMixin(Vue); function initMixin (Vue) { Vue.prototype._init = function (options) {}; };
用法:
jQuery.extend( target [, object1 ] [, objectN ] ) Returns: Object jQuery.extend( [deep ], target, object1 [, objectN ] )
jQuery.extend API
jQuery.fn.extend API
看幾個例子:
(例子能夠我放到在線編輯代碼的jQuery.extend例子codepen了,能夠直接運行)。
// 1. jQuery.extend( target) var result1 = $.extend({ job: '前端開發工程師', }); console.log(result1, 'result1', result1.job); // $函數 加了一個屬性 job // 前端開發工程師 // 2. jQuery.extend( target, object1) var result2 = $.extend({ name: '若川', }, { job: '前端開發工程師', }); console.log(result2, 'result2'); // { name: '若川', job: '前端開發工程師' } // deep 深拷貝 // 3. jQuery.extend( [deep ], target, object1 [, objectN ] ) var result3 = $.extend(true, { name: '若川', other: { mac: 0, ubuntu: 1, windows: 1, }, }, { job: '前端開發工程師', other: { mac: 1, linux: 1, windows: 0, } }); console.log(result3, 'result3'); // deep true // { // "name": "若川", // "other": { // "mac": 1, // "ubuntu": 1, // "windows": 0, // "linux": 1 // }, // "job": "前端開發工程師" // } // deep false // { // "name": "若川", // "other": { // "mac": 1, // "linux": 1, // "windows": 0 // }, // "job": "前端開發工程師" // }
結論:extend
函數既能夠實現給jQuery
函數能夠實現淺拷貝、也能夠實現深拷貝。能夠給jQuery上添加靜態方法和屬性,也能夠像jQuery.fn
(也就是jQuery.prototype
)上添加屬性和方法,這個功能歸功於this
,jQuery.extend
調用時this
指向是jQuery
,jQuery.fn.extend
調用時this
指向則是jQuery.fn
。
知道這些,其實實現淺拷貝仍是比較容易的:
// 淺拷貝實現 jQuery.extend = function(){ // options 是擴展的對象object1,object2... var options, // object對象上的鍵 name, // copy object對象上的值,也就是是須要拷貝的值 copy, // 擴展目標對象,可能不是對象,因此或空對象 target = arguments[0] || {}, // 定義i爲1 i = 1, // 定義實參個數length length = arguments.length; // 只有一個參數時 if(i === length){ target = this; i--; } for(; i < length; i++){ // 不是underfined 也不是null if((options = arguments[i]) != null){ for(name in options){ copy = options[name]; // 防止死循環,continue 跳出當前這次循環 if ( name === "__proto__" || target === copy ) { continue; } if ( copy !== undefined ) { target[ name ] = copy; } } } } // 最後返回目標對象 return target; }
深拷貝則主要是在如下這段代碼作判斷。多是數組和對象引用類型的值,作判斷。
if ( copy !== undefined ) { target[ name ] = copy; }
爲了方便讀者調試,代碼一樣放在jQuery.extend淺拷貝代碼實現codepen,可在線運行。
$.extend = function(){ // options 是擴展的對象object1,object2... var options, // object對象上的鍵 name, // copy object對象上的值,也就是是須要拷貝的值 copy, // 深拷貝新增的四個變量 deep、src、copyIsArray、clone deep = false, // 源目標,須要往上面賦值的 src, // 須要拷貝的值的類型是函數 copyIsArray, // clone, // 擴展目標對象,可能不是對象,因此或空對象 target = arguments[0] || {}, // 定義i爲1 i = 1, // 定義實參個數length length = arguments.length; // 處理深拷貝狀況 if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target // target目標對象開始後移 target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) // target不等於對象,且target不是函數的狀況下,強制將其賦值爲空對象。 if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } // 只有一個參數時 if(i === length){ target = this; i--; } for(; i < length; i++){ // 不是underfined 也不是null if((options = arguments[i]) != null){ for(name in options){ copy = options[name]; // 防止死循環,continue 跳出當前這次循環 if ( name === "__proto__" || target === copy ) { continue; } // Recurse if we're merging plain objects or arrays // 這裏deep爲true,而且須要拷貝的值有值,而且是純粹的對象 // 或者需拷貝的值是數組 if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { // 源目標,須要往上面賦值的 src = target[ name ]; // Ensure proper type for the source value // 拷貝的值,而且src不是數組,clone對象改成空數組。 if ( copyIsArray && !Array.isArray( src ) ) { clone = []; // 拷貝的值不是數組,對象不是純粹的對象。 } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { // clone 賦值爲空對象 clone = {}; } else { // 不然 clone = src clone = src; } // 把下一次循環時,copyIsArray 須要從新賦值爲false copyIsArray = false; // Never move original objects, clone them // 遞歸調用本身 target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // 最後返回目標對象 return target; };
爲了方便讀者調試,這段代碼一樣放在jQuery.extend深拷貝代碼實現codepen,可在線運行。
判斷參數是不是函數。
var isFunction = function isFunction( obj ) { // Support: Chrome <=57, Firefox <=52 // In some browsers, typeof returns "function" for HTML <object> elements // (i.e., `typeof document.createElement( "object" ) === "function"`). // We don't want to classify *any* DOM node as a function. return typeof obj === "function" && typeof obj.nodeType !== "number"; };
jQuery.isPlainObject(obj)
測試對象是不是純粹的對象(經過 "{}" 或者 "new Object" 建立的)。
jQuery.isPlainObject({}) // true jQuery.isPlainObject("test") // false
var getProto = Object.getPrototypeOf; var class2type = {}; var toString = class2type.toString; var hasOwn = class2type.hasOwnProperty; var fnToString = hasOwn.toString; var ObjectFunctionString = fnToString.call( Object ); jQuery.extend( { isPlainObject: function( obj ) { var proto, Ctor; // Detect obvious negatives // Use toString instead of jQuery.type to catch host objects // !obj 爲true或者 不爲[object Object] // 直接返回false if ( !obj || toString.call( obj ) !== "[object Object]" ) { return false; } proto = getProto( obj ); // Objects with no prototype (e.g., `Object.create( null )`) are plain // 原型不存在 好比 Object.create(null) 直接返回 true; if ( !proto ) { return true; } // Objects with prototype are plain iff they were constructed by a global Object function Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; // 構造器是函數,而且 fnToString.call( Ctor ) === fnToString.call( Object ); return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; }, });
extend
函數,也能夠本身刪掉寫一寫,算是jQuery
中一個比較核心的函數了。並且用途普遍,能夠內部使用也能夠,外部使用擴展 插件等。
jQuery
可以鏈式調用是由於一些函數執行結束後 return this
。
好比jQuery
源碼中的addClass
、removeClass
、toggleClass
。
jQuery.fn.extend({ addClass: function(){ // ... return this; }, removeClass: function(){ // ... return this; }, toggleClass: function(){ // ... return this; }, });
jQuery.noConflict
不少js
庫都會有的防衝突函數用法:
<script> var $ = '我是其餘的$,jQuery不要覆蓋我'; </script> <script src="./jquery-3.4.1.js"> </script> <script> $.noConflict(); console.log($); // 我是其餘的$,jQuery不要覆蓋我 </script>
jQuery.noConflict 源碼
var // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$; jQuery.noConflict = function( deep ) { // 若是已經存在$ === jQuery; // 把已存在的_$賦值給window.$; if ( window.$ === jQuery ) { window.$ = _$; } // 若是deep爲 true, 而且已經存在jQuery === jQuery; // 把已存在的_jQuery賦值給window.jQuery; if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } // 最後返回jQuery return jQuery; };
全文主要經過淺析了jQuery
總體結構,自執行匿名函數、無new
構造、支持多種規範(如commonjs、amd規範)、核心函數之extend
、鏈式調用、jQuery.noConflict
等方面。
從新梳理下文中學習的源碼結構。
// 源碼結構 ( function( global, factory ) "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { factory( global ); } } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { var version = "3.4.1", // Define a local copy of jQuery jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context ); }; jQuery.fn = jQuery.prototype = { jquery: version, constructor: jQuery, length: 0, // ... }; jQuery.extend = jQuery.fn.extend = function() {}; jQuery.extend( { // ... isPlainObject: function( obj ) {}, // ... }); init = jQuery.fn.init = function( selector, context, root ) {}; init.prototype = jQuery.fn; if ( typeof define === "function" && define.amd ) { define( "jquery", [], function() { return jQuery; } ); } jQuery.noConflict = function( deep ) {}; if ( !noGlobal ) { window.jQuery = window.$ = jQuery; } return jQuery; });
能夠學習到jQuery
巧妙的設計和架構,爲本身所用,打造屬於本身的js
類庫。
相關代碼和資源放置在github blog中,須要的讀者能夠自取。
下一篇文章是學習underscorejs
的源碼總體架構。
學習underscorejs總體架構,打造屬於本身的函數式編程類庫
讀者發現有不妥或可改善之處,歡迎評論指出。另外以爲寫得不錯,能夠點贊、評論、轉發,也是對筆者的一種支持。
面試官問:JS的繼承
面試官問:JS的this指向
面試官問:可否模擬實現JS的call和apply方法
面試官問:可否模擬實現JS的bind方法
面試官問:可否模擬實現JS的new操做符
前端使用puppeteer 爬蟲生成《React.js 小書》PDF併合並
chokcoco: jQuery- v1.10.2 源碼解讀
chokcoco:【深刻淺出jQuery】源碼淺析--總體架構
songjz :jQuery 源碼系列(一)整體架構
做者:常以若川爲名混跡於江湖。前端路上 | PPT愛好者 | 所知甚少,惟善學。
我的博客 https://lxchuan12.github.io
github blog,相關源碼和資源都放在這裏,求個star
^_^~
加微信 lxchuan12
,備註寫明來源。拉您進微信羣【前端視野交流羣】。