經過new操做符構建一個對象,通常通過四步:javascript
A.建立一個新對象css
B.將構造函數的做用域賦給新對象(因此this就指向了這個新對象)html
C.執行構造函數中的代碼java
D.返回這個新對象jquery
最後一點就說明了,咱們只要返回一個新對象便可。其實new操做符主要是把原型鏈跟實例的this關聯起來,這纔是最關鍵的一點,因此咱們若是須要原型鏈就必需要new操做符來進行處理。不然this則變成window對象了。算法
改造jQuery無new的格式,咱們能夠經過instanceof判斷this是否爲當前實例:設計模式
var $$ = ajQuery = function(selector) { if(!(this instanceof ajQuery)){ // 第二次看仍是以爲這一句很NB return new ajQuery(selector); } this.selector = selector; return this }
但在jQuery實際上採起的手段是把原型上的一個init方法做爲構造器,這樣貌似更節省代碼空間?數組
var $$ = ajQuery = function(selector) { //把原型上的init做爲構造器 return new ajQuery.fn.init( selector ); } ajQuery.fn = ajQuery.prototype = { name: 'aaron', init: function() { console.log(this) }, constructor: ajQuery }
但這樣子還缺點東西,init是ajQuery原型上做爲構造器的一個方法,那麼其this就不是ajQuery了,因此this就徹底引用不到ajQuery的原型了,因此這裏經過new把init方法與ajQuery給分離成2個獨立的構造器。app
接着上面分割出2個構造器的疑問,來看看jQuery的一個遍歷接口:框架
$(".aaron").each() //做爲實例方法存在 $.each() //做爲靜態方法存在
看似實例和靜態方法須要兩個函數來實現,但在jQuery源碼中是這樣的:
jQuery.prototype = { // 調用實例方法其實是將實例對象this做爲一個參數,調用對應的靜態方法,這樣就造成了共享 each: function( callback, args ) { return jQuery.each( this, callback, args ); } }
實例方法取於靜態方法,換句話來講這是靜態與實例方法共享設計,靜態方法掛在jQuery構造器上,原型方法掛在哪裏呢?------jQuery經過new原型prototype上的init方法看成構造器,那麼init的原型鏈方法就是實例的方法了,因此jQuery經過2個構造器劃分2種不一樣的調用方式一種是靜態,一種是原型。
那若是要將2個構造器原型關聯起來,關鍵就是靠下面一句:
ajQuery.fn.init.prototype = ajQuery.fn
這樣init構造出來的實例對象也可以繼承jQuery原型上的方法了。
jQuery的核心理念是Write less,Do more(寫的更少,作的更多),那麼鏈式方法的設計與這個核心理念不謀而合。那麼從深層次考慮這種設計其實就是一種Internal DSL。
DSL是指Domain Specific Language,也就是用於描述和解決特定領域問題的語言。
jQuery的Internal DSL形式帶來的好處——編寫代碼時,讓代碼更貼近做者的思惟模式;閱讀代碼時,讓讀者更容易理解代碼的含義;應用DSL能夠有效的提升系統的可維護性(縮小了實現模型和領域模型的距離,提升了實現的可讀性)和靈活性,而且提供開發的效率。
jQuery的這種管道風格的DSL鏈式代碼,總的來講:
☑ 節約JS代碼;
☑ 所返回的都是同一個對象,能夠提升代碼的效率
實現鏈式操做的原理你們都懂的,就只須要在方法內返回當前的這個實例對象this就能夠了,由於返回當前實例的this,從而又能夠訪問本身的原型了,這樣的就節省代碼量,提升代碼的效率,代碼看起來更優雅。可是這種方法有一個問題是:全部對象的方法返回的都是對象自己,也就是說沒有返回值,因此這種方法不必定在任何環境下都適合。
jQuery插件的開發分爲兩種:
☑ 一種是掛在jQuery命名空間下的全局函數,也可稱爲靜態方法;
☑ 另外一種是jQuery對象級別的方法,即掛在jQuery原型下的方法,這樣經過選擇器獲取的jQuery對象實例也能共享該方法。
提供的接口:
$.extend(target, [object1], [objectN]); $.fn.extend();
接口的使用:
// 拓展到jQuery上的靜態方法 jQuery.extend({ data:function(){}, removeData:function(){} }) // 拓展到實例對象上的原型方法 jQuery.fn.extend({ data:function(){}, removeData:function(){} })
而jQuery源碼中對於上面兩種擴展,實際上是同指向同一方法的不一樣引用(這裏有一個設計的重點,經過調用的上下文,咱們來肯定這個方法是做爲靜態仍是實例處理,在javascript的世界中一共有四種上下文調用方式:方法調用模式、函數調用模式、構造器調用模式、apply調用模式),而這一切都是依靠this來完成的。
☑ jQuery.extend調用的時候上下文指向的是jQuery構造器,this指向的是jQuery ☑ jQuery.fn.extend調用的時候上下文指向的是jQuery構造器的實例對象了,this指向實例對象
所以在源碼中是這樣的:
aAron.extend = aAron.fn.extend = function() { var options, src, copy, target = arguments[0] || {}, i = 1, length = arguments.length; // 只有一個參數,就是對jQuery自身的擴展處理 if (i === length) { target = this; // 調用的上下文對象,前一個方法對應jQuery,後一個方法對應實例 i--; } for (; i < length; i++) { // 從i開始取參數,不爲空開始遍歷 if ((options = arguments[i]) != null) { for (name in options) { copy = options[name]; // 覆蓋拷貝 target[name] = copy; } } } return target; }
我來說解一下上面的代碼:由於extend的核心功能就是經過擴展收集功能(相似於mix混入),因此就會存在收集對象(target)與被收集的數據,由於jQuery.extend並無明確實參,並且是經過arguments來判斷的,因此這樣處理起來很靈活。arguments經過判斷傳遞參數的數量能夠實現函數重載。其中最重要的一段target = this,經過調用的方式咱們就能確實當前的this的指向,因此這時候就能肯定target了。最後就很簡單了,經過for循環遍歷把數據附加到這個target上了。固然在這個附加的過程當中咱們還能夠作數據過濾、深拷貝等一系列的操做了。
經過jQuery處理後返回的不只僅只有DOM對象,而是一個包裝容器,返回jQuery對象。而這一個對象中有一個preObject的屬性。
要了解這個屬性是作什麼的,首先了解一下jQuery對象棧,jQuery內部維護着一個jQuery對象棧。每一個遍歷方法(在當前選中範圍內的DOM再進行篩選的操做,例如.find()方法)都會找到一組新元素(一個jQuery對象),而後jQuery會把這組元素推入到棧中。
而每一個jQuery對象都有三個屬性:context、selector和prevObject(用id選擇器的話這個屬性不必定有),其中的prevObject屬性就指向這個對象棧中的前一個對象,而經過這個屬性能夠回溯到最初的DOM元素集中。
能夠看下下面的例子:
$("div").find('.foo').find('.aaa') // 這裏的preObject屬性就會指向$("div").find('.foo')的DOM集合 $("div").find('.foo') // 往前一級的preObect屬性就是指向$("div")的DOM集合
而這種能夠回溯到以前選擇的DOM集合的機制,是爲這兩個方法服務的:
.end() // 回溯到前一個jQuery對象,即prevObject屬性 .addBack() // 把當前位置和前一個位置的元素結合組合起來,而且將這個新的組合的元素集推入棧的上方
而利用這個回溯機制和對應的方法能夠進行以下的操做:
<ul class="first"> <li class="foo">list item 1</li> <li>list item 2</li> <li class="bar">list item 3</li> </ul> <script> // foo類li標籤背景設置爲紅色, bar類li標籤背景設置爲綠色 $("#test2").click(function(){ //經過end連貫處理 $('ul.first') .find('.foo') .css('background-color', 'red') .end() .find('.bar') .css('background-color', 'green'); }) </scripts>
利用這個DOM元素棧能夠減小重複的查詢和遍歷的操做,而減小重複操做也正是優化jQuery代碼性能的關鍵所在。
end方法可以幫助咱們回溯到上一個DOM合集,所以該方法返回的就是一個jQuery對象,在源碼中的表現就是返回了prevObject對象:
end: function() { return this.prevObject || this.constructor(null); }
那麼prevObject在什麼狀況下會產生?
在構建jQuery對象的時候,經過pushStack方法構建,以下代碼:
pushStack: function( elems ) { // Build a new jQuery matched element set // 這裏將傳進來的DOM元素,經過調用jQuery的方法構建成一個新的jQuery對象 var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) // 在此對象上把前一個jQuery對象添加到prevObject屬性中 ret.prevObject = this; ret.context = this.context; // Return the newly-formed element set // 最後返回這個jQuery對象 return ret; }
那麼在find方法中,爲了將前一個jQuery對象推入棧中,就會調用這個pushStack方法來構建:
jQuery.fn.extend({ find: function(selector) { //...........................省略................................ //經過sizzle選擇器,返回結果集 jQuery.find(selector, self[i], ret); // Needed because $( selector, context ) becomes $( context ).find( selector ) ret = this.pushStack(len > 1 ? jQuery.unique(ret) : ret); // 這裏就是將實例對象推入棧中,而後返回新的jQuery對象 ret.selector = this.selector ? this.selector + " " + selector : selector; return ret; } }
jQuery既然是模仿的數組結構,那麼確定會實現一套類數組的處理方法,好比常見的棧與隊列操做push、pop、shift、unshift、求和、遍歷循環each、排序及篩選等一系的擴展方法。
jQuery提供了.get()、:index()、 :lt()、:gt()、:even()及 :odd()這類索引值相關的選擇器,他們的做用能夠過濾他們前面的匹配表達式的集合元素,篩選的依據就是這個元素在原先匹配集合中的順序。
先來看看get方法的實現源碼:
get: function(num) { return num != null ? // 不傳參爲undefined, 走false線 // Return just the one element from the set (num < 0 ? this[num + this.length] : this[num]) : // Return all the elements in a clean array slice.call(this); // 返回整個DOM元素數組 }
熟悉jQuery的童鞋都清楚,get返回的是DOM元素,而eq返回的是jQuery對象,這樣就能夠繼續執行鏈式操做。
eq實現的原理:
eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
上面實現代碼的邏輯就是跟get是同樣的,區別就是經過了pushStack產生了一個新的jQuery對象。
若是須要的是一個合集對象要怎麼處理?所以jQuery便提供了一個slice方法,根據下標範圍取元素集合,並生成一個新的jQuery對象。
slice方法實現源碼:
slice: function() { return this.pushStack( slice.apply( this, arguments ) ); },
迭代器是一個框架的重要設計。咱們常常須要提供一種方法順序用來處理聚合對象中各個元素,而又不暴露該對象的內部,這也是設計模式中的迭代器模式(Iterator)。
針對迭代器,這裏有幾個特色:
☑ 訪問一個聚合對象的內容而無需暴露它的內部。
☑ 爲遍歷不一樣的集合結構提供一個統一的接口,從而支持一樣的算法在不一樣的集合結構上進行操做。
☑ 遍歷的同時更改迭代器所在的集合結構可能會致使問題。
另外還要考慮這四點:
☑ 聚合對象,多是對象,字符串或者數組等類型
☑ 支持參數傳遞
☑ 支持上下文的傳遞
☑ 支持循環中退出(返回false的時候退出循環,節省性能)
簡單實現一個迭代器:
function each(obj, callback, context, arg) { var i = 0; var value; var length = obj.length; for (; i < length; i++) { value = callback.call(context || null, obj[i], arg); if (value === false) { break; } }
$.each()函數和$(selector).each()是不同的,後者是專門用來遍歷一個jQuery對象的,是爲jQuery內部服務的。
jQuery的實例方法最終也是調用的靜態方法,咱們在以前就解釋過jQuery的實例與原型方法共享的設計。
$.each()實例方法以下:
// 內部是直接調用的靜態方法 each: function(callback, args) { return jQuery.each(this, callback, args); },
jQuery.each靜態方法:
each: function(obj, callback, args) { var value, i = 0, length = obj.length, isArray = isArraylike(obj); if (args) { if (isArray) { for (; i < length; i++) { value = callback.apply(obj[i], args); if (value === false) { break; } } } else { for (i in obj) { value = callback.apply(obj[i], args); if (value === false) { break; } } }
實現原理幾乎一致,只是增長了對於參數的判斷。對象用for in遍歷,數組用for遍歷。