jQuery 2.0.3 原始碼分析 回溯魔法 end()和pushStack()
瞭解了jQuery對DOM進行遍歷背後的工作機制,可以在編寫程式碼時有意識地避免一些不必要的重複操作,從而提升程式碼的效能
從這章開始慢慢插入jQuery內部一系列工具方法的實現
關於jQuery物件的包裝
var $aaron = $("aaron");
通過對sizzle的分析呢,jQuery選擇器,反正最終都是通過dom介面實現取值的, 但是通過jQuery處理後返回的不僅僅只有dom物件,而是一個包裝容器
返回的jQuery物件:$aaron
jQuery物件,其中有個prevObject這個是幹嘛用的呢?
jQuery物件棧
jQuery內部維護著一個jQuery物件棧。每個遍歷方法都會找到一組新元素(一個jQuery物件),然後jQuery會把這組元素推入到棧中。
而每個jQuery物件都有三個屬性:context、selector和prevObject,其中的prevObject屬性就指向這個物件棧中的前一個物件,而通過這個屬性可以回溯到最初的DOM元素集。
簡單的測試demo
父元素ul,嵌套了li節點, 我們現給li繫結一個事件
<ul id="aaron">
parent
<li>child</li>
</ul>
這個很簡單找到ul下面的li,繫結即可
var aaron = $("#aaron"); aaron.find('li').click(function(){ alert(1) //1 })
此時我又想給父元素繫結一個事件,我們是不是又要在aaron上繫結事件?
通過find處理後,此時的上下文是每一個li了,所以必須要重新引用aaron父元素
aaron.click(function(){ alert(2) //1 })
所有jQuery引入一個機制,可以回溯到之前的dom元素集合
通過end()方法
aaron.find('li').click(function(){ alert(1) }).end().click(function(){ alert(2) })
jQuery為我們操作這個內部物件棧提供了兩個非常有用的方法:
- .end()
- .andBack()
呼叫第一個方法只是簡單地彈出一個物件(結果就是回到前一個jQuery物件)。第二個方法更有意思,呼叫它會在棧中回溯一個位置,然後把兩個位置上的元素集組合起來,並把這個新的、組合之後的元素集推入棧的上方。
利用這個DOM元素棧可以減少重複的查詢和遍歷的操作,而減少重複操作也正是優化jQuery程式碼效能的關鍵所在。
.end() 方法
大多數 jQueryDOM遍歷 方法來操作 jQuery 物件例項,並建立一個新的物件,匹配一個不同的 DOM 元素集合。當發生這種情況時,實際上是新的元素集合被壓入到物件內部維護的棧中。每次過濾方法都會被壓入棧中。當我們需要返回到前一個狀態時,我們可以使用end()
進行出棧操作,來返回棧中的前一個狀態。
假設頁面上有幾個短的列表
<ul class="first"> <li class="foo">list item 1</li> <li>list item 2</li> <li class="bar">list item 3</li> </ul> <ul class="second"> <li class="foo">list item 1</li> <li>list item 2</li> <li class="bar">list item 3</li> </ul>
end()
方法主要用於 jQuery 的鏈式屬性中。當沒有使用鏈式用法時,我們通常只是呼叫變數名上的前一個物件,所以我們不需要操作棧。使用 end()
時,我們可以一次性呼叫所有需要的方法:
$('ul.first').find('.foo').css('background-color', 'red')
.end().find('.bar').css('background-color', 'green');
鏈式的原理就是要返回當前操作的上下文
錯誤的:
跟上面的demo一樣,上下文被切換了,所以下面find(‘bar’)出錯了
$('ul.first').find('.foo').css('background-color', 'red').find('.bar').css('background-color', 'green');
正確的:
首先在鏈式用法中只在第一個列表中查詢樣式為 foo
的專案,並將其背景色變成紅色。然後 end()
返回呼叫 find()
之前的狀態。因此,第二次 find()
將只會查詢 <ul class="first">
中的 '.bar',而不是繼續在<li class="foo">
中進行查詢,結果是將匹配到的元素的背景色變成綠色。上述程式碼的最終結果是,第一個列表中的第 1 和第 3 個列表項的背景色有顏色,而第二個列表中的任何專案都沒有背景色。
$('ul.first').find('.foo').css('background-color', 'red')
.end().find('.bar').css('background-color', 'green');
總的來說end方法就是回溯到上一個dom合集,因此對於鏈式操作與優化,這個方法還是很有意義的
原始碼實現
既然是回溯到上一個dom合集,那麼肯定end方法中返回的就是一個jQuery物件了,所以我們看原始碼
其實就是返回prevObject物件了
end: function() { return this.prevObject || this.constructor(null); },
prevObject在什麼情況下會產生?
在構建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 ); ret.selector = this.selector ? this.selector + " " + selector : selector; return ret; }
pushStack:將一個DOM元素集合加入到jQuery棧。
pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; // Return the newly-formed element set return ret; },
流程解析:
1. 構建一個新的jQuery物件,無參 this.constructor(),只是返回引用this
2. jQuery.merge 把elems節點,合併到新的jQuery物件
3. 給返回的新jQuery物件新增屬性prevObject ,所以我們看到prevObject 其實還是當前jQuery的一個引用罷了
所以也就是為什麼通過prevObject能取到上一個合集的引用了
總結:
- pushStack()方法在jQuery的DOM操作中被頻繁的使用, 如在parent(), find(), filter()中, 當然還有其他許多類似的方法, 它們往往需要返回一個jQuery封裝過的DOM結果集.但在我們自己寫jQuery程式碼的時候,卻很少關注或使用過pushStack()
- 在jQuery內部,pushStack()方法通過改變一個jQuery物件的prevObject屬性來"跟蹤"鏈式呼叫中前一個方法返回的DOM結果集(被jQuery封裝過,也是個jQuery物件,說是"跟蹤",是因為實際儲存的是個引用). 當我們再鏈式呼叫end()方法後, 內部就返回當前jQuery物件的prevObject.