1、問題發現:
父元素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)
})
2、jQuery對象棧
jQuery内部維護着一個jQuery對象棧。每個周遊方法都會找到一組新元素(一個jQuery對象),然後jQuery會把這組元素推入到棧中。
而每個jQuery對象都有三個屬性:context、selector和prevObject,其中的prevObject屬性就指向這個對象棧中的前一個對象,而通過這個屬性可以回溯到最初的DOM元素集。
如:
<div id="view">
<h1 class="header">标題</h1>
<p>container</p>
<strong class="espe">重點</strong>
</div>
對于上面的 html:
var $view = $("#view");
// $header 是由 $view 操作得到的
var $header = $view.find('.header');
$header.prevObject === $view; // true
3、end()和addBack()
jQuery為我們操作這個内部對象棧提供了兩個非常有用的方法,這兩個函數其實就是 prevObject 的應用:
- .end()
- .andBack()
end()
函數隻是單純的進行出棧操作,并傳回出棧的這個 jQuery 對象,而
addBack()
函數不會執行出棧,而是把棧頂對象和目前的對象組成一個新對象,入棧。利用這個DOM元素棧可以減少重複的查詢和周遊的操作,而減少重複操作也正是優化jQuery代碼性能的關鍵所在。
例如:
$('#view').find('.header').css({'color': 'red'})
.end()
.find(.espe).css({'color': 'white'})
加了
end
之後,目前執行的 jQuery 對象就變成
$('#view')
了,是以可以繼續執行 find 操作等等。
$('#view').find('.header').nextAll()
.addBack()
.css({'color': 'red'}) // 顔色全紅
上面的代碼,會使得 #view 的三個子元素的顔色都設定為紅色。
4、源碼實作
既然是回溯到上一個dom合集,那麼肯定end方法中傳回的就是一個jQuery對象了,是以我們看源碼
其實就是傳回prevObject對象了
jQuery.fn.end: function() {
return this.prevObject || this.constructor(null);
},
jQuery.fn.addBack = function (selector) {
// 可以看出有參數的 addBack 會對 prevObject 進行過濾
return this.add(selector == null ? this.prevObject : this.prevObject.filter(selector));
}
prevObject在什麼情況下會産生?
在建構jQuery對象的時候,通過pushStack方法建構
pushStack:将一個DOM元素集合加入到jQuery棧。
// Take an array of elements and push it onto the stack
// (returning the new matched element set)
jQuery.fn.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.