Jquery源碼分析與簡單模擬實現

前言

  最近學習了一下jQuery源碼,順便總結一下,版本:v2.0.3css

  主要是經過簡單模擬實現jQuery的封裝/調用、選擇器、類級別擴展等。加深對js/Jquery的理解。html

正文

先來講問題:node

 1.jQuery爲何能使用$的方式調用,$是什麼、$()又是什麼、鏈式調用如何實現的jquery

 2.jQuery的類級別的擴展內部是怎樣實現的,方法級別的擴展有是怎樣實現的,$.fn又是什麼git

 3.jQuery選擇器是如何執行的,又是如何將結果包裝並返回的ajax

帶着這些問題,咱們進行jquery的模擬實現,文章下方有demo代碼。spring

a.關於$ chrome

1 //@spring:window:便於壓縮,查找速度要快 undefined:ie7ie8是能夠被修改如var undefined = 10;,爲了防止外界改變
2 (function (window, undefined) {
3     var jQuery = {
4     };
5 
6     if (typeof window === "object" && typeof window.document === "object") {
7         window.jQuery = window.$ = jQuery;
8     }
9 }(window));
View Code

jquery用了個自執行方法封裝了一下,傳入window對象是爲了便於壓縮,至關於給了個臨時變量,像jquery聲明的如下變量也是這個做用數組

 1 var
 2     // A central reference to the root jQuery(document)
 3     rootjQuery,//@spring:html文件的document節點
 4 
 5     // The deferred used on DOM ready
 6     readyList,//@spring:dom加載相關
 7 
 8     // Support: IE9
 9     // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
10     core_strundefined = typeof undefined,//@spring:xmlnode判斷的時候會產生bug,因此用typeof來判斷
11 
12     // Use the correct document accordingly with window argument (sandbox)
13     location = window.location,//@spring:這些存儲都是爲了便於壓縮操做,如location=window.location;location會壓縮成i,l等
14     document = window.document,//@spring:同上
15     docElem = document.documentElement,//@spring:同上
16 
17     // Map over jQuery in case of overwrite
18     _jQuery = window.jQuery,
19 
20     // Map over the $ in case of overwrite
21     _$ = window.$,//衝突解決
22 
23     // [[Class]] -> type pairs
24     class2type = {},//相似兩個字符串組成的[{'[Object String]','[spring]'}]
25 
26     // List of deleted data cache ids, so we can reuse them
27     core_deletedIds = [],
28 
29     core_version = "2.0.3",
30 
31     // Save a reference to some core methods
32     core_concat = core_deletedIds.concat,
33     core_push = core_deletedIds.push,
34     core_slice = core_deletedIds.slice,
35     core_indexOf = core_deletedIds.indexOf,
36     core_toString = class2type.toString,
37     core_hasOwn = class2type.hasOwnProperty,
38     core_trim = core_version.trim,
View Code

b.再看$()或者$("***"),也就是jquery的構造函數。先看jq源碼數據結構

1 // Define a local copy of jQuery
2     jQuery = function (selector, context) {
3         // The jQuery object is actually just the init constructor 'enhanced'
4         return new jQuery.fn.init( selector, context, rootjQuery );
5     },
View Code

selector:是個對象,最多見的就是字符串選擇器,其餘還有好多類型,下面會不斷給出說明。

context:數據上下文,也就是個範圍限定,平時用的少些。好比$(".highlight","#div1")就是找id爲div1下面的全部class爲highlight。不傳就是document

new jQuery.fn.init( selector, context, rootjQuery ):使用jQuery.fn.init初始化構造jquery對象,jQuery.fn是啥,看源碼截圖:

jQuery.fn就是jQuery.prototype,因此想一想對象級別的擴展就是prototype下擴展方法而已。那麼init也就是jquery下面的一個擴展方法了

講到這裏咱們先模擬一下過程

 1 (function (window, undefined) {
 2              var jQuery = function (selector) {
 3                  return new jQuery.fn.init(selector);
 4              };
 5              jQuery.fn = jQuery.prototype = {
 6                  jquery: "spring-1.0.js",//jquery版本
 7                  init: function (selector) {//context,rootjQuery不傳了,稍微看看就懂了
 8                      console.log("" + selector + "進行處理");
 9                  }
10              }
11 
12              if (typeof window === "object" && typeof window.document === "object") {
13                  window.jQuery = window.$ = jQuery;
14              }
15          }(window));
16          $("input[name='age']");
View Code

看下jq內部的init實現過程(已將詳細實現代碼剔除,只看結構)

 

看看Jquery選擇器返回的數據結構。

啥都查不到時,jQuery.fn.jQuery.init[0],看起來像個數組。有個length就是查詢到的數據長度。有個context 指向document,context 也就是上面所述的上下文(查找範圍)

查找到數據時,更像個數組了。0/1是查到的元素,length是長度。在chrome輸出臺輸出的也是個數組。挺奇怪的!

這些都很奇怪,並且更奇怪的是new jQuery.fn.init(selector),實例化的是init對象,init裏面沒有這些ajax/add/append/css等方法或屬性,這些都是jquery的屬性/方法。

_proto_是指向init的prototype的(關於_proto_是啥,每一個對象初始化實例都會生成一個_proto_指向該對象的prototype。簡單說下,其餘的自行百度研究一下),卻爲啥會指向jQuery.prototype。

查一下jQuery源碼,沒啥玄虛,手動改指向。這樣new了init對象,執行也查詢方法,同時又指向了Jquery,這纔有了$().各種方法。以下:

前面一直說查詢的元素像個數組,像個數組但不是數組,它是一個對象。怎麼作的呢,咱們把init方法模擬下一塊兒說

 1 //輔助:jquery合併數組的方法
 2         function merge(first, second) {
 3             var l = second.length,
 4                 i = first.length,
 5                 j = 0;
 6 
 7             if (typeof l === "number") {
 8                 for (; j < l; j++) {
 9                     first[i++] = second[j];
10                 }
11             } else {
12                 while (second[j] !== undefined) {
13                     first[i++] = second[j++];
14                 }
15             }
16 
17             first.length = i;
18 
19             return first;
20         }
21 
22 
23         (function (window, undefined) {
24             var core_version = "spring v.1",
25                 core_deletedIds = [],
26                 core_push = core_deletedIds.push,
27                 core_slice = core_deletedIds.slice;
28             var jQuery = function (selector) {
29                 return new jQuery.fn.init(selector);
30             };
31             jQuery.fn = jQuery.prototype = {
32                 jquery: core_version,//jquery版本
33                 constructor: jQuery,//覆蓋構造函數防止被外部改變
34                 init: function (selector) {//context,rootjQuery不傳了,稍微看看就懂了
35                     //針對不一樣參數類型進行不一樣處理方式,若是$("")$(null就直接返回)
36                     if (!selector) {
37                         //參數不對直接將this返回,想一想如今this的值是什麼,提示:new init();=>jQuery.fn.init[0]
38                         return this;
39                     } else {
40                         //若是是字符串juqery會調用查詢方法進行查詢dom元素(jquery調用sizzle專門進行dom解析)
41                         var nodes = document.getElementsByName("age");
42                         var arr = [];
43                         for (var i = 0; i < nodes.length; i++) {
44                             arr.push(nodes[i]);
45                         }
46                         //若是傳遞了Context上下文,則在context中尋找元素。這裏指定位document
47                         this.context = document;
48                         //把selector存到jQuery中
49                         this.selector = selector;
50                         //jquery的合併方法,直接拿出來就能用,合併查詢結果
51                         var result = merge(this, arr);
52                         //對處理過的this進行封裝返回,注意爲了鏈式調用,都須要返回this
53                         return result;
54                     }
55                 },
56                 selector: ""
57             }
58             jQuery.fn.init.prototype = jQuery.fn;
59             if (typeof window === "object" && typeof window.document === "object") {
60                 window.jQuery = window.$ = jQuery;
61             }
62         }(window));
63         $(".test");
View Code

其實代碼裏沒啥東西都是模仿jquery的。不過就是簡化一下,模仿一下。因此要先看結構這樣才知道簡化哪句,模仿那句。

看下結果:

結果查出來了,可是不像數組啊,四不像的。init後面也沒有個[]啊。

看下jQuery源碼:

關鍵代碼就這這裏,讓對象像個數據加這幾句就好了,咱們來試試(完整的代碼):

 1 <input type="text" class="test" name="age" />
 2     <input type="text" class="test" name="Name" />
 3     <div class="test"></div>
 4     <script>
 5         //輔助:jquery合併數組的方法
 6         function merge(first, second) {
 7             var l = second.length,
 8                 i = first.length,
 9                 j = 0;
10 
11             if (typeof l === "number") {
12                 for (; j < l; j++) {
13                     first[i++] = second[j];
14                 }
15             } else {
16                 while (second[j] !== undefined) {
17                     first[i++] = second[j++];
18                 }
19             }
20 
21             first.length = i;
22 
23             return first;
24         }
25 
26 
27         (function (window, undefined) {
28             var core_version = "spring v.1",
29                 core_deletedIds = [],
30                 core_push = core_deletedIds.push,
31                 core_slice = core_deletedIds.slice;
32             var jQuery = function (selector) {
33                 return new jQuery.fn.init(selector);
34             };
35             jQuery.fn = jQuery.prototype = {
36                 jquery: core_version,//jquery版本
37                 constructor: jQuery,//覆蓋構造函數防止被外部改變
38                 init: function (selector) {//context,rootjQuery不傳了,稍微看看就懂了
39                     //針對不一樣參數類型進行不一樣處理方式,若是$("")$(null就直接返回)
40                     if (!selector) {
41                         //參數不對直接將this返回,想一想如今this的值是什麼,提示:new init();=>jQuery.fn.init[0]
42                         return this;
43                     } else {
44                         //若是是字符串juqery會調用查詢方法進行查詢dom元素(jquery調用sizzle專門進行dom解析)
45                         var nodes = document.getElementsByName(selector);
46                         var arr = [];
47                         for (var i = 0; i < nodes.length; i++) {
48                             arr.push(nodes[i]);
49                         }
50                         //若是傳遞了Context上下文,則在context中尋找元素。這裏指定位document
51                         this.context = document;
52                         this[0] = document;
53                         //把selector存到jQuery中
54                         this.selector = selector;
55                         //jquery的合併方法,直接拿出來就能用,合併查詢結果
56                         var result = merge(this, arr);
57                         //對處理過的this進行封裝返回,注意爲了鏈式調用,都須要返回this
58                         return result;
59                     }
60                 },
61                 selector: "",
62                 length: 0,
63                 toArray: function () {
64                     return core_slice.call(this);
65                 },
66                 get: function (num) {
67                     return num == null ?
68                         this.toArray() :
69                         (num < 0 ? this[this.length + num] : this[num]);
70                 },
71                 //這裏要注意,想要長得像jquery.fn.jquery.init[0],而且init方法中的this值爲數組就必須加下面這三個字段
72                 push: core_push,
73                 sort: [].sort,
74                 splice: [].splice
75             }
76             jQuery.fn.init.prototype = jQuery.fn;
77             if (typeof window === "object" && typeof window.document === "object") {
78                 window.jQuery = window.$ = jQuery;
79             }
80         }(window));
81         $("age");
82 
83     </script>
View Code

看看輸出結果:

恩恩,不錯不錯…挺像的。

這就是對選擇器的簡單模擬。其實jQuery也是調用Sizzle.js進行html元素解析的(牽涉許多,很少講了,本身去查吧)

 

至於jQuery對象級別的擴展,簡單模擬一個,其實就是jQuery.prototype.method擴展一個方法而已

//jquery對象級別的擴展插件,看看就明白是啥了
    jQuery.fn.css = function (className) {
        //注意this是一個對象,length值是手動賦予的
        for (var i = 0; i < this.length; i++) {
            var item = this[i];//經過下標找元素,this不是數組
            item.setAttribute("class", className);
        }
        return this;//鏈式調用返回this
    };

調用如:

咱們本身擴展一個:

 1 //對象級別的擴展插件
 2 $.fn.attr = function (name, value) {
 3     for (var i = 0; i < this.length; i++) {
 4         var item = this[i];
 5         if (name && value) {
 6             item.setAttribute(name, value);
 7         } else if (name && !value) {
 8             return item.getAttribute(name);
 9         }
10     }
11     return this;
12 };

調用一下,結果沒錯。返回this,也是爲了鏈式調用。

如上所示比較簡單,很少說。

 

而後就是所謂的類級別的擴展了,也就是jquery的靜態方法。常常被寫爲$.method(如$.ajax)。實現的時候呢用的是$.extend({方法對象,寫各類擴展方法})

$.extend是啥,看看源碼:

其實就是jQuery的一個擴展方法,接收argument參數,這個參數就是你傳過來的方法對象了,使用argument[0]一個個獲取就好了

獲取完了就是怎麼把這些方法合併到jQuery自己了。看了下jquery源碼,也來模擬下extend吧。

先看個小demo:

一看就懂,Person自己就是個對象,給它加個方法而已(Person又是啥對象呢,越講越多,講不完滴)。你把Person當作jQuery,那就是$.ajax。

再看下面這個模擬jQuery的方法:

 1 //jquery靜態方法擴展,即類級別擴展
 2     jQuery.extend = jQuery.fn.extend = function () {
 3         var src, copy, options, target = this;
 4         ////arguments[0] 如{a:function(){},b:funciton(){}},一個參數對象
 5         if ((options = arguments[0]) != null) {
 6             for (var name in options) {
 7                 copy = options[name];
 8                 target[name] = copy;//其實jquery就是把這些參數取出來,而後一個個複製到jquery這個object中
 9                 //如 var Person=function(){};Person.ajax=function(){}同樣
10             }
11         }
12     };

關鍵代碼第一句:target=this;this是啥或者說jQuery.fn.extend中的this是啥,其實就是jQuery對象。

關鍵代碼第二句:for (var name in options),option就是你傳遞的那個對象,循環那個對象如:

var options={
  ajax: function () {
    console.log("模擬執行ajax");
  },
  load: function () {
    console.log("模擬執行load");
  }
}

關鍵代碼第三句:target[name] = copy,其實也就是:

jQuery["ajax"]=function(){

  console.log("模擬執行ajax");

}

結合前面Person的demo一會兒就明白了。

而後咱們就能夠寫出下面的jQuery方法了

 1 /*****調用演示******/
 2 //函數級別的擴展插件
 3 $.extend({
 4     ajax: function () {
 5         console.log("模擬執行ajax");
 6     },
 7     load: function () {
 8         console.log("模擬執行load");
 9     }
10 });
11 $.ajax();

以上就是所有正文,本文所有代碼:http://git.oschina.net/GspringG/jQueryDemo

總結

其實這篇也是越講越多,js/jQuery的點是很是多的,也是越說越有意思。固然了本文也有可能出現一些有誤的地方,請你們及時告知。

相關文章
相關標籤/搜索