雖然現(xiàn)在基本不怎么使用jQuery
了,但jQuery
流行10多年
的JS庫
搞莺,還是有必要學(xué)習(xí)它的源碼的息罗。也可以學(xué)著打造屬于自己的js
類庫,求職面試時(shí)可以增色不少才沧。
本文章學(xué)習(xí)的是v3.4.1
版本阱当。
unpkg.com
源碼地址:https://unpkg.com/jquery@3.4.1/dist/jquery.js
自執(zhí)行匿名函數(shù)
(function(global, factory){
})(typeof window !== "underfined" ? window: this, function(window, noGlobal){
});
外界訪問不到里面的變量和函數(shù),里面可以訪問到外界的變量糜工,但里面定義了自己的變量弊添,則不會(huì)訪問外界的變量。
匿名函數(shù)將代碼包裹在里面捌木,防止與其他代碼沖突和污染全局環(huán)境油坝。
關(guān)于自執(zhí)行函數(shù)不是很了解的讀者可以參看這篇文章。
[譯] JavaScript:立即執(zhí)行函數(shù)表達(dá)式(IIFE)
瀏覽器環(huán)境下刨裆,最后把$
和 jQuery
函數(shù)掛載到window
上澈圈,所以在外界就可以訪問到$
和jQuery
了。
if ( !noGlobal ) {
window.jQuery = window.$ = jQuery;
}
// 其中`noGlobal`參數(shù)只有在這里用到帆啃。
支持多種環(huán)境下使用 比如 commonjs瞬女、amd規(guī)范
commonjs 規(guī)范支持
commonjs
實(shí)現(xiàn) 主要代表 nodejs
// global是全局變量,factory 是函數(shù)
( function( global, factory ) {
// 使用嚴(yán)格模式
"use strict";
// Commonjs 或者 CommonJS-like 環(huán)境
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
// 第一個(gè)參數(shù)判斷window努潘,存在返回window诽偷,不存在返回this
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {});
amd 規(guī)范 主要代表 requirejs
if ( typeof define === "function" && define.amd ) {
define( "jquery", [], function() {
return jQuery;
} );
}
cmd 規(guī)范 主要代表 seajs
很遺憾,jQuery
源碼里沒有暴露對(duì)seajs
的支持疯坤。但網(wǎng)上也有一些方案报慕。這里就不具體提了。畢竟現(xiàn)在基本不用seajs
了压怠。
無 new 構(gòu)造
實(shí)際上也是可以 new
的眠冈,因?yàn)?code>jQuery是函數(shù)。而且和不用new
效果是一樣的菌瘫。
new顯示返回對(duì)象蜗顽,所以和直接調(diào)用jQuery
函數(shù)作用效果是一樣的。
如果對(duì)new
操作符具體做了什么不明白雨让」透牵可以參看我之前寫的文章。
面試官問:能否模擬實(shí)現(xiàn)JS的new操作符
源碼:
var
version = "3.4.1",
// Define a local copy of jQuery
jQuery = function( selector, context ) {
// 返回new之后的對(duì)象
return new jQuery.fn.init( selector, context );
};
jQuery.fn = jQuery.prototype = {
// jQuery當(dāng)前版本
jquery: version,
// 修正構(gòu)造器為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
關(guān)于這個(gè)筆者畫了一張jQuery
原型關(guān)系圖宫患,所謂一圖勝千言刊懈。
<sciprt src="https://unpkg.com/jquery@3.4.1/dist/jquery.js">
</script>
console.log({jQuery});
// 在谷歌瀏覽器控制臺(tái),可以看到j(luò)Query函數(shù)下掛載了很多靜態(tài)屬性和方法娃闲,在jQuery.fn 上也掛著很多屬性和方法虚汛。
Vue
源碼中,也跟jQuery
類似皇帮,執(zhí)行的是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) {};
};
核心函數(shù)之一 extend
用法:
jQuery.extend( target [, object1 ] [, objectN ] ) Returns: Object
jQuery.extend( [deep ], target, object1 [, objectN ] )
jQuery.extend API
jQuery.fn.extend API
看幾個(gè)例子:
(例子可以我放到在線編輯代碼的jQuery.extend例子codepen了,可以直接運(yùn)行)属拾。
// 1. jQuery.extend( target)
var result1 = $.extend({
job: '前端開發(fā)工程師',
});
console.log(result1, 'result1', result1.job); // $函數(shù) 加了一個(gè)屬性 job // 前端開發(fā)工程師
// 2. jQuery.extend( target, object1)
var result2 = $.extend({
name: '若川',
},
{
job: '前端開發(fā)工程師',
});
console.log(result2, 'result2'); // { name: '若川', job: '前端開發(fā)工程師' }
// deep 深拷貝
// 3. jQuery.extend( [deep ], target, object1 [, objectN ] )
var result3 = $.extend(true, {
name: '若川',
other: {
mac: 0,
ubuntu: 1,
windows: 1,
},
}, {
job: '前端開發(fā)工程師',
other: {
mac: 1,
linux: 1,
windows: 0,
}
});
console.log(result3, 'result3');
// deep true
// {
// "name": "若川",
// "other": {
// "mac": 1,
// "ubuntu": 1,
// "windows": 0,
// "linux": 1
// },
// "job": "前端開發(fā)工程師"
// }
// deep false
// {
// "name": "若川",
// "other": {
// "mac": 1,
// "linux": 1,
// "windows": 0
// },
// "job": "前端開發(fā)工程師"
// }
結(jié)論:extend
函數(shù)既可以實(shí)現(xiàn)給jQuery
函數(shù)可以實(shí)現(xiàn)淺拷貝将谊、也可以實(shí)現(xiàn)深拷貝〗グ祝可以給jQuery上添加靜態(tài)方法和屬性尊浓,也可以像jQuery.fn
(也就是jQuery.prototype
)上添加屬性和方法,這個(gè)功能歸功于this
纯衍,jQuery.extend
調(diào)用時(shí)this
指向是jQuery
栋齿,jQuery.fn.extend
調(diào)用時(shí)this
指向則是jQuery.fn
。
淺拷貝實(shí)現(xiàn)
知道這些襟诸,其實(shí)實(shí)現(xiàn)淺拷貝還是比較容易的:
// 淺拷貝實(shí)現(xiàn)
jQuery.extend = function(){
// options 是擴(kuò)展的對(duì)象object1瓦堵,object2...
var options,
// object對(duì)象上的鍵
name,
// copy object對(duì)象上的值,也就是是需要拷貝的值
copy,
// 擴(kuò)展目標(biāo)對(duì)象歌亲,可能不是對(duì)象菇用,所以或空對(duì)象
target = arguments[0] || {},
// 定義i為1
i = 1,
// 定義實(shí)參個(gè)數(shù)length
length = arguments.length;
// 只有一個(gè)參數(shù)時(shí)
if(i === length){
target = this;
i--;
}
for(; i < length; i++){
// 不是underfined 也不是null
if((options = arguments[i]) != null){
for(name in options){
copy = options[name];
// 防止死循環(huán),continue 跳出當(dāng)前此次循環(huán)
if ( name === "__proto__" || target === copy ) {
continue;
}
if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// 最后返回目標(biāo)對(duì)象
return target;
}
深拷貝則主要是在以下這段代碼做判斷陷揪⊥锱福可能是數(shù)組和對(duì)象引用類型的值悍缠,做判斷扮休。
if ( copy !== undefined ) {
target[ name ] = copy;
}
為了方便讀者調(diào)試迎卤,代碼同樣放在jQuery.extend淺拷貝代碼實(shí)現(xiàn)codepen,可在線運(yùn)行蜗搔。
深拷貝實(shí)現(xiàn)
$.extend = function(){
// options 是擴(kuò)展的對(duì)象object1,object2...
var options,
// object對(duì)象上的鍵
name,
// copy object對(duì)象上的值樟凄,也就是是需要拷貝的值
copy,
// 深拷貝新增的四個(gè)變量 deep缝龄、src、copyIsArray、clone
deep = false,
// 源目標(biāo)叔壤,需要往上面賦值的
src,
// 需要拷貝的值的類型是函數(shù)
copyIsArray,
//
clone,
// 擴(kuò)展目標(biāo)對(duì)象瞎饲,可能不是對(duì)象,所以或空對(duì)象
target = arguments[0] || {},
// 定義i為1
i = 1,
// 定義實(shí)參個(gè)數(shù)length
length = arguments.length;
// 處理深拷貝情況
if ( typeof target === "boolean" ) {
deep = target;
// Skip the boolean and the target
// target目標(biāo)對(duì)象開始后移
target = arguments[ i ] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
// target不等于對(duì)象炼绘,且target不是函數(shù)的情況下嗅战,強(qiáng)制將其賦值為空對(duì)象。
if ( typeof target !== "object" && !isFunction( target ) ) {
target = {};
}
// 只有一個(gè)參數(shù)時(shí)
if(i === length){
target = this;
i--;
}
for(; i < length; i++){
// 不是underfined 也不是null
if((options = arguments[i]) != null){
for(name in options){
copy = options[name];
// 防止死循環(huán)俺亮,continue 跳出當(dāng)前此次循環(huán)
if ( name === "__proto__" || target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
// 這里deep為true驮捍,并且需要拷貝的值有值,并且是純粹的對(duì)象
// 或者需拷貝的值是數(shù)組
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = Array.isArray( copy ) ) ) ) {
// 源目標(biāo)脚曾,需要往上面賦值的
src = target[ name ];
// Ensure proper type for the source value
// 拷貝的值东且,并且src不是數(shù)組,clone對(duì)象改為空數(shù)組本讥。
if ( copyIsArray && !Array.isArray( src ) ) {
clone = [];
// 拷貝的值不是數(shù)組相速,對(duì)象不是純粹的對(duì)象菩貌。
} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
// clone 賦值為空對(duì)象
clone = {};
} else {
// 否則 clone = src
clone = src;
}
// 把下一次循環(huán)時(shí),copyIsArray 需要重新賦值為false
copyIsArray = false;
// Never move original objects, clone them
// 遞歸調(diào)用自己
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
}
else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// 最后返回目標(biāo)對(duì)象
return target;
};
為了方便讀者調(diào)試,這段代碼同樣放在jQuery.extend深拷貝代碼實(shí)現(xiàn)codepen邦危,可在線運(yùn)行盟劫。
深拷貝衍生的函數(shù) isFunction
判斷參數(shù)是否是函數(shù)祷愉。
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";
};
深拷貝衍生的函數(shù) jQuery.isPlainObject
jQuery.isPlainObject(obj)
測(cè)試對(duì)象是否是純粹的對(duì)象(通過 "{}" 或者 "new Object" 創(chuàng)建的)莱找。
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;
// 構(gòu)造器是函數(shù),并且 fnToString.call( Ctor ) === fnToString.call( Object );
return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
},
});
extend
函數(shù)勤庐,也可以自己刪掉寫一寫示惊,算是jQuery
中一個(gè)比較核心的函數(shù)了。而且用途廣泛愉镰,可以內(nèi)部使用也可以米罚,外部使用擴(kuò)展 插件等。
鏈?zhǔn)秸{(diào)用
jQuery
能夠鏈?zhǔn)秸{(diào)用是因?yàn)橐恍┖瘮?shù)執(zhí)行結(jié)束后 return this
丈探。
比如
jQuery
源碼中的addClass
录择、removeClass
、toggleClass
碗降。
jQuery.fn.extend({
addClass: function(){
// ...
return this;
},
removeClass: function(){
// ...
return this;
},
toggleClass: function(){
// ...
return this;
},
});
jQuery.noConflict
很多js
庫都會(huì)有的防沖突函數(shù)
用法:
<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 ) {
// 如果已經(jīng)存在$ === jQuery;
// 把已存在的_$賦值給window.$;
if ( window.$ === jQuery ) {
window.$ = _$;
}
// 如果deep為 true, 并且已經(jīng)存在jQuery === jQuery;
// 把已存在的_jQuery賦值給window.jQuery;
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
// 最后返回jQuery
return jQuery;
};
總結(jié)
全文主要通過淺析了jQuery
整體結(jié)構(gòu)讼渊,自執(zhí)行匿名函數(shù)动看、無new
構(gòu)造、支持多種規(guī)范(如commonjs爪幻、amd規(guī)范)菱皆、核心函數(shù)之extend
须误、鏈?zhǔn)秸{(diào)用、jQuery.noConflict
等方面仇轻。
重新梳理下文中學(xué)習(xí)的源碼結(jié)構(gòu)京痢。
// 源碼結(jié)構(gòu)
( 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;
});
可以學(xué)習(xí)到jQuery
巧妙的設(shè)計(jì)和架構(gòu),為自己所用拯田,打造屬于自己的js
類庫历造。
相關(guān)代碼和資源放置在github blog中甩十,需要的讀者可以自取船庇。
下一篇文章可能是學(xué)習(xí)underscorejs
的源碼整體架構(gòu)。
讀者發(fā)現(xiàn)有不妥或可改善之處侣监,歡迎評(píng)論指出鸭轮。另外覺得寫得不錯(cuò),可以點(diǎn)贊橄霉、評(píng)論窃爷、轉(zhuǎn)發(fā),也是對(duì)筆者的一種支持姓蜂。
筆者往期文章
面試官問:能否模擬實(shí)現(xiàn)JS的call和apply方法
面試官問:能否模擬實(shí)現(xiàn)JS的bind方法
面試官問:能否模擬實(shí)現(xiàn)JS的new操作符
前端使用puppeteer 爬蟲生成《React.js 小書》PDF并合并
擴(kuò)展閱讀
chokcoco: jQuery- v1.10.2 源碼解讀
chokcoco:【深入淺出jQuery】源碼淺析--整體架構(gòu)
songjz :jQuery 源碼系列(一)總體架構(gòu)
關(guān)于
作者:常以若川為名混跡于江湖按厘。前端路上 | PPT愛好者 | 所知甚少,唯善學(xué)钱慢。
個(gè)人博客 https://lxchuan12.github.io
github blog逮京,相關(guān)源碼和資源都放在這里,求個(gè)star
_~
微信交流群
加微信 lxchuan12
束莫,備注寫明來源懒棉。拉您進(jìn)微信群【前端視野交流群】。