jQuery原始碼閱讀(十二)---Callbacks回撥物件

鐺鐺鐺鐺Huan發表於2017-07-24

還記得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模組,如果能考慮到所有的情況,並可以沿著原始碼走一遍,那麼對於整個模組已經理解得差不多了。

相關文章