jQuery內核詳解與實踐讀書筆記1:原型技術分解1

  一直以來都有研究一下jQuery源代碼的想法,可是每次看到jQuery幾千行的代碼,頭就大了,沒有一點頭緒,也不知道從哪裏開始。昨天去圖書館無心間發現了這本《jQuery內核詳解和實踐》,翻看了一下里面的內容,這正是我尋覓多時剖析jQuery源碼的好書。html

  廢話很少說,直入正題吧。第一章介紹了一下jQuery的起步和一些歷史故事,沒什麼重要內容。這裏直接進入第二章,jQuery技術解密,從這一章開始就所有是乾貨了。這一章主要分四部分:jQuery原型技術分解,破解jQuery選擇器接口,解析jQuery選擇器引擎Sizzle,類數組。node

  jQuery原型技術分解主要就是從0開始一步步搭建一個簡易的jQuery框架,講述了jQuery框架的搭建過程,書中主要分紅了9個步驟,最後造成一個jQuery框架的雛形。jquery

 

1. 起源--原型繼承跨域

模仿jQuery框架源碼,添加兩個成員,一個原型屬性jquery,一個原型方法size(),源代碼以下:數組

1 var $ = jQuery = function() {};
2 jQuery.fn = jQuery.prototype = {
3     jquery : "1.3.2",         //原型屬性
4     size : function() {       //原型方法
5        return this.length;
6     }
7 };
View Code

此時這個框架最基本的樣子就孕育出來了。這幾行代碼都很簡單,但倒是整個框架的基礎。微信

 

2. 生命--返回實例app

若是用上面的代碼時,獲得一個jQuery的對象是須要new出來的,可是咱們使用的jQuery並非經過new來獲得jQuery對象的,而是經過$()獲得的。jQuery是如何實現$()的方式進行函數的調用?框架

咱們應該把jQuery看作是一個類,同時也應該把它視爲一個普通的函數,並讓這個函數的返回值爲jQuery類的實例。可是若是直接在jQuery函數中返回一個new出來的jQuery實例,會形成死循環,致使內存外溢。ide

考慮:在建立jQuery類實例時,this關鍵字就是指向對象實例的,並且不管是在jQuery.prototype中原型屬性仍是方法,this關鍵字老是指向類的實例。函數

結論:在jQuery中使用一個工廠方法來建立一個實例,把這個方法放在jQuery.prototype 原型對象中,而後在jQuery()函數中返回這個原型方法的調用。

這樣就能夠將1中的代碼修改爲下面的代碼:

 1 var $ = jQuery = function() {
 2   return jQuery.fn.init();    //調用原型方法init()
 3 };
 4 jQuery.fn = jQuery.prototype = {
 5     init : function() {      //在初始化原型方法中返回實例的引用
 6        return this;
 7     },
 8     jquery : "1.3.2",         //原型屬性
 9     size : function() {       //原型方法
10        return this.length;
11     }
12 }; 
View Code

 

3. 學步--分隔做用域

若是按照2的代碼,咱們又會出現問題。以下代碼:

 1 var $ = jQuery = function() {
 2   return jQuery.fn.init();    //調用原型方法init()
 3 };
 4 jQuery.fn = jQuery.prototype = {
 5     init : function() {      //在初始化原型方法中返回實例的引用
 6        this.length = 0;
 7        this.test = function() {
 8          return this.length;
 9        };
10        return this;
11     },
12     jquery : "1.3.2",         //原型屬性
13     length : 1,
14     size : function() {       //原型方法
15        return this.length;
16     }
17 }; 
View Code

上述代碼中jQuery原型對象中包含一個length屬性,同時init()從一個普通函數變成了構造器,它也包含一個length屬性和一個test()方法。this關鍵字引用了init()函數做用域所在的對象,此時它訪問length屬性時,返回0.而this關鍵字也可以訪問上一級對象jQuery.fn對象的做用域,因此$().jquery返回"1.3.2"。可是調用$().size()方法時,返回的是0,而不是1?

解決方法:jQuery框架是經過下面的方式調用init()初始化構造函數,達到隔離做用域的目的:

1 var $ = jQuery = function() {
2   return new jQuery.fn.init();    //實例化init初始化類型,分隔做用域
3 };
View Code

這樣就能夠把init()構造器中的this和jQuery.fn對象中的this關鍵字隔離開來,避免相互混淆。
此時源代碼就變成以下:

 1 var $ = jQuery = function() {
 2   return new jQuery.fn.init();    //實例化init初始化類型,分隔做用域
 3 };
 4 jQuery.fn = jQuery.prototype = {
 5     init : function() {      //在初始化原型方法中返回實例的引用
 6        this.length = 0;
 7        this.test = function() {
 8          return this.length;
 9        };
10        return this;
11     },
12     jquery : "1.3.2",         //原型屬性
13     length : 1,
14     size : function() {       //原型方法
15        return this.length;
16     }
17 }; 
View Code

可是,這種方式也會帶來另外一個問題:沒法訪問jQuery.fn對象的屬性或方法。

 

4. 生長--跨域訪問

上一節拋出了一個問題:沒法訪問jQuery.fn對象的屬性或方法,如何解決?

方法:經過原型傳遞,jQuery框架把jQuery.fn傳遞給jQuery.fn.init.prototype,也就是說用jQuery的原型對象覆蓋init構造器的原型對象,從而實現跨域訪問,其源代碼以下:

 1 var $ = jQuery = function() {
 2   return new jQuery.fn.init();    //實例化init初始化類型,分隔做用域
 3 };
 4 jQuery.fn = jQuery.prototype = {
 5     init : function() {      //在初始化原型方法中返回實例的引用
 6        this.length = 0;
 7        this.test = function() {
 8          return this.length;
 9        };
10        return this;
11     },
12     jquery : "1.3.2",         //原型屬性
13     length : 1,
14     size : function() {       //原型方法
15        return this.length;
16     }
17 };
18 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型對象覆蓋init的原型對象
View Code

new jQuery.fn.init()建立的新對象擁有init構造器的prototype原型對象的方法,經過改變prototype指針的指向,使其指向jQuery類的prototype,這樣建立出來的對象就繼承了jQuery.fn原型對象定義的方法。

 

5. 成熟--選擇器

jQuery函數包含兩個參數selector和context,其中selector表示選擇器,而context表示的內容範圍,它表示一個DOM元素。在此,爲了簡化操做,假設選擇器的類型僅限定爲標籤選擇器,其實現代碼以下:

 1 var $ = jQuery = function(selector, context) {       //定義類
 2   return new jQuery.fn.init(selector, context);    //返回選擇器的實例
 3 };
 4 jQuery.fn = jQuery.prototype = {                  //jQuery類的原型對象
 5     init : function(selector, context) {      //定義選擇器構造器
 6        selector = selector || document;      //設置默認值爲document
 7        context = context || document;        //設置默認值爲document
 8        if(selector.nodeType) {               //若是選擇符爲節點對象
 9          this[0] = selector;               //把參數節點傳遞給實例對象的數組
10          this.length = 1;                  //並設置實例對象的length屬性,定義包含的元素個數
11          this.context = selector;          //設置實例的屬性,返回選擇範圍
12          return this;                      //返回當前實例
13        }
14        if(typeof selector === "string") {                    //若是選擇符是字符串
15          var e = context.getElementsByTagName(selector);   //獲取指定名稱的元素
16          for(var i=0; i<e.length; i++) {                   //遍歷元素集合,並把全部元素填入到當前實例數組中
17            this[i] = e[i];
18          }
19          this.length = e.length;                          //設置實例的length屬性,即定義包含的元素個數
20          this.context = context;                          //設置實例的屬性,返回選擇範圍
21          return this;                                     //返回當前實例
22        } else {
23          this.length = 0;                  //不然,設置實例的length屬性值爲0
24          this.context = context;           //設置實例的屬性,返回選擇範圍
25          return this;                      //返回當前實例
26        }
27     },
28     jquery : "1.3.2",         //原型屬性
29     size : function() {       //原型方法
30        return this.length;
31     }
32 };
33 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型對象覆蓋init的原型對象 
View Code

這裏就實現了一個最簡單的選擇器了,固然jQuery框架中的選擇器比這裏的要複雜的多,這裏只是爲了搭建一個jQuery框架的最簡單形式,之後再收入去研究它的選擇器。

 

6. 延續--迭代器

在jQuery框架中,jQuery對象是一個比較特殊的對象,具備多重身份,能夠分解以下:

第一, jQuery對象是一個數組集合,它不是一個個具體對象。所以,沒法直接使用JavaScript的方法來操做它。

第二, jQuery對象實際上就是一個普通的對象,由於它是經過new運算符建立的一個新的實例對象。它能夠繼承原型方法或屬性,一樣也擁有Object類型的方法和屬性。

第三, jQuery對象包含數組特性,由於它賦值了數組元素,以數組結構存儲返回的數據。能夠以JavaScript的概念理解jQuery對象,jQuery對象就是對象和數組的混合體,可是它不擁有數組的方法,由於它的數組結構是人爲附加的,也就是說它不是Array類型數據,而是Object類型數據。

第四, jQuery對象包含的數據都是DOM元素,是經過數組形式存儲的,即經過jQuery[n]形式獲取。同時jQuery對象又定義了幾個模仿Array基本特性的屬性,如length等

因此,jQuery對象是不容許直接操做的,只有分別讀取它包含的每個DOM元素,纔可以實現各類操做,如插入,刪除,嵌套,賦值和讀寫DOM元素屬性等。

 

如何實現直接操做jQuery對象中的DOM元素呢?例如$("div").html()

jQuery定義了一個工具函數each(),利用這個工具函數能夠遍歷jQuery對象中全部的DOM元素,並把須要操做的內存封裝到一個回調函數中,而後經過在每一個DOM元素上調用這個回調函數便可。實現代碼以下:

 1 var $ = jQuery = function(selector, context) {       //定義類
 2   return new jQuery.fn.init(selector, context);    //返回選擇器的實例
 3 };
 4 jQuery.fn = jQuery.prototype = {                  //jQuery類的原型對象
 5     init : function(selector, context) {      //定義選擇器構造器
 6        selector = selector || document;      //設置默認值爲document
 7        context = context || document;        //設置默認值爲document
 8        if(selector.nodeType) {               //若是選擇符爲節點對象
 9          this[0] = selector;               //把參數節點傳遞給實例對象的數組
10          this.length = 1;                  //並設置實例對象的length屬性,定義包含的元素個數
11          this.context = selector;          //設置實例的屬性,返回選擇範圍
12          return this;                      //返回當前實例
13        }
14        if(typeof selector === "string") {                    //若是選擇符是字符串
15          var e = context.getElementsByTagName(selector);   //獲取指定名稱的元素
16          for(var i=0; i<e.length; i++) {                   //遍歷元素集合,並把全部元素填入到當前實例數組中
17            this[i] = e[i];
18          }
19          this.length = e.length;                          //設置實例的length屬性,即定義包含的元素個數
20          this.context = context;                          //設置實例的屬性,返回選擇範圍
21          return this;                                     //返回當前實例
22        } else {
23          this.length = 0;                  //不然,設置實例的length屬性值爲0
24          this.context = context;           //設置實例的屬性,返回選擇範圍
25          return this;                      //返回當前實例
26        }
27     },
28     jquery : "1.3.2",         //原型屬性
29     size : function() {       //原型方法
30        return this.length;
31     },
32     
33     //定義jQuery對象方法
34     html : function(val) {                   //模仿jQuery框架中的html()方法,爲匹配的每個DOM元素插入html代碼
35        jQuery.each(this, function(val) {    //調用jQuery.each()工具函數,爲每個DOM元素執行回調函數
36          this.innerHTML = val;
37        }, val);
38     }
39 };
40 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型對象覆蓋init的原型對象
41 
42 //擴展jQuery工具函數
43 jQuery.each = function(object, callback, args) {
44   for(var i=0; i<object.length; i++) {
45     callback.call(object[i], args);
46   }
47   return object;
48 };
View Code

注意:在上面的代碼中,each()函數的當前做用對象是jQuery對象,故this指向當前jQuery對象,即this表示一個集合對象;而在html()方法中,因爲each()函數是在指定DOM元素上執行的,因此該函數內的this指針指向的是當前DOM元素對象,即this表示一個元素。

以上定義的each()工具函數比較簡單,適應能力頗有限。在jQuery框架中,它封裝的each()函數功能強大不少,具體代碼以下:

 1 var $ = jQuery = function(selector, context) {       //定義類
 2     return new jQuery.fn.init(selector, context);    //返回選擇器的實例
 3 };
 4 jQuery.fn = jQuery.prototype = {                  //jQuery類的原型對象
 5         init : function(selector, context) {      //定義選擇器構造器
 6             selector = selector || document;      //設置默認值爲document
 7             context = context || document;        //設置默認值爲document
 8             if(selector.nodeType) {               //若是選擇符爲節點對象
 9                 this[0] = selector;               //把參數節點傳遞給實例對象的數組
10                 this.length = 1;                  //並設置實例對象的length屬性,定義包含的元素個數
11                 this.context = selector;          //設置實例的屬性,返回選擇範圍
12                 return this;                      //返回當前實例
13             }
14             if(typeof selector === "string") {                    //若是選擇符是字符串
15                 var e = context.getElementsByTagName(selector);   //獲取指定名稱的元素
16                 for(var i=0; i<e.length; i++) {                   //遍歷元素集合,並把全部元素填入到當前實例數組中
17                     this[i] = e[i];
18                 }
19                 this.length = e.length;                          //設置實例的length屬性,即定義包含的元素個數
20                 this.context = context;                          //設置實例的屬性,返回選擇範圍
21                 return this;                                     //返回當前實例
22             } else {
23                 this.length = 0;                  //不然,設置實例的length屬性值爲0
24                 this.context = context;           //設置實例的屬性,返回選擇範圍
25                 return this;                      //返回當前實例
26             }
27         },
28         jquery : "1.3.2",         //原型屬性
29         size : function() {       //原型方法
30             return this.length;
31         },
32         
33         //定義jQuery對象方法
34         html : function(value) {                   
35             return value === undefined ? 
36                     (this[0] ? 
37                             this[0].innerHTML.repalce(/ jQuery\d+="(?:\d+|null)"/g, "") :
38                                 null) : 
39                     this.empty().append(value);
40         }
41 };
42 jQuery.fn.init.prototype = jQuery.fn; //使用jQuery的原型對象覆蓋init的原型對象
43 
44 //擴展jQuery工具函數
45 jQuery.extend({
46     //參數說明:object表示jQuery對象,callback表示回調函數,args回調函數的參數數組
47     each : function(object, callback, args) {
48         var name, i = 0, length = object.length;
49         if(args) {//若是存在回調函數的參數數組
50             if(length === undefined) {//若是object不是jQuery對象
51                 for(name in object) {//遍歷object的屬性
52                     if(callback.apply(object[name], args) === false) {//在對象上調用回調函數
53                         break;//若是回調函數返回值爲false,則跳出循環
54                     }
55                 }
56             } else {//若是object是jQuery對象
57                 for( ; i< length; ) { //遍歷jQuery對象數組
58                     if(callback.apply(object[i++], args) === false) { //在對象上調用回調函數
59                         break;//若是回調函數返回值爲false,則跳出循環
60                     }
61                 }
62             }
63         } else {
64             if(length === undefined) {//若是object不是jQuery對象
65                 for(name in object) {//遍歷object對象
66                     if(callback.call(object[name], name, object[name]) === false) {//在對象上調用回調函數
67                         break;//若是回調函數返回值爲false,則跳出循環
68                     }
69                 }
70             } else {//若是object是jQuery對象
71                 //遍歷jQuery對象數組,並在對象上調用回調函數
72                 for(var value=object[0]; i<length && callback.call(value, i, value) !== false; value=object[i++]) {}
73             }
74         }
75         return object;//返回jQuery對象
76     }
77 });
View Code

同時jQuery框架定義的html()方法包含的功能比較多,它不只能夠插入HTML源代碼,還能夠返回匹配元素包含的HTML源代碼,故使用了一個條件結構分別進行處理。首先,判斷參數是否爲空,若是爲空,則表示獲取匹配元素中第一個元素包含的HTML源代碼,此時返回該innerHTML的值。若是不爲空,則先清空匹配元素中每一個元素包含的內容,並使用append()方法插入HTML源代碼。

好了,暫時只看到了這裏,下次把剩下的三步完成。

 

我的微信公衆號:programmlife,若有興趣敬請關注,主要一個碼農的所看所思所想所嘆,或掃描下方二維碼關注:

相關文章
相關標籤/搜索