1. 程式人生 > >jQuery回撥、遞延物件總結(上篇)—— jQuery.Callbacks

jQuery回撥、遞延物件總結(上篇)—— jQuery.Callbacks

前言:

作為引數傳遞給另一個函式執行的函式我們稱為回撥函式,那麼該回調又是否是非同步的呢,何謂非同步,如:作為事件處理器,或作為引數傳遞給

(setTimeout,setInterval)這樣的非同步函式,或作為ajax傳送請求,應用於請求各種狀態的處理,我們可以稱為非同步回撥,jQuery.Callbacks

為我們封裝了一個回撥物件模組,我們先來看一個應用場景:

// 為什麼jQuery中的ready事件可以執行多個回撥,這得益於我們的jQuery.Deferred遞延物件(是基於jQuery.Callbacks回撥模組)
jQuery(function($) {
    console.log(
'document is ready!'); // do something }); jQuery(function($) { // do something }); // 實現原型 // jQuery.Deferred版程式碼 var df = jQuery.Deferred(); df.resolve(); // 在ready事件中呼叫 // 可以多次執行繫結的函式 df.done(fn1, fn2, fn3); df.done(fn4); // ... // jQuery.Callbacks版程式碼 var cb = jQuery.Callbacks('once memory'); cb.fire();
// 在ready事件中呼叫 // 可以多次執行繫結的函式 cb.add(fn1, fn2, fn3); cb.add(fn4); // ...

現在我們知道jQuery中的ready事件是可以這樣執行多個回撥的,要想深入理解其原始碼,讓我們繼續看下面吧

jQuery回撥、遞延物件總結篇索引:

一、jQuery.Callbacks設計思路

使用一個私有變數list(陣列)儲存回撥,執行jQuery.Callbacks函式將返回一個可以操作回撥列表list的介面物件,
而傳入jQuery.Callbacks函式的options引數則用來控制返回的回撥物件操作回撥列表的行為

回撥物件中的方法

{
    add:        增加回調到list中
    remove:     從list中移除回撥
    fire:       觸發list中的回撥
    fired:      回撥物件是否執行過fire方法
    fireWith:   觸發list中的回撥,第一個引數為執行域
    has:        判斷函式是否在list中
    empty:      將list致空,list = [];
    lock:       鎖定list
    locked:     是否鎖定
    disable:    禁用回撥物件
    disabled:   是否禁用
}

引數標誌:

options = {
    once:       回撥物件僅觸發(fire)一次

    memory:     跟蹤記錄每一次傳遞給fire函式的引數,在回撥物件觸發後(fired),
                將最後一次觸發(fire)時的引數(value)傳遞給在add操作後即將被呼叫的回撥

    unique:     在add操作中,相同的函式僅只一次被新增(push)到回撥列表中

    stopOnFalse:當回撥函式返回false,中斷列表中的回撥迴圈呼叫,且memory === false,阻止在add操作中將要觸發的回撥
}

二、原始碼解析

var // Used for splitting on whitespace
    core_rnotwhite = /\S+/g;

var optionsCache = {};

// Convert String-formatted options into Object-formatted ones and store in cache
// 將字串格式選項轉化成物件格式形式,並存儲在快取物件optionsCache[options]中
// 該快取起作用適用於執行多次jQuery.Callbacks函式,且傳遞options引數一致,我們在jQuery.Deferred
// 原始碼就可以看到tuples二維陣列中執行了兩次jQuery.Callbacks('once memory')
function createOptions( options ) {
    var object = optionsCache[ options ] = {};
    jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
        object[ flag ] = true;
    });
    return object;
}
jQuery.Callbacks = function( options ) {

    // Convert options from String-formatted to Object-formatted if needed
    // (we check in cache first)
    options = typeof options === "string" ?
        ( optionsCache[ options ] || createOptions( options ) ) :
        jQuery.extend( {}, options );

    var // Flag to know if list is currently firing
        firing,
        // Last fire value (for non-forgettable lists)
        memory,
        // Flag to know if list was already fired
        fired,
        // End of the loop when firing
        firingLength,
        // Index of currently firing callback (modified by remove if needed)
        firingIndex,
        // First callback to fire (used internally by add and fireWith)
        firingStart,
        // Actual callback list
        list = [],
        // Stack of fire calls for repeatable lists
        stack = !options.once && [],
        // Fire callbacks
        fire = function( data ) {
            memory = options.memory && data;
            fired = true;
            firingIndex = firingStart || 0;
            firingStart = 0;
            firingLength = list.length;
            firing = true;
            // 迭代list回撥列表,列表中的回撥被應用(或執行回撥)
            for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                // 如果回撥返回false,且options.stopOnFlase === true,則中斷迴圈
                // 注:data[1]是一個偽陣列(self.fire方法中的arguments(引數集合))
                if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {                    
                    memory = false; // To prevent further calls using add   // 阻止在add操作中可能執行的回撥
                    break;
                }
            }
            firing = false;
            if ( list ) {
                // (options.once === undefined),回撥物件可以觸發多次
                if ( stack ) {
                    // 處理正在執行的回撥中的fireWith操作
                    // 注:如果執行的回撥中真的擁有fire或fireWith操作,那麼列表中的回撥將會無限迴圈的執行,請看例項1
                    if ( stack.length ) {
                        fire( stack.shift() );
                    }
                }
                // (options.once === true && options.memory === true)
                // 回撥列表致空,但允許add繼續新增並執行回撥
                else if ( memory ) {
                    list = [];
                }
                // (options.once === true && options.memory === undefined)
                // 禁用回撥物件
                else {
                    self.disable();
                }
            }
        },
        // Actual Callbacks object
        self = {
            // Add a callback or a collection of callbacks to the list
            add: function() {
                if ( list ) {
                    // First, we save the current length
                    var start = list.length;
                    (function add( args ) {
                        jQuery.each( args, function( _, arg ) {
                            var type = jQuery.type( arg );
                            if ( type === "function" ) {
                                // 回撥不唯一 或 唯一且不存在,則push
                                if ( !options.unique || !self.has( arg ) ) {
                                    list.push( arg );
                                }
                            } else if ( arg && arg.length && type !== "string" ) {
                                // Inspect recursively
                                // 遞迴檢查
                                add( arg );
                            }
                        });
                    })( arguments );

                    // Do we need to add the callbacks to the
                    // current firing batch?
                    // 如果正在執行的回撥執行了add操作,更新firingLength,將列表中新加進來的最後一個回撥加入到回撥執行的佇列中
                    if ( firing ) {
                        firingLength = list.length;

                    // With memory, if we're not firing then
                    // we should call right away
                    // 如果可能(options.memory === true),在回撥物件不能再次fire(options.once === true)時,
                    // 我們應該使用memory(記錄的最後一次fire時的引數)立即呼叫回撥
                    } else if ( memory ) {
                        firingStart = start;
                        fire( memory );
                    }
                }
                return this;
            },
            // Remove a callback from the list
            remove: function() {
                if ( list ) {
                    jQuery.each( arguments, function( _, arg ) {
                        var index;
                        while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                            list.splice( index, 1 );
                            // Handle firing indexes
                            // 在回撥物件觸發(fire)時,如果firingLength、firingIndex(正在執行的回撥在列表list中的索引index)
                            // 大於等於 移除的回撥的索引(index),分別減一,確保回撥執行佇列中未執行的回撥依次執行
                            if ( firing ) {
                                if ( index <= firingLength ) {
                                    firingLength--;
                                }
                                if ( index <= firingIndex ) {
                                    firingIndex--;
                                }
                            }
                        }
                    });
                }
                return this;
            },
            // Check if a given callback is in the list.
            // If no argument is given, return whether or not list has callbacks attached.
            // 檢查給定的回撥是否在列表中
            // 如果未給定回撥引數,返回列表是否有回撥
            has: function( fn ) {
                return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
            },
            // Remove all callbacks from the list
            // 將列表致空,list = []; firingLenght = 0;
            empty: function() {
                list = [];
                firingLength = 0;
                return this;
            },
            // Have the list do nothing anymore
            // 禁用回撥物件
            // 將list賦值為undefined就可以使self中的add,remove,fire,fireWith方法停止工作
            // 我認為這裡把stack、memory賦值為undefined與否是沒有任何關係的
            disable: function() {
                list = stack = memory = undefined;
                return this;
            },
            // Is it disabled?
            disabled: function() {
                return !list;
            },
            // Lock the list in its current state
            // 鎖定回撥列表
            // 如果(fired !== true || options.memory === false),則視為禁用(disable)
            // 如果(fired === true && options.memory === true),則視為options.once === true
            // 請看例項2
            lock: function() {
                stack = undefined;
                if ( !memory ) {
                    self.disable();
                }
                return this;
            },
            // Is it locked?
            locked: function() {
                return !stack;
            },
            // Call all callbacks with the given context and arguments
            fireWith: function( context, args ) {
                // 回撥物件未執行過fire 或且 可以執行多次(options.once === false)
                // 如果(fired === true && options.once === true),則不會執行fire操作
                if ( list && ( !fired || stack ) ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    if ( firing ) {
                        stack.push( args );
                    } else {
                        fire( args );
                    }
                }
                return this;
            },
            // Call all the callbacks with the given arguments
            fire: function() {
                self.fireWith( this, arguments );
                return this;
            },
            // To know if the callbacks have already been called at least once
            fired: function() {
                return !!fired;
            }
        };

    return self;
};

三、例項

例項1、 處理回撥函式中的fire,或fireWidth操作

var cb = jQuery.Callbacks();

var fn1 = function(arg){ console.log( arg + '1' ); };
var fn2 = function(arg){ console.log( arg + '2' ); cb.fire();  };
var fn3 = function(arg){ console.log( arg + '3' ); };

cb.add(fn1, fn2, fn3);

cb.fire('fn'); // 其中回撥fn1,fn2,fn3無限制的迴圈呼叫

/*
控制檯將無限制輸出如下:
fn1
fn2
fn3
fn1
fn2
fn3
fn1
fn2
fn3
.
.
.
*/

例項2、 鎖定(lock)操作各種場景中的用法

var cb1 = jQuery.Callbacks();
var cb2 = jQuery.Callbacks('memory');
var cb3 = jQuery.Callbacks('memory');

var fn1 = function(arg){ console.log( arg + '1' ); };
var fn2 = function(arg){ console.log( arg + '2' ); };
var fn3 = function(arg){ console.log( arg + '3' ); };

// 如果options.memory !== true,鎖定操作視為禁用回撥物件
cb1.add(fn1);
cb1.lock();
// 以下操作無任何反應
cb1.add(fn2);
cb1.fire('fn');

// 如果fried !== true,鎖定操作也視為禁用回撥物件
cb2.add(fn1);
cb2.lock();
// 以下操作無任何反應
cb2.add(fn2);
cb2.fire('fn');

// 如果(fired === true && options.memory === true),鎖定操作類似控制標誌once(options.once === true);
cb3.add(fn1);
cb3.fire('fn'); // fn1,此時fired === true
cb3.lock();     // 像是傳入了'once'標誌,jQuery.Callbacks('once memory');
cb3.add(fn2);   // fn2
cb3.fire('fn'); // 再次觸發,無任何反應
cb3.add(fn3);   // fn3


// 再來看看jQuery.Deferred中的一段原始碼
var tuples = [
    // action, add listener, listener list, final state
    [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
    [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
    [ "notify", "progress", jQuery.Callbacks("memory") ]
];

// Handle state
if ( stateString ) {
    list.add(function() {
        // state = [ resolved | rejected ]
        state = stateString;

    // [ reject_list | resolve_list ].disable; progress_list.lock
    }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}

/*
    當執行了tuples中前面兩組中任意一個回撥物件的fire方法時,後一組回撥物件被鎖定,
    相當於(fired === true && options.memory === true),後一組回撥物件實際為執行
    jQuery.Callbacks('once memory')生成的回撥物件。
*/

PS: 如有描述錯誤,請幫忙指正,如果你們有不明白的地方也可以發郵件給我,

  如需轉載,請附上本文地址及出處:部落格園華子yjh,謝謝!

相關推薦

jQuery物件總結—— jQuery.Callbacks

前言: 作為引數傳遞給另一個函式執行的函式我們稱為回撥函式,那麼該回調又是否是非同步的呢,何謂非同步,如:作為事件處理器,或作為引數傳遞給 (setTimeout,setInterval)這樣的非同步函式,或作為ajax傳送請求,應用於請求各種狀態的處理,我們可以稱為非同步回撥,jQuery.Callba

jQuery物件總結中篇) —— 神奇的then方法

前言: 什麼叫做遞延物件,生成一個遞延物件只需呼叫jQuery.Deferred函式,deferred這個單詞譯為延期,推遲,即延遲的意思,那麼在jQuery中 又是如何表達延遲的呢,從遞延物件中的then方法或許能找到這種延遲的行為,本文重點解讀遞延物件中的then方法 jQuery回撥、遞延物件

jQuery物件總結下篇) —— 解密jQuery.when方法

前言: 前一篇文章中重點總結了一下then方法,它主要用來處理多個非同步任務按順序執行,即前一個任務處理完了,再繼續下一個,以此類推; 而這一章節jQuery.when方法也是處理多個非同步任務,它把多個非同步任務(Promise物件)合併為一個Promise物件,這個合併後的Promise物件 到底是

【MyBatis源碼分析】insert方法update方法delete方法處理流程

times database connect 環境 enable clas 它的 java對象 ace 打開一個會話Session 前文分析了MyBatis將配置文件轉換為Java對象的流程,本文開始分析一下insert方法、update方法、delete方法處理的流程,至

Android通知欄介紹與適配總結

此文已由作者黎星授權網易雲社群釋出。 歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。 由於歷史原因,Android在釋出之初對通知欄Notification的設計相當簡單,而如今面對各式各樣的通知欄玩法,谷歌也不得不對其進行更新迭代調整,增加新功能的同時,也在不斷地改變樣式,試圖迎合更多

簡單總結jQuery API中的知識點之DOM操作

    最近為了學習前端其他新的知識,特意重新複習了一遍jQuery,算是查漏補缺,把之前學習時忽略的知識點補回來順便也把一些知識點做了總結方便自己理解。 JQ的DOM操作分類 1.遍歷 (1)遍歷的介面:父級,後代,同胞兄弟(相鄰元素),過濾 (2)遍歷的結構設計  di

編程經常使用設計模式具體解釋--工廠單例建造者原型

-a 裝飾器模式 nds support art 類的繼承 兩個 開放 lose 參考來自:http://zz563143188.iteye.com/blog/1847029 一、設計模式的分類 整體來說設計模式分為三大類: 創建型模式。共五種:工廠方法模式、抽

支付網關 | 京東618雙11用戶支付的核心承載系統

java 支付 雙11 支付網關 618 二零一七年六月二十一日,就是年中大促剛結束的那一天,我午飯時間獨在辦公室裏徘徊,遇見X君,前來問我道,“可曾為這次大促寫了一點什麽沒有?”我說“沒有”。他就正告我,“還是寫一點罷;小夥伴們很想了解支撐起這麽大的用戶支付流量所采用的技術。”「摘要

Leetcode解題思路總結Easy

otto 一半 number steps aced 其中 運動 may copy 終於刷完了leetcode的前250道題的easy篇。好吧,其實也就60多道題,但是其中的套路還是值得被記錄的。 至於全部code,請移步github,題目大部分采用python3,小部分使用

Java個人技術知識點總結框架

框架篇 Struts1的執行原理 在啟動時通過前端總控制器ActionServlet載入struts-config.xml並進行解析,當用戶在jsp頁面傳送請求被struts1的核心控制器ActionServlet接收,ActionServlet在使用者請求時將請求引數放

c++| |類和物件

類和物件(上篇) 1.類和物件的初步認知 c語言是面向過程的,關注的是過程,分析出求解問題的步驟,通過函式呼叫逐步解決問題 c++是基於面向物件的,關注的是物件,將一件事情拆分成不同的物件,考物件之間的互動完成 2.類的引入 c語言中,結構

java設計模式個人總結第一

java設計模式可將其分為三種類型:建立型、結構型、行為型。 建立型有:    工廠模式(Factory)單例模式(singleton)Builder模式(生成器模式)原型模式(Prototype) 壹.工廠模式(Factory)(典型案例:製造商品流水)

關於在本地idea當中提交spark程式碼到遠端的錯誤總結第二

當代碼能正常提交到spark叢集執行的時候,出現下面的錯誤: Exception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.lang.ClassLoader.defineClass1(Native Me

前端知識點總結HTML

HTML ie的某些相容性問題 doctype的作用 HTML中標準模式和怪異模式有什麼不同 寫出你常用的HTML標籤 為什麼要少用iframe HTML語義化的理解 行內元素和塊級元素的異同及img類似的特殊性 盒模型,及在瀏覽器相容方面的異同 HTML5

前端知識點總結CSS

聖盃佈局 CSS合併方法 盒子模型 CSS定位 CSS動畫原理 CSS3動畫(簡單動畫的實現,如旋轉等) CSS不同選擇器的權重(CSS層疊的規則) flexbox佈局 塊級元素和行內元素的異同 CSS在效能優化方面的實踐(比方說選擇器的效率等) CSS打包壓縮的

【python基礎】面向物件程式設計初級

在Python教學中發現,很多同學在走到面向物件程式設計這塊就開始蒙圈了,為了幫助大家更好的理解面向物件程式設計並其能將其用到自己的開發過程中,特寫此文。 概述 面向過程:根據業務邏輯從上到下寫壘程式碼 函式式:將某功能程式碼封裝到函式中,日後便無需重複

JAVA執行緒總結完結

執行緒池概述 程式啟動一個新執行緒成本是比校高的,因カ它渉及到要與作業系統迸行互動而使用執行緒池可以很好的提高效能,尤其是當程式中要建立大量生存期很短的執行緒吋,更應該考慮執行緒池.執行緒池裡的毎一個執行緒程式碼結束後,井不會死亡,而是再次回到執行緒池中成為空閒

Java總結Web

Web三要素·一、瀏覽器-向伺服器發起請求,下載伺服器中的網頁(HTML),然後執行HTML顯示出內容。二、伺服器-接收瀏覽器的請求,傳送相應的頁面到瀏覽器。三、HTTP協議-瀏覽器與伺服器的通訊協議。

安卓 Data Binding 使用方法總結姐姐

0. 前言 在專案中使用到了 Data Binding,總結使用經驗後寫成本文。 本文涉及安卓自帶框架 DataBinding 的基礎使用方法,適合初次接觸 Data Binding 的同學閱讀。 1. Data Binding 利弊 優勢 D

C/C++日常學習總結第一const用法及printf的執行順序

1.c語言中printf在不同編譯器下面的執行順序    【程式碼】:   int n = 0; printf("%d,%d,%d",++(++n),++(++n),++(++n));    【結果】:      VC6.0下面的結果是:6,5,4