天天看點

jQuery源碼學習(10)-回溯end()和addBack()

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.

繼續閱讀