摘要:到目前為止,的貢獻者團隊共名成員,多條,可想而知,是一個多么龐大的項目。參考源碼分析整體架構(gòu)源碼解析讀書筆記第二章構(gòu)造對象函數(shù)詳解本文在上的源碼地址,歡迎來。
歡迎來我的專欄查看系列文章。
決定你走多遠的是基礎(chǔ),jQuery 源碼分析,向長者膜拜!
我雖然接觸 jQuery 很久了,但也只是局限于表面使用的層次,碰到一些問題,找到 jQuery 的解決辦法,然后使用。顯然,這種做法的弊端就是,無論你怎么學(xué),都只能是個小白。
當我建立這個項目的時候,就表示,我要改變這一切了,做一些人想做,憧憬去做,但從沒踏入第一步的事情,學(xué)習 jQuery 源碼。
到目前為止,jQuery 的貢獻者團隊共 256 名成員,6000 多條 commits,可想而知,jQuery 是一個多么龐大的項目。jQuery 官方的版本目前是 v3.1.1,已經(jīng)衍生出 jQueryUI、jQueryMobile 等多個項目。
雖然我在前端爬摸打滾一年多,自認基礎(chǔ)不是很好,在沒有外界幫助的情況下,直接閱讀項目源碼太難了,所以在邊參考遍實踐的過程中寫下來這個項目。
首先,先推薦一個 jQuery 的源碼查詢網(wǎng)站,這個網(wǎng)站給初學(xué)者非常大的幫助,不僅能查找不同版本的 jQuery 源碼,還能索引函數(shù),功能簡直吊炸天。
另外,推薦兩個分析 jQuery 的博客:
jQuery源碼分析系列
原創(chuàng) jQuery1.6.1源碼分析系列(停止更新)
這兩個博客給我了很大的幫助,謝謝。
另外還有下面的網(wǎng)址,讓我在如何使用 jQuery 上得心應(yīng)手:
jQuery 總體架構(gòu)jQuery API 中文文檔
首先,jQuery 是一個開發(fā)框架,它的火爆程度已經(jīng)無法用言語來形容,當你隨便打開一個網(wǎng)站,一半以上直接使用了 jQuery。或許,早幾年,一個前端工程師,只要會寫 jQuery,就可以無憂工作。雖說最近 react、vue 很火,但 jQuery 中許多精彩的方法和邏輯值得每一個前端人員學(xué)習。
和其眾多的框架一樣,總要把接口放到外面來調(diào)用,內(nèi)部往往是一個閉包,避免環(huán)境變量的污染。
先來看看 jQuery 使用上的幾大特點:
$("#id") 函數(shù)方式直接生成 jQuery 對象
$("#id").css().html().hide() 鏈式調(diào)用
關(guān)于鏈式調(diào)用,我想有點基礎(chǔ)都很容易實現(xiàn),函數(shù)結(jié)尾 return this 即可,主要來介紹一下無 new 實現(xiàn)創(chuàng)建對象。
無 new 函數(shù)實現(xiàn)下面是一個普通的函數(shù),很顯然,會陷入死循環(huán):
var jQuery = function(){ return new jQuery(); } jQuery.prototype = { ... }
這個死循環(huán)來的太突然,jQuery() 會創(chuàng)建一個 new jQuery,new jQuery 又會創(chuàng)建一個 new jQuery...
jQuery 用一個 init 函數(shù)來代替直接 new 函數(shù)名的方式,還要考慮到 jQuery 中分離作用域:
var jQuery = function(){ return new jQuery.prototype.init(); } jQuery.prototype = { constructor: jQuery, init: function(){ this.jquery = 1.0; return this; }, jquery: 2.0, each: function(){ console.log("each"); return this; } } jQuery().jquery //1.0 jQuery.prototype.jquery //2.0 jQuery().each() // error
上面看似運行正常,但是問題出在 jQuery().each() // error,訪問不到 each 函數(shù)。實際上,new jQuery.prototype.init() 返回到是誰的實例?是 init 這個函數(shù)的實例,所以 init 函數(shù)中的 this 就沒了意義。
那么,如果:
var jq = jQuery(); jq.__proto__ === jQuery.prototype; jq.each === jQuery.prototype.each;
如果可以實現(xiàn)上面的 proto 的指向問題,原型函數(shù)調(diào)用問題就解決了,但實際上:
var jq = jQuery(); jq.__proto__ === jQuery.prototype.init.prototype; //true
實際上,jq 的 proto 是指向 init 函數(shù)的原型,所以,我們可以把 jQuery.prototype.init.prototype = jQuery.prototype,這個時候,函數(shù)調(diào)用就順理成章了,而且使用的都是引用,指向的都是同一個 prototype 對象,也不需要擔心循環(huán)問題。實際上,jQuery 就是這么干的。
var jQuery = function(){ return new jQuery.prototype.init(); } jQuery.prototype = { constructor: jQuery, init: function(){ this.jquery = 1.0; return this; }, jquery: 2.0, each: function(){ console.log("each"); return this; } } jQuery.prototype.init.prototype = jQuery.prototype; jQuery().each() //"each"jQuery 內(nèi)部結(jié)構(gòu)圖
在說內(nèi)部圖之前,先說下 jQuery.fn,它實際上是 prototype 的一個引用,指向 jQuery.prototype 的,
var jQuery = function(){ return new jQuery.prototype.init(); } jQuery.fn = jQuery.prototype = { ... }
那么為什么要用 fn 指向 prototype?我本人查閱了一些資料,貌似還是下面的回答比較中肯:簡介。你不覺得 fn 比 prototype 好寫多了嗎。
借用網(wǎng)上的一張圖:
從這張圖中可以看出,window 對象上有兩個公共的接口,分別是 $ 和 jQuery:
window.jQuery = window.$ = jQuery;
jQuery.extend 方法是一個對象拷貝的方法,包括深拷貝,后面會詳細講解源碼,暫時先放一邊。
下面的關(guān)系可能會有些亂,但是仔細看了前面的介紹,應(yīng)該能看懂。fn 就是 prototype,所以 jQuery 的 fn 和 prototype 屬性指向 fn 對象,而 init 函數(shù)本身就是 jQuery.prototype 中的方法,且 init 函數(shù)的 prototype 原型指向 fn。
鏈式調(diào)用鏈式調(diào)用的好處,就是寫出來的代碼非常簡潔,而且代碼返回的都是同一個對象,提高代碼效率。
前面已經(jīng)說了,在沒有返回值的原型函數(shù)后面添加 return this:
var jQuery = function(){ return new jQuery.fn.init(); } jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function(){ this.jquery = 3.0; return this; }, each: function(){ console.log("each"); return this; } } jQuery.fn.init.prototype = jQuery.fn; jQuery().each().each(); // "each" // "each"extend
jQuery 中一個重要的函數(shù)便是 extend,既可以對本身 jQuery 的屬性和方法進行擴張,又可以對原型的屬性和方法進行擴展。
先來說下 extend 函數(shù)的功能,大概有兩種,如果參數(shù)只有一個 object,即表示將這個對象擴展到 jQuery 的命名空間中,也就是所謂的 jQuery 的擴展。如果函數(shù)接收了多個 object,則表示一種屬性拷貝,將后面多個對象的屬性全拷貝到第一個對象上,這其中,還包括深拷貝,即非引用拷貝,第一個參數(shù)如果是 true 則表示深拷貝。
jQuery.extend(target);// jQuery 的擴展 jQuery.extend(target, obj1, obj2,..);//淺拷貝 jQuery.extend(true, target, obj1, obj2,..);//深拷貝
以下是 jQuery 3 之后的 extend 函數(shù)源碼,自己做了注釋:
jQuery.extend = jQuery.fn.extend = function () { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // 判斷是否為深拷貝 if (typeof target === "boolean") { deep = target; // 參數(shù)后移 target = arguments[i] || {}; i++; } // 處理 target 是字符串或奇怪的情況,isFunction(target) 可以判斷 target 是否為函數(shù) if (typeof target !== "object" && !jQuery.isFunction(target)) { target = {}; } // 判斷是否 jQuery 的擴展 if (i === length) { target = this; // this 做一個標記,可以指向 jQuery,也可以指向 jQuery.fn i--; } for (; i < length; i++) { // null/undefined 判斷 if ((options = arguments[i]) != null) { // 這里已經(jīng)統(tǒng)一了,無論前面函數(shù)的參數(shù)怎樣,現(xiàn)在的任務(wù)就是 target 是目標對象,options 是被拷貝對象 for (name in options) { src = target[name]; copy = options[name]; // 防止死循環(huán),跳過自身情況 if (target === copy) { continue; } // 深拷貝,且被拷貝對象是 object 或 array // 這是深拷貝的重點 if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) { // 說明被拷貝對象是數(shù)組 if (copyIsArray) { copyIsArray = false; clone = src && Array.isArray(src) ? src : []; // 被拷貝對象是 object } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } // 遞歸拷貝子屬性 target[name] = jQuery.extend(deep, clone, copy); // 常規(guī)變量,直接 = } else if (copy !== undefined) { target[name] = copy; } } } } // Return the modified object return target; }
extend 函數(shù)符合 jQuery 中的參數(shù)處理規(guī)范,算是比較標準的一個。jQuery 對于參數(shù)的處理很有一套,總是喜歡錯位來使得每一個位置上的變量和它們的名字一樣,各司其職。比如 target 是目標對象,如果第一個參數(shù)是 boolean 型的,就對 deep 賦值 target,并把 target 向后移一位;如果參數(shù)對象只有一個,即對 jQuery 的擴展,就令 target 賦值 this,當前指針 i 減一。
這種方法邏輯雖然很復(fù)雜,但是帶來一個非常大的優(yōu)勢:后面的處理邏輯只需要一個就可以。target 就是我們要拷貝的目標,options 就是要拷貝的對象,邏輯又顯得非常的清晰。
extend 函數(shù)還需要主要一點,jQuery.extend = jQuery.fn.extend,不僅 jQuery 對象又這個函數(shù),連原型也有,那么如何區(qū)分對象是擴展到哪里了呢,又是如何實現(xiàn)的?
其實這一切都要借助與 javascript 中 this 的動態(tài)性,target = this,代碼就放在那里,誰去執(zhí)行,this 就會指向誰,就會在它的屬性上擴展。
由 extend 衍生的函數(shù)再看 extend 源碼,里面有一些函數(shù),只是看名字知道了它是干什么的,我專門挑出來,找到它們的源碼。
jQuery.isFunction 源碼jQuery.isFunction = function (obj) { return jQuery.type(obj) === "function"; }
這也太簡單了些。這里又要引出 jQuery 里一個重要的函數(shù) jQuery.type,這個函數(shù)用于類型判斷。
首先,為什么傳統(tǒng)的 typeof 不用?因為不好用(此處應(yīng)有一個哭臉):
// Numbers typeof 37 === "number"; typeof 3.14 === "number"; typeof(42) === "number"; typeof Math.LN2 === "number"; typeof Infinity === "number"; typeof NaN === "number"; // Despite being "Not-A-Number" typeof Number(1) === "number"; // but never use this form! // Strings typeof "" === "string"; typeof "bla" === "string"; typeof (typeof 1) === "string"; // typeof always returns a string typeof String("abc") === "string"; // but never use this form! // Booleans typeof true === "boolean"; typeof false === "boolean"; typeof Boolean(true) === "boolean"; // but never use this form! // Symbols typeof Symbol() === "symbol" typeof Symbol("foo") === "symbol" typeof Symbol.iterator === "symbol" // Undefined typeof undefined === "undefined"; typeof declaredButUndefinedVariable === "undefined"; typeof undeclaredVariable === "undefined"; // Objects typeof {a:1} === "object"; // use Array.isArray or Object.prototype.toString.call // to differentiate regular objects from arrays typeof [1, 2, 4] === "object"; typeof new Date() === "object"; // The following is confusing. Don"t use! typeof new Boolean(true) === "object"; typeof new Number(1) === "object"; typeof new String("abc") === "object"; // Functions typeof function(){} === "function"; typeof class C {} === "function"; typeof Math.sin === "function"; // This stands since the beginning of JavaScript typeof null === "object";
可以看得出來,對于一些 new 對象,比如 new Number(1),也會返回 object。具體請參考typeof MDN。
網(wǎng)上有兩種解決方法(有效性未經(jīng)考證,請相信 jQuery 的方法),一種是用 constructor.nameObject.prototype.constructor MDN,一種是用 Object.prototype.toString.call()Object.prototype.toString(),最終 jQuery 選擇了后者。
var n1 = 1; n1.constructor.name;//"Number" var n2 = new Number(1); n2.constructor.name;//"Number" var toString = Object.prototype.toString; toString.call(n1);//"[object Number]" toString.call(n2);//"[object Number]"
以上屬于科普,原理不多闡述,接下來繼續(xù)看源碼 jQuery.type:
// 這個對象是用來將 toString 函數(shù)返回的字符串轉(zhuǎn)成 var class2type = { "[object Boolean]": "boolean", "[object Number]": "number", "[object String]": "string", "[object Function]": "function", "[object Array]": "array", "[object Date]": "date", "[object RegExp]": "regexp", "[object Object]": "object", "[object Error]": "error", "[object Symbol]": "symbol" } var toString = Object.prototype.toString; jQuery.type = function (obj) { if (obj == null) { return obj + ""; } return typeof obj === "object" || typeof obj === "function" ? class2type[toString.call(obj)] || "object" : typeof obj; }
因為 jQuery 用的是 toString 方法,所以需要有一個 class2type 的對象用來轉(zhuǎn)換。
jQuery.isPlainObject這個函數(shù)用來判斷對象是否是一個純粹的對象,:
var getProto = Object.getPrototypeOf;//獲取父對象 var hasOwn = class2type.hasOwnProperty; var fnToString = hasOwn.toString; var ObjectFunctionString = fnToString.call( Object ); jQuery.isPlainObject = function (obj) { var proto, Ctor; // 排除 underfined、null 和非 object 情況 if (!obj || toString.call(obj) !== "[object Object]") { return false; } proto = getProto(obj); // Objects with no prototype (e.g., `Object.create( null )`) are plain 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; return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString; }
看一下效果:
jQuery.isPlainObject({});// true jQuery.isPlainObject({ a: 1 });// true jQuery.isPlainObject(new Object());// true jQuery.isPlainObject([]);// false jQuery.isPlainObject(new String("a"));// false jQuery.isPlainObject(function(){});// false
除了這幾個函數(shù)之外,還有個 Array.isArray(),這個真的不用介紹了吧。
總結(jié)總結(jié)還是多說一點的好,現(xiàn)在已經(jīng)基本理清 jQuery 內(nèi)部的情況了?no,還差一點,看下面的代碼:
(function(window) { // jQuery 變量,用閉包避免環(huán)境污染 var jQuery = (function() { var jQuery = function(selector, context) { return new jQuery.fn.init(selector, context, rootjQuery); }; // 一些變量聲明 jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function(selector, context, rootjQuery) { // 下章會重點討論 } // 原型方法 }; jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() {};//已介紹 jQuery.extend({ // 一堆靜態(tài)屬性和方法 // 用 extend 綁定,而不是直接在 jQuery 上寫 }); return jQuery; })(); // 工具方法 Utilities // 回調(diào)函數(shù)列表 Callbacks Object // 異步隊列 Defferred Object // 瀏覽器功能測試 Support // 數(shù)據(jù)緩存 Data // 隊列 Queue // 屬性操作 Attributes // 事件系統(tǒng) Events // 選擇器 Sizzle // DOM遍歷 Traversing // 樣式操作 CSS(計算樣式、內(nèi)聯(lián)樣式) // 異步請求 Ajax // 動畫 Effects // 坐標 Offset、尺寸 Dimensions window.jQuery = window.$ = jQuery; })(window);
可以看出 jQuery 很巧妙的整體布局思路,對于屬性方法和原型方法等區(qū)分,防止變量污染等,都做的非常好。閱讀框架源碼只是開頭,有趣的還在后面。
參考jQuery 2.0.3 源碼分析core - 整體架構(gòu)
《jQuery源碼解析》讀書筆記(第二章:構(gòu)造jQuery對象)
jQuery.isPlainObject() 函數(shù)詳解
本文在 github 上的源碼地址,歡迎來 star。
歡迎來我的博客交流。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/88136.html
摘要:而事件委托的概念事件目標自身不處理事件,而是將其委托給父元素或祖先元素或根元素,而借助事件的冒泡性質(zhì)由內(nèi)向外來達到最終處理事件。而且一旦出現(xiàn),局部刷新導(dǎo)致重新綁定事件。函數(shù)的用法,代表要移除的事件,表示選擇的,表示事件處理函數(shù)。 歡迎來我的專欄查看系列文章。 這次的內(nèi)容是來介紹關(guān)于 jQuery 的事件委托。不過在之前呢有必要先來了解一下 JS 中的事件委托與冒泡,我之前也寫過類似的博...
摘要:源碼中接受個參數(shù),空參數(shù),這個會直接返回一個空的對象,。,這是一個標準且常用法,表示一個選擇器,這個選擇器通常是一個字符串,或者等,表示選擇范圍,即限定作用,可為,對象。,會把普通的對象或?qū)ο蟀b在對象中。介紹完入口,就開始來看源碼。 歡迎來我的專欄查看系列文章。 init 構(gòu)造器 前面一講總體架構(gòu)已經(jīng)介紹了 jQuery 的基本情況,這一章主要來介紹 jQuery 的入口函數(shù) jQu...
摘要:不過這樣子又回帶來另一個問題,對于函數(shù),函數(shù)返回什么不重要,主要是處理過程,可以支持鏈式調(diào)用,對于函數(shù),返回的是處理后的結(jié)果,可以不用鏈式,所以函數(shù)就是來判斷是否需要鏈式,而對返回值進行處理。然后后面還有一個函數(shù),也是用來作為回調(diào)函數(shù)的。 其實,學(xué)習一個庫的源碼,最重要的就是先理清它的基本架構(gòu),jQuery 是這樣,Underscore 也應(yīng)該是這樣。 Underscore 這個庫提供...
摘要:我拖拖拖拖放基礎(chǔ)篇前端掘金不要搞錯,本文不是講如何拖地的。結(jié)構(gòu)說明前端應(yīng)該從哪些方面來優(yōu)化網(wǎng)站前端掘金不知道是哪位大牛的文章,轉(zhuǎn)過來回答。 我拖拖拖 --H5 拖放 API 基礎(chǔ)篇 - 前端 - 掘金不要搞錯,本文不是講如何拖地的。看過《javascript精粹》朋友應(yīng)該知道,他實現(xiàn)拖放的過程比較復(fù)雜,現(xiàn)在時代不同了,我們用H5的新的拖放API就能非常方便的實現(xiàn)拖放效果了。最近在園子見...
閱讀 3694·2021-11-11 10:58
閱讀 2476·2021-09-22 15:43
閱讀 2869·2019-08-30 15:44
閱讀 2188·2019-08-30 13:08
閱讀 1821·2019-08-29 17:28
閱讀 884·2019-08-29 10:54
閱讀 675·2019-08-26 11:46
閱讀 3507·2019-08-26 11:43