最近學習了一下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));
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,
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 },
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']");
看下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");
其實代碼裏沒啥東西都是模仿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>
看看輸出結果:
恩恩,不錯不錯…挺像的。
這就是對選擇器的簡單模擬。其實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的點是很是多的,也是越說越有意思。固然了本文也有可能出現一些有誤的地方,請你們及時告知。