jQuery 2.0.3 源碼分析 回溯魔法 end()和pushStack()


了解了jQuery對DOM進行遍歷背后的工作機制,可以在編寫代碼時有意識地避免一些不必要的重復操作,從而提升代碼的性能

從這章開始慢慢插入jQuery內部一系列工具方法的實現

關於jQuery對象的包裝

var $aaron = $("aaron");

通過對sizzle的分析呢,jQuery選擇器,反正最終都是通過dom接口實現取值的, 但是通過jQuery處理后返回的不僅僅只有dom對象,而是一個包裝容器

返回的jQuery對象:$aaron

image

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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM