jQuery源碼閱讀(十二)---Callbacks回調對象

還記得jQuery源碼閱讀(一)的時候,整理了jQuery庫的整體架構,主要分爲三個模塊:
入口模塊、底層支持模塊和功能模塊,各個模塊之間也是有關聯的。
前面幾篇博文分別分析了jQuery庫的入口模塊,最主要的是init方法的分析;還分析了jQuery底層支持模塊中的工具方法,主要是通過jQuery.extend()方法來擴展jQuery靜態方法的。底層支持模塊中,出了工具方法之外,還有很多模塊,個人覺得這些模塊的一個作用就是增強代碼的複用性和易用性。比如回調對象模塊,異步隊列模塊,延遲對象,隊列,數據緩存,瀏覽器支持模塊,Sizzle選擇器模塊等等。這次主要先整理jQuery中的回調對象。

源碼框架及使用

jQuery.Callbacks = function(flags){
    add = function(){
    }
    fire = function(){
    }
    self = {
        //定義了一些變量
        //下面是用來管理函數的方法
        add: function(){
        },
        fire: function(){
        },
        fireWith: function(){
        },
        has: function(){
        },
        fired: function(){
        },
        lock: function(){
        },
        locked: function(){
        }
        //等等
    }
    return self;
}

由上面架構可以知道,jQuery.Callbacks函數主要是返回一個回調對象,這個回調對象通過一些方法來管理回調函數。具體什麼方法呢?有add(),fire(),fireWith(),lock()等等很多方法,主要是通過將函數加入到數組列表中的方式,然後一個個去執行從而達到管理函數的目的。
可以看到,Callbacks方法裏面,定義了兩個私有函數,爲什麼要這麼做呢?
我的理解是:爲了減少內存的佔用。因爲Callbacks函數最終是要返回一個回調對象的,而回調對象中有管理函數的一些方法,如果把這些方法直接寫到回調對象中,也是可以的,但是這就意味着每個回調對象都會有一份自己的方法,只是這些方法都是同名的,這就導致內存的浪費。而把add和fire定義成兩個私有的變量,不管調用多少次jQuery.Callbacks方法,返回的回調對象中的方法都會去調Callbacks函數中的私有方法,並且指向的是相同的內存地址。

下來我們看下jQuery.Callbacks的參數情況:

1. once               //只執行一遍函數,不會重複執行
2. memory             //會將所有add進函數列表的函數執行
3. unique             //不允許列表中有相同的函數
4. stopOnFalse        //對於返回值爲false的函數,執行之後停止對後面函數的執行

首先來體會下這幾個參數的作用:

function a1(){
    console.log('111');
    return false;
}
function a2(){
    console.log('222');
}

分別展示四組圖來顯示Callbacks中四個參數的意義:

  • once標誌

這裏寫圖片描述

這裏寫圖片描述

  • memory標誌

這裏寫圖片描述

這裏寫圖片描述

  • unique標誌

這裏寫圖片描述

這裏寫圖片描述

  • stopOnFalse標誌

這裏寫圖片描述

這裏寫圖片描述

從上面四組圖可以很容易理解四個參數的含義,那麼這四個參數具體在源碼中是如何影響的?我想用下面的圖來說明
這裏寫圖片描述

源碼分析

根據上面那個圖,我們就大致應該知道:
add函數中,將函數fn Push到list數組裏,同時會判斷unique和memory兩個參數,如果unique參數爲真,那麼不會將相同的函數都push到數組裏;如果memory爲真,會add之後再調一次fire函數。

fire函數中,將數組中所有的函數進行執行,同時會判斷once和stopOnFalse兩個參數。如果once爲真,只會將所有函數執行一遍;如果stopOnFalse爲真,那麼對於返回值爲false的函數,執行之後不會再對後面的函數執行。

基於上面兩塊思路來看源碼,會更好理解一些。

add方法

回調對象中的add方法:

add: function() {
    if ( list ) {
        var length = list.length;
        //這裏的add方法是Callbacks函數中的私有方法
        add( arguments );

        if ( firing ) {
            firingLength = list.length;
        } else if ( memory && memory !== true ) {
            //判斷memory參數,並且調fire函數
            firingStart = length;
            fire( memory[ 0 ], memory[ 1 ] );
        }
    }
    return this;
}

Callbacks函數中的私有add方法:

add = function( args ) {
    var i,
    length,
    elem,
    type,
    actual;
    for ( i = 0, length = args.length; i < length; i++ ) {
        elem = args[ i ];
        type = jQuery.type( elem );
        if ( type === "array" ) {
            //如果參數爲數組,再遞歸調add方法
            add( elem );
        } else if ( type === "function" ) {
            //判斷unique標誌,並且看list數組中是否有該函數
            if ( !flags.unique || !self.has( elem ) ) {
                list.push( elem );
            }
        }
    }
}

fire方法

self對象中的fire函數:

fire: function() {
    //fire方法去調self對象的fireWith方法,該方法不僅將當前作用域傳了進去,還將fire中的參數也傳進去了
    self.fireWith( this, arguments );
    return this;
}
fireWith: function( context, args ) {
    if ( stack ) { //一開始stack爲空數組,轉換成Boolean值也是真;設置了once,會將stack置爲undefined,所以重複的fire都不會真正執行
        if ( firing ) {  //firing這個標誌是解決嵌套調用fire的情況;與stack結合起來處理,stack用於保存上下文和參數
            if ( !flags.once ) {  
                stack.push( [ context, args ] );
            }
        } else if ( !( flags.once && memory ) ) {  //第一次執行或者未設置once參數多次執行
            fire( context, args );
        }
    }
    return this;
}
fire = function( context, args ) {
    args = args || [];
    //如果未設置memory參數,那麼memory變量爲true;
    memory = !flags.memory || [ context, args ];
    fired = true;
    firing = true;
    firingIndex = firingStart || 0;
    firingStart = 0;
    firingLength = list.length;
    for ( ; list && firingIndex < firingLength; firingIndex++ ) {
        //對list中每一個函數執行,如果返回false並且stopOnFalse參數爲真,那麼跳出循環,不再對後面的函數執行。
        if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
            memory = true; // Mark as halted
            break;
        }
    }
    firing = false;
    if ( list ) {
        if ( !flags.once ) {  
            if ( stack && stack.length ) {
                memory = stack.shift();
                self.fireWith( memory[ 0 ], memory[ 1 ] );
            }
        } else if ( memory === true ) {
            self.disable();        //這個用於將stack標記設爲undefined
        } else {
            list = [];
        }
    }
}

對於Callbacks回調模塊,主要就是這兩個函數,當然,還有像self.hasself.empty, self.lock, self.disable等方法,不過相比來說,這幾個方法都比較好理解。這裏大概提下lock和disable方法,lock方法是禁止後面的fire函數; 而disable方法會禁止後面要執行的所有操作。總體上來說,Callbacks模塊,如果能考慮到所有的情況,並可以沿着源碼走一遍,那麼對於整個模塊已經理解得差不多了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章