1. 程式人生 > >【深入淺出jQuery】原始碼淺析2--奇技淫巧

【深入淺出jQuery】原始碼淺析2--奇技淫巧

原文:http://www.cnblogs.com/coco1s/p/5303041.html

最近一直在研讀 jQuery 原始碼,初看原始碼一頭霧水毫無頭緒,真正靜下心來細看寫的真是精妙,讓你感嘆程式碼之美。

其結構明晰,高內聚、低耦合,兼具優秀的效能與便利的擴充套件性,在瀏覽器的相容性(功能缺陷、漸進增強)優雅的處理能力以及 Ajax 等方面周到而強大的定製功能無不令人驚歎。

另外,閱讀原始碼讓我接觸到了大量底層的知識。對原生JS 、框架設計、程式碼優化有了全新的認識,接下來將會寫一系列關於 jQuery 解析的文章。

我在 github 上關於 jQuery 原始碼的全文註解,感興趣的可以圍觀一下。

jQuery v1.10.2 原始碼註解 

 

系列第一篇:【深入淺出jQuery】原始碼淺析--整體架構

本篇是系列第二篇,標題起得有點大,希望內容對得起這個標題,這篇文章主要總結一下在 jQuery 中一些十分討巧的 coding 方式,將會由淺及深,可能會有一些基礎,但是我希望全面一點,對看文章的人都有所幫助,原始碼我還一直在閱讀,也會不斷的更新本文。

即便你不想去閱讀原始碼,看看下面的總結,我想對提高程式設計能力,轉換思維方式都大有裨益,廢話少說,進入正題。

 

   短路表示式 與 多重短路表示式

短路表示式這個應該人所皆知了。在 jQuery 中,大量的使用了短路表示式與多重短路表示式。

短路表示式:作為"&&"和"||"操作符的運算元表示式,這些表示式在進行求值時,只要最終的結果已經可以確定是真或假,求值過程便告終止,這稱之為短路求值。這是這兩個操作符的一個重要屬性。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

// ||短路表示式

var foo = a || b;

// 相當於

if(a){

    foo = a;

}else{

    foo = b;

}

 

// &&短路表示式

var bar = a && b;

// 相當於

if(a){

    bar = b;

}else{

    bar = a;

}

當然,上面兩個例子是短路表示式最簡單是情況,多數情況下,jQuery 是這樣使用它們的:

1

2

3

4

5

6

7

8

9

// 選自 jQuery 原始碼中的 Sizzle 部分

function siblingCheck(a, b) {

    var cur = b && a,

        diff = cur && a.nodeType === 1 && b.nodeType === 1 &&

        (~b.sourceIndex || MAX_NEGATIVE) -

        (~a.sourceIndex || MAX_NEGATIVE);

 

    // other code ...  

}

嗯,可以看到,diff 的值經歷了多重短路表示式配合一些全等判斷才得出,這種程式碼很優雅,但是可讀性下降了很多,使用的時候權衡一下,多重短路表示式和簡單短路表示式其實一樣,只需要先把後面的當成一個整體,依次推進,得出最終值。

1

2

3

4

var a = 1, b = 0, c = 3;

 

var foo = a && b && c, // 0 ,相當於 a && (b && c)

  bar = a || b || c;  // 1

這裡需要提出一些值得注意的點:

1、在 Javascript 的邏輯運算中,0、""、null、false、undefined、NaN 都會判定為 false ,而其他都為 true ;

2、因為 Javascript 的內建弱型別域 (weak-typing domain),所以對嚴格的輸入驗證這一點不太在意,即便使用 && 或者 || 運算子的運算數不是布林值,仍然可以將它看作布林運算。雖然如此,還是建議如下:

1

2

if(foo){ ... }     //不夠嚴謹

if(!!foo){ ... }   //更為嚴謹,!!可將其他型別的值轉換為boolean型別

注重細節,JavaScript 既不弱也不低等,我們只是需要更努力一點工作以使我們的程式碼變得真正健壯。

 

   預定義常用方法的入口

在 jQuery 的頭幾十行,有這麼一段有趣的程式碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

(function(window, undefined) {

    var

        // 定義了一個物件變數,一個字串變數,一個數組變數

        class2type = {},

        core_version = "1.10.2",

        core_deletedIds = [],

 

        // 儲存了物件、字串、陣列的一些常用方法 concat push 等等...

        core_concat = core_deletedIds.concat,

        core_push = core_deletedIds.push,

        core_slice = core_deletedIds.slice,

        core_indexOf = core_deletedIds.indexOf,

        core_toString = class2type.toString,

        core_hasOwn = class2type.hasOwnProperty,

        core_trim = core_version.trim;

         

})(window);

不得不說,jQuery 在細節上做的真的很好,這裡首先定義了一個物件變數、一個字串變數、陣列變數,要注意這 3 個變數本身在下文是有自己的用途的(可以看到,jQuery 作者惜字如金,真的是去壓榨每一個變數的作用,使其作用最大化);其次,借用這三個變數,再定義些常用的核心方法,從上往下是陣列的 concat、push 、slice 、indexOf 方法,物件的 toString 、hasOwnProperty 方法以及字串的 trim 方法,core_xxxx 這幾個變數事先儲存好了這些常用方法的入口,如果下文行文當中需要呼叫這些方法,將會:

1

2

3

4

5

6

7

8

9

10

jQuery.fn = jQuery.prototype = {

    // ...

     

    // 將 jQuery 物件轉換成陣列型別

    toArray: function() {

        // 呼叫陣列的 slice 方法,使用預先定義好了的 core_slice ,節省查詢記憶體地址時間,提高效率

        // 相當於 return Array.prototype.slice.call(this)

        return core_slice.call(this);

    }

}

可以看到,當需要使用這些預先定義好的方法,只需要藉助 call 或者 apply(戳我詳解)進行呼叫。

那麼 jQuery 為什麼要這樣做呢,我覺得:

1、以陣列物件的 concat 方法為例,如果不預先定義好 core_concat = core_deletedIds.concat 而是呼叫例項 arr 的方法 concat 時,首先需要辨別當前例項 arr 的型別是 Array,在記憶體空間中尋找 Array 的 concat 記憶體入口,把當前物件 arr 的指標和其他引數壓入棧,跳轉到 concat 地址開始執行,而當儲存了 concat 方法的入口 core_concat 時,完全就可以省去前面兩個步驟,從而提升一些效能;

2、另外一點,藉助 call 或者 apply 的方式呼叫,讓一些類陣列可以直接呼叫陣列的方法。就如上面是示例,jQuery 物件是類陣列型別,可以直接呼叫陣列的 slice 方法轉換為陣列型別。又譬如,將引數 arguments 轉換為陣列型別:

1

2

3

4

5

6

7

function test(a,b,c){

    // 將引數 arguments 轉換為陣列

    // 使之可以呼叫陣列成員方法

    var arr = Array.prototype.slice.call(arguments);

 

    ...

}

 

   鉤子機制(hook)

在 jQuery 2.0.0 之前的版本,對相容性做了大量的處理,正是這樣才讓廣大開發人員能夠忽略不同瀏覽器的不同特性的專注於業務本身的邏輯。而其中,鉤子機制在瀏覽器相容方面起了十分巨大的作用。

鉤子是程式設計慣用的一種手法,用來解決一種或多種特殊情況的處理。

簡單來說,鉤子就是介面卡原理,或者說是表驅動原理,我們預先定義了一些鉤子,在正常的程式碼邏輯中使用鉤子去適配一些特殊的屬性,樣式或事件,這樣可以讓我們少寫很多 else if 語句。

如果還是很難懂,看一個簡單的例子,舉例說明 hook 到底如何使用:

現在考公務員,要麼靠實力,要麼靠關係,但領導肯定也不會弄的那麼明顯,一般都是暗箱操作,這個場景用鉤子實現再合理不過了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

// 如果不用鉤子的情況

// 考生分數以及父親名

function examinee(name, score, fatherName) {

    return {

        name: name,

        score: score,

        fatherName: fatherName

    };

}

  

// 審閱考生們

function judge(examinees) {

    var result = {};

    for (var in examinees) {

        var curExaminee = examinees[i];

        var ret = curExaminee.score;

        // 判斷是否有後門關係

        if (curExaminee.fatherName === 'xijingping') {

            ret += 1000;

        else if (curExaminee.fatherName === 'ligang') {

            ret += 100;

        else if (curExaminee.fatherName === 'pengdehuai') {

            ret += 50;

        }

        result[curExaminee.name] = ret;

    }

    return result;

}

  

  

var lihao = examinee("lihao", 10, 'ligang');

var xida = examinee('xida', 8, 'xijinping');

var peng = examinee('peng', 60, 'pengdehuai');

var liaoxiaofeng = examinee('liaoxiaofeng', 100, 'liaodaniu');

  

var result = judge([lihao, xida, peng, liaoxiaofeng]);

  

// 根據分數選取前三名

for (var name in result) {

    console.log("name:" + name);

    console.log("score:" + score);

}

可以看到,在中間審閱考生這個函式中,運用了很多 else if 來判斷是否考生有後門關係,如果現在業務場景發生變化,又多了幾名考生,那麼 else if 勢必越來越複雜,往後維護程式碼也將越來越麻煩,成本很大,那麼這個時候如果使用鉤子機制,該如何做呢?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

// relationHook 是個鉤子函式,用於得到關係得分

var relationHook = {

    "xijinping": 1000,   

    "ligang": 100,

    "pengdehuai": 50,

   // 新的考生只需要在鉤子裡新增關係分

}

 

// 考生分數以及父親名

function examinee(name, score, fatherName) {

    return {

        name: name,

        score: score,

        fatherName: fatherName

    };

}

  

// 審閱考生們

function judge(examinees) {

    var result = {};

    for (var in examinees) {

        var curExaminee = examinees[i];

        var ret = curExaminee.score;

        if (relationHook[curExaminee.fatherName] ) {

            ret += relationHook[curExaminee.fatherName] ;

        }

        result[curExaminee.name] = ret;

    }

    return result;

}

  

  

var lihao = examinee("lihao", 10, 'ligang');

var xida = examinee('xida', 8, 'xijinping');

var peng = examinee('peng', 60, 'pengdehuai');

var liaoxiaofeng = examinee('liaoxiaofeng', 100, 'liaodaniu');

  

var result = judge([lihao, xida, peng, liaoxiaofeng]);

  

// 根據分數選取前三名

for (var name in result) {

    console.log("name:" + name);

    console.log("score:" + score);

}

可以看到,使用鉤子去處理特殊情況,可以讓程式碼的邏輯更加清晰,省去大量的條件判斷,上面的鉤子機制的實現方式,採用的就是表驅動方式,就是我們事先預定好一張表(俗稱打表),用這張表去適配特殊情況。當然 jQuery 的 hook 是一種更為抽象的概念,在不同場景可以用不同方式實現。

看看 jQuery 裡的表驅動 hook 實現,$.type 方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

(function(window, undefined) {

    var

        // 用於預儲存一張型別表用於 hook

        class2type = {};

 

    // 原生的 typeof 方法並不能區分出一個變數它是 Array 、RegExp 等 object 型別,jQuery 為了擴充套件 typeof 的表達力,因此有了 $.type 方法

    // 針對一些特殊的物件(例如 null,Array,RegExp)也進行精準的型別判斷

    // 運用了鉤子機制,判斷型別前,將常見型別打表,先存於一個 Hash 表 class2type 裡邊

    jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {

        class2type["[object " + name + "]"] = name.toLowerCase();

    });

 

    jQuery.extend({

        // 確定JavaScript 物件的型別

        // 這個方法的關鍵之處在於 class2type[core_toString.call(obj)]

        // 可以使得 typeof obj 為 "object" 型別的得到更進一步的精確判斷

        type: function(obj) {

 

            if (obj == null) {

                return String(obj);

            }

            // 利用事先存好的 hash 表 class2type 作精準判斷

            // 這裡因為 hook 的存在,省去了大量的 else if 判斷

            return typeof obj === "object" || typeof obj === "function" ?

                class2type[core_toString.call(obj)] || "object" :

                typeof obj;

        }

    })

})(window);

這裡的 hook 只是 jQuery 大量使用鉤子的冰山一角,在對 DOM 元素的操作一塊,attr 、val 、prop 、css 方法大量運用了鉤子,用於相容 IE 系列下的一些怪異行為。在遇到鉤子函式的時候,要結合具體情境具體分析,這些鉤子相對於表驅動而言更加複雜,它們的結構大體如下,只要記住鉤子的核心原則,保持程式碼整體邏輯的流暢性,在特殊的情境下去處理一些特殊的情況:

1

2

3

4

5

6

7

8

9

var someHook = {

    get: function(elem) {

        // obtain and return a value

        return "something";

    },

    set: function(elem, value) {

        // do something with value

    }

}

從某種程度上講,鉤子是一系列被設計為以你自己的程式碼來處理自定義值的回撥函式。有了鉤子,你可以將差不多任何東西保持在可控範圍內。

從設計模式的角度而言,這種鉤子運用了策略模式。

策略模式:將不變的部分和變化的部分隔開是每個設計模式的主題,而策略模式則是將演算法的使用與演算法的實現分離開來的典型代表。使用策略模式重構程式碼,可以消除程式中大片的條件分支語句。在實際開發中,我們通常會把演算法的含義擴散開來,使策略模式也可以用來封裝一系列的“業務規則”。只要這些業務規則指向的目標一致,並且可以被替換使用,我們就可以使用策略模式來封裝他們。

策略模式的優點:

  • 策略模式利用組合,委託和多型等技術思想,可以有效的避免多重條件選擇語句;
  • 策略模式提供了對開放-封閉原則的完美支援,將演算法封裝在獨立的函式中,使得它們易於切換,易於理解,易於擴充套件。
  • 策略模式中的演算法也可以複用在系統的其它地方,從而避免許多重複的複製貼上工作。

 

   連貫介面

無論 jQuery 如今的流行趨勢是否在下降,它用起來確實讓人大呼過癮,這很大程度歸功於它的鏈式呼叫,介面的連貫性及易記性。很多人將連貫介面看成鏈式呼叫,這並不全面,我覺得連貫介面包含了鏈式呼叫且代表更多。而 jQuery 無疑是連貫介面的佼佼者。

1、鏈式呼叫:鏈式呼叫的主要思想就是使程式碼儘可能流暢易讀,從而可以更快地被理解。有了鏈式呼叫,我們可以將程式碼組織為類似語句的片段,增強可讀性的同時減少干擾。(鏈式呼叫的具體實現上一章有詳細講到

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// 傳統寫法

var elem = document.getElementById("foobar");

elem.style.background = "red";

elem.style.color = "green";

elem.addEventListener('click'function(event) {

  alert("hello world!");

}, true);

 

// jQuery 寫法

$('xxx')

    .css("background""red")

    .css("color""green")

    .on("click"function(event) {

      alert("hello world");

    });

2、命令查詢同體:這個上一章也講過了,就是函式過載。正常而言,應該是命令查詢分離(Command and Query Separation,CQS),是源於指令式程式設計的一個概念。那些改變物件的狀態(內部的值)的函式稱為命令,而那些檢索值的函式稱為查詢。原則上,查詢函式返回資料,命令函式返回狀態,各司其職。而 jQuery 將 getter 和 setter 方法壓縮到單一方法中建立了一個連貫的介面,使得程式碼暴露更少的方法,但卻以更少的程式碼實現同樣的目標。

3、引數對映及處理:jQuery 的介面連貫性還體現在了對引數的相容處理上,方法如何接收資料比讓它們具有可鏈性更為重要。雖然方法的鏈式呼叫是非常普遍的,你可以很容易地在你的程式碼中實現,但是處理引數卻不同,使用者可能傳入各種奇怪的引數型別,而 jQuery 作者想的真的很周到,考慮了使用者的多種使用場景,提供了多種對引數的處理。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

// 傳入鍵值對

jQuery("#some-selector")

  .css("background""red")

  .css("color""white")

  .css("font-weight""bold")

  .css("padding", 10);

 

// 傳入 JSON 物件

jQuery("#some-selector").css({

  "background" "red",

  "color" "white",

  "font-weight" "bold",

  "padding" : 10

});

jQuery 的 on() 方法可以註冊事件處理器。和 CSS() 一樣它也可以接收一組對映格式的事件,但更進一步地,它允許單一處理器可以被多個事件註冊:

1

2

3

4

5

6

7

8

9

// binding events by passing a map

jQuery("#some-selector").on({

  "click" : myClickHandler,

  "keyup" : myKeyupHandler,

  "change" : myChangeHandler

});

 

// binding a handler to multiple events:

jQuery("#some-selector").on("click keyup change", myEventHandler);

 

   無 new 構造

怎麼訪問 jQuery 類原型上的屬性與方法,怎麼做到做到既能隔離作用域還能使用 jQuery 原型物件的作用域呢?重點在於這一句:

1

2

// Give the init function the jQuery prototype for later instantiation

jQuery.fn.init.prototype = jQuery.fn;

這裡的關鍵就是通過原型傳遞解決問題,這一塊上一章也講過了,看過可以跳過了,將文字搬過來。

嘿,回想一下使用 jQuery 的時候,例項化一個 jQuery 物件的方法:

1

2

3

4

5

6

// 無 new 構造

$('#test').text('Test');

 

// 當然也可以使用 new

var test = new $('#test');

test.text('Test');

大部分人使用 jQuery 的時候都是使用第一種無 new 的構造方式,直接 $('') 進行構造,這也是 jQuery 十分便捷的一個地方。當我們使用第一種無 new 構造方式的時候,其本質就是相當於 new jQuery(),那麼在 jQuery 內部是如何實現的呢?看看:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

(function(window, undefined) {

    var

    // ...

    jQuery = function(selector, context) {

        // The jQuery object is actually just the init constructor 'enhanced'

        // 看這裡,例項化方法 jQuery() 實際上是呼叫了其拓展的原型方法 jQuery.fn.init

        return new jQuery.fn.init(selector, context, rootjQuery);

    },

 

    // jQuery.prototype 即是 jQuery 的原型,掛載在上面的方法,即可讓所有生成的 jQuery 物件使用

    jQuery.fn = jQuery.prototype = {

        // 例項化化方法,這個方法可以稱作 jQuery 物件構造器

        init: function(selector, context, rootjQuery) {

            // ...

        }

    }

    // 這一句很關鍵,也很繞

    // jQuery 沒有使用 new 運算子將 jQuery 例項化,而是直接呼叫其函式

    // 要實現這樣,那麼 jQuery 就要看成一個類,且返回一個正確的例項

    // 且例項還要能正確訪問 jQuery 類原型上的屬性與方法

    // jQuery 的方式是通過原型傳遞解決問題,把 jQuery 的原型傳遞給jQuery.prototype.init.prototype

    // 所以通過這個方法生成的例項 this 所指向的仍然是 jQuery.fn,所以能正確訪問 jQuery 類原型上的屬性與方法

    jQuery.fn.init.prototype = jQuery.fn;

 

})(window);

大部分人初看 jQuery.fn.init.prototype = jQuery.fn 這一句都會被卡主,很是不解。但是這句真的算是 jQuery 的絕妙之處。理解這幾句很重要,分點解析一下:

1)首先要明確,使用 $('xxx') 這種例項化方式,其內部呼叫的是 return new jQuery.fn.init(selector, context, rootjQuery) 這一句話,也就是構造例項是交給了 jQuery.fn.init() 方法取完成。

2)將 jQuery.fn.init 的 prototype 屬性設定為 jQuery.fn,那麼使用 new jQuery.fn.init() 生成的物件的原型物件就是 jQuery.fn ,所以掛載到 jQuery.fn 上面的函式就相當於掛載到 jQuery.fn.init() 生成的 jQuery 物件上,所有使用 new jQuery.fn.init() 生成的物件也能夠訪問到 jQuery.fn 上的所有原型方法。

3)也就是例項化方法存在這麼一個關係鏈  

  • jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype ;
  • new jQuery.fn.init() 相當於 new jQuery() ;
  • jQuery() 返回的是 new jQuery.fn.init(),而 var obj = new jQuery(),所以這 2 者是相當的,所以我們可以無 new 例項化 jQuery 物件。

 

   setTimeout in Jquery

寫到這裡,發現上文的主題有些飄忽,接近於寫成了 如何寫出更好的 Javascript 程式碼,下面介紹一些 jQuery 中我覺得很棒的小技巧。

熟悉 jQuery 的人都知道 DOM Ready 事件,傳Javascript原生的 window.onload 事件是在頁面所有的資源都載入完畢後觸發的。如果頁面上有大圖片等資源響應緩慢, 會導致 window.onload 事件遲遲無法觸發,所以出現了DOM Ready 事件。此事件在 DOM 文件結構準備完畢後觸發,即在資源載入前觸發。另外我們需要在 DOM 準備完畢後,再修改DOM結構,比如新增DOM元素等。而為了完美實現 DOM Ready 事件,相容各瀏覽器及低版本IE(針對高階的瀏覽器,可以使用 DOMContentLoaded 事件,省時省力),在 jQuery.ready() 方法裡,運用了 setTimeout() 方法的一個特性, 在 setTimeout 中觸發的函式, 一定是在 DOM 準備完畢後觸發。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

jQuery.extend({

    ready: function(wait) {

        // 如果需要等待,holdReady()的時候,把hold住的次數減1,如果還沒到達0,說明還需要繼續hold住,return掉

        // 如果不需要等待,判斷是否已經Ready過了,如果已經ready過了,就不需要處理了。非同步佇列裡邊的done的回撥都會執行了

        if (wait === true ? --jQuery.readyWait : jQuery.isReady) {

            return;

        }

 

        // 確定 body 存在

        if (!document.body) {

            // 如果 body 還不存在 ,DOMContentLoaded 未完成,此時

            // 將 jQuery.ready 放入定時器 setTimeout 中

            // 不帶時間引數的 setTimeout(a) 相當於 setTimeout(a,0)

            // 但是這裡並不是立即觸發 jQuery.ready

            // 由於 javascript 的單執行緒的非同步模式

            // setTimeout(jQuery.ready) 會等到重繪完成才執行程式碼,也就是 DOMContentLoaded 之後才執行 jQuery.ready

            // 所以這裡有個小技巧:在 setTimeout 中觸發的函式, 一定會在 DOM 準備完畢後觸發

            return setTimeout(jQuery.ready);

        }

 

        // Remember that the DOM is ready

        // 記錄 DOM ready 已經完成

        jQuery.isReady = true;

 

        // If a normal DOM Ready event fired, decrement, and wait if need be

        // wait 為 false 表示ready事情未觸發過,否則 return

        if (wait !== true && --jQuery.readyWait > 0) {

            return;

        }

 

        // If there are functions bound, to execute

        // 呼叫非同步佇列,然後派發成功事件出去(最後使用done接收,把上下文切換成document,預設第一個引數是jQuery。

        readyList.resolveWith(document, [jQuery]);

 

        // Trigger any bound ready events

        // 最後jQuery還可以觸發自己的ready事件

        // 例如:

        //    $(document).on('ready', fn2);

        //    $(document).ready(fn1);

        // 這裡的fn1會先執行,自己的ready事件繫結的fn2回撥後執行

        if (jQuery.fn.trigger) {

            jQuery(document).trigger("ready").off("ready");

        }

    }

})

 

暫且寫這麼多吧,技巧還有很多,諸如 $.Deferred() 非同步佇列的實現,jQuery 事件流機制等,篇幅較長,將會在以後慢慢詳述。

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。

如果本文對你有幫助,請點下推薦,寫文章不容易。

系列第一篇:【深入淺出jQuery】原始碼淺析--整體架構

最後,我在 github 上關於 jQuery 原始碼的全文註解,感興趣的可以圍觀一下,給顆星星。jQuery v1.10.2 原始碼註解