本文章學習的是 v3.4.1版本。unpkg.com源碼地址:https://unpkg.com/jquery@3.4.1/dist/jquery.js前端
jQuery github倉庫node
(function(global, factory){jquery
})(typeof window !== "underfined" ? window: this, function(window, noGlobal){linux
});git
外界訪問不到裏面的變量和函數,裏面能夠訪問到外界的變量,但裏面定義了本身的變量,則不會訪問外界的變量。匿名函數將代碼包裹在裏面,防止與其餘代碼衝突和污染全局環境。關於自執行函數不是很瞭解的讀者能夠參看這篇文章。[譯] JavaScript:當即執行函數表達式(IIFE)github
瀏覽器環境下,最後把 $ 和 jQuery函數掛載到 window上,因此在外界就能夠訪問到 $和 jQuery了。面試
if ( !noGlobal ) {ubuntu
window.jQuery = window.$ = jQuery;windows
}數組
// 其中`noGlobal`參數只有在這裏用到。
commonjs實現 主要表明 nodejs
// 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了。
實際上也是能夠 new的,由於 jQuery是函數。並且和不用 new效果是同樣的。new顯示返回對象,因此和直接調用 jQuery函數做用效果是同樣的。若是對 new操做符具體作了什麼不明白。能夠參看我以前寫的文章。
面試官問:可否模擬實現JS的new操做符
源碼:
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原型關係圖,所謂一圖勝千言。
jquery-v3.4.1 原型關係圖
<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 APIjQuery.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可以鏈式調用是由於一些函數執行結束後 returnthis。好比 jQuery 源碼中的 addClass、 removeClass、 toggleClass。
jQuery.fn.extend({
addClass: function(){
// ...
return this;
},
removeClass: function(){
// ...
return this;
},
toggleClass: function(){
// ...
return this;
},
});
jQuery.noConflict API
用法:
<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的源碼總體架構。