zepto源碼研究 - callback.js

来源:http://www.cnblogs.com/zhutao/archive/2016/06/19/5593045.html
-Advertisement-
Play Games

簡要:$.Callbacks是一個生成回調管家Callback的工廠,Callback提供一系列方法來管理一個回調列表($.Callbacks的一個私有變數list),包括添加回調函數, 刪除回調函數等等...,話不多說看正文: memory的值由傳入$.Callbacks的形參對象決定,具有狀態記 ...


簡要:$.Callbacks是一個生成回調管家Callback的工廠,Callback提供一系列方法來管理一個回調列表($.Callbacks的一個私有變數list),包括添加回調函數,

刪除回調函數等等...,話不多說看正文:

 

var memory, // Last fire value (for non-forgettable lists)
        fired,  // Flag to know if list was already fired    //是否回調過
        firing, // Flag to know if list is currently firing  //回調函數列表是否正在執行中
        firingStart, // First callback to fire (used internally by add and fireWith) //第一回調函數的下標,啟動回調任務的開始位置
        firingLength, // End of the loop when firing   //回調函數列表長度?
        firingIndex, // Index of currently firing callback (modified by remove if needed),正在執行回調函數的索引
        list = [], // Actual callback list     //回調數據源: 回調列表
        stack = !options.once && [], // Stack of fire calls for repeatable lists//回調只能觸發一次的時候,stack永遠為false

 memory的值由傳入$.Callbacks的形參對象決定,具有狀態記憶功能。當為null||false時,callback.add僅僅是添加方法,而當為true時,則添加之後會立即執行。

請參考如下代碼(來自: http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)
 function fn1() {
      console.log(1)
 }
  function fn2() {
      console.log(2)
  }
  var callbacks = $.Callbacks('memory');
  callbacks.add(fn1);
  callbacks.fire(); // 必須先fire
  callbacks.add(fn2); // 此時會立即觸發fn2

 

如下是觸發回調任務的底層函數:

fire = function(data) {
          memory = options.memory && data   //記憶模式,觸發過後,再添加新回調,也立即觸發。
          fired = true
          firingIndex = firingStart || 0    //回調任務開始的索引賦值給將要執行函數的索引
          firingStart = 0                   //回調任務的觸發會將列表裡剩下的所有函數執行,因此下一次任務觸發肯定是從0開始的,這裡重置一下
          firingLength = list.length
          firing = true      //標記正在回調

          //遍歷回調列表
          for ( ; list && firingIndex < firingLength ; ++firingIndex ) {
            //如果 list[ firingIndex ] 為false,且stopOnFalse(中斷)模式
            //list[firingIndex].apply(data[0], data[1])  這是執行回調
            if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) {
              memory = false  //中斷回調執行
              break
            }
          }
          firing = false //標記回調執行完畢
          if (list) {
            //stack里還緩存有未執行的回調,如果回調任務只能執行一次則stack為false
            if (stack) stack.length && fire(stack.shift())  //執行stack里的回調
            else if (memory) list.length = 0 //memory 清空回調列表    list.length = 0清空數組的技巧
            else Callbacks.disable();             //其他情況如  once 禁用回調
          }
        },

 

fire方法在回調執行前首先初始化回調索引和回調狀態,然後迴圈順序執行回調函數,傳遞參數為data[1],以data[0]為上下文執行,執行完後根據once是否只執行一次

,處理和回收list,memory,stack等變數。

var ca = {
  name:"tom",
  age:1
}
function printName() {
  console.log(this.name+"-----"+arguments[0]);
}
function printAge() {
  console.log(this.age+"-----"+arguments[0]);
}
list.push(printName);
list.push(printAge);
fire([ca,'test']);
結果:
tom-----test
1-----test

 

接下來是創建了一個Callbacks 對象以及一系列方法

//添加一個或一組到回調列表裡
          add: function() {
            if (list) {        //回調列表已存在
              var start = list.length,   //位置從最後一個開始
                  add = function(args) {  //參數可以是:fn,[fn,fn],fn
                    $.each(args, function(_, arg){
                      if (typeof arg === "function") {    //是函數
                        //非unique,或者是unique,但回調列表未添加過
                        if (!options.unique || !Callbacks.has(arg)) list.push(arg)
                      }
                      //是數組/偽數組,添加,重新遍歷
                      else if (arg && arg.length && typeof arg !== 'string') add(arg)
                    })
                  }

              //添加進列表
              add(arguments)

              //如果列表正在執行中,修正長度,使得新添加的回調也可以執行,
              //firing:true表明fire中的迴圈執行還未結束,此時可以修改length;為false則表示迴圈執行結束了
              if (firing) firingLength = list.length
              else if (memory) {
                //memory 模式下,修正開始下標,start為list.length,這裡只迴圈一次
                firingStart = start
                fire(memory)         //立即執行所有回調
              }
            }
            return this
          }

add方法是將一系列的fn加入到回調列表中,內部的add方法用到了遞歸技巧,同時對於回調任務的執行期間做出相應處理

,如下是例子(參考鏈接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)

function fn1() {
    console.log(1)
}
function fn2() {
    console.log(2)
}
 
var callbacks = $.Callbacks();
// 方式1
callbacks.add(fn1);
// 方式2 一次添加多個回調函數
callbacks.add(fn1, fn2);
// 方式3 傳數組
callbacks.add([fn1, fn2]);
// 方式4 函數和數組摻和
callbacks.add(fn1, [fn2]);

 

remove方法會從回調列表中刪除一個或一組fn(有去重功能)

//從回調列表裡刪除一個或一組回調函數,remove(fn),remove(fn,fn)
          remove: function() {
            if (list) {       //回調列表存在才可以刪除
              //_作廢參數
              //遍歷參數
              $.each(arguments, function(_, arg){
                var index

                //如果arg在回調列表裡
                while ((index = $.inArray(arg, list, index)) > -1) {
                  list.splice(index, 1)                                //執行刪除
                  // Handle firing indexes
                  //回調正在執行中
                  if (firing) {
                    //避免回調列表溢出
                    if (index <= firingLength) --firingLength  //在正執行的回調函數後,遞減結尾下標
                    if (index <= firingIndex) --firingIndex     //在正執行的回調函數前,遞減開始下標
                  }
                }
              })
            }
            return this
          }

方法中的while迴圈是去除回調列表中重覆的函數的技巧,去除指定fn之後,如果此時回調列表正在執行回調任務,則修正回調索引(因為list中所有的回調函數的索引都改變了),這裡能傳多個fn做參數,它會迴圈刪除,如下例子(參考鏈接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)

function fn1() {
    console.log(1)
}
function fn2() {
    console.log(2)
}
var callbacks = $.Callbacks();
callbacks.add(fn1, fn2);
callbacks.remove(fn1);
//此時fire只會觸發fn2了。


var callbacks = $.Callbacks();
callbacks.add(fn1, fn2, fn1, fn2);
callbacks.remove(fn1);
//此時會把add兩次的fn1都刪掉,fire時只觸發fn2兩次。換成if則只刪fn1一次

Callbacks裡面的函數封裝了fire方法,Callbacks.fire(args),回調函數將以Callbacks為this,args為參數調用,stack的作用是若回調列表處於觸髮狀態,此時將

本次要觸發的任務信息存入stack中

/**
           * 用上下文、參數執行列表中的所有回調函數
           * @param context
           * @param args
           * @returns {*}
           */
          fireWith: function(context, args) {
            // 未回調過,非鎖定、禁用時
            if (list && (!fired || stack)) {

              args = args || []
                args = [context, args.slice ? args.slice() : args]
              if (firing) stack.push(args)  //正在回調中  ,存入static

              else fire(args) //否則立即回調
            }
            return this
          },

          /**
           * 用參數執行列表中的所有回調函數
           * @param context
           * @param args
           * @returns {*}
           */
          fire: function() {
            //執行回調
            return Callbacks.fireWith(this, arguments)
          }

 

如下:(參考鏈接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)

function fn() {
    console.log(this); // 上下文是callbacks
    console.log(arguments); // [3]
}
var callbacks = $.Callbacks();
callbacks.add(fn);
callback.fire(3);
//前面已經提到了,fire方法用來觸發回調函數,預設的上下文是callbacks對象,
//還可以傳參給回調函數。
function fn() {
    console.log(this); // 上下文是person
    console.log(arguments); // [3]
}
var person = {name: 'jack'};
var callbacks = $.Callbacks();
callbacks.add(fn);
callback.fireWith(person, 3); //callback.fire.call(person, 3);
//其實fire內部調用的是fireWith,只是將上下文指定為this了,
//而this正是$.Callbacks構造的對象。

設計思考:

add,remove方法和fire等方法內部都加入了對回調列表的狀態的判斷和相應處理,比如fireWith方法內部判斷當前回調任務是否正在進行,如果是,則將要執行的fn暫時加入到stack中。但這一設計思路對於單線程的js來說有點不太合理,如果是先執行回調列表,再fireWith,則實際過程是回調任務執行完之後再執行fireWith,這個時候回調任務已經結束了,不可能存在firing的情況。但為何jser還是要這麼設計呢?

這裡的設計是針對多線程來設計的。回調任務開始的同時,容許另一線程操作並修改回調列表內容。假想這樣一個場景:有一個網路機器人R,功能是執行所有線上客戶要求執行的一系列操作,某一時刻,客戶A上傳了一系列操作(命名為DO),當R正在執行操作時,客戶A要求此時執行DO,而R正在執行其他操作,此時,R是被鎖住的。DO則被加入到緩衝隊列中延後執行。

這裡在舉個例子說明  來自鏈接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html

// 觀察者模式
var observer = {
    hash: {},
    subscribe: function(id, callback) {
        if (typeof id !== 'string') {
            return
        }
        if (!this.hash[id]) {
            this.hash[id] = $.Callbacks()
            this.hash[id].add(callback)
        } else {
            this.hash[id].add(callback)
        }
    },
    publish: function(id) {
        if (!this.hash[id]) {
            return
        }
        this.hash[id].fire(id)
    }
}
 
// 訂閱
observer.subscribe('mailArrived', function() {
    alert('來信了')
})
observer.subscribe('mailArrived', function() {
    alert('又來信了')
})
observer.subscribe('mailSend', function() {
    alert('發信成功')
})
 
// 發佈
setTimeout(function() {
    observer.publish('mailArrived')
}, 5000)
setTimeout(function() {
    observer.publish('mailSend')
}, 10000)

 

結束語:本文素材多來自其他文章並加上了自己的理解,感謝如下兩位博客:

http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html

http://www.cnblogs.com/mominger/p/4369469.html

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文將介紹Java多線程開發必不可少的鎖和同步機制,同時介紹sleep和wait等常用的暫停線程執行的方法,並詳述synchronized的幾種使用方式,以及Java中的重入鎖(ReentrantLock)和讀寫鎖(ReadWriteLock),之後結合實例分析了重入鎖條件變數(Condition)... ...
  • 1 設計模式 類是我們面向對象編程的承載工具,可以說是面向對象的起點。 設計模式,這種算面向對象的進化。按照gof設計模式的分類 設計模式分為:創建型,結構型,行為型。 其中創建型主要和類的創建有關 結構性組織擴展類和類之間的關係 行為型主要擴展的類的訪問 這三個對應到類上 創建型模式對應的是構造函 ...
  • 數組的增加 ary.push() 向數組末尾添加元素,返回的是添加後新數組的長度,原有數組改變 ary.unshift() 向數組開頭添加元素,返回的是添加後新數組的長度,原有數組改變 var ary=[1,2,3,4]; var res=ary.unshift(6); console.log(re ...
  • 先上代碼,下麵是使用例子。 上面是HTML代碼,下麵是調用方法。 調用方法很簡單吧! 再來一個處理多行數據的例子。 data2={"data":[{"帳號":"yeruzhao1","name":"葉如兆","avatar":"http:\/\/shp.qpic.cn\/bizmp\/3eJCszw ...
  • 前言: 上周天的時候有個學長找我幫忙做三張頁面,因為沒有數據交換之類的,只是單純的前端頁面,想著好久沒做東西, 看書都看煩了,所以就接了也當是練手。之前因為沒有系統的看書,所以其實很多問題都考慮的不全面,屬於知其然不知其所以然的狀態,雖然現在也還有很多要學的東西,但是知道自己的不足總比不知道強吧?而 ...
  • var myDate = new Date();myDate.getYear(); //獲取當前年份(2位)myDate.getFullYear(); //獲取完整的年份(4位,1970-????)myDate.getMonth(); //獲取當前月份(0-11,0代表1月)myDate.getDa ...
  • 註:本實例JS部分均以原生JS編寫,不善用原生JS的,可用jQuery等對三方框架改寫 先上效果圖:(樣式有點醜,可以忽略一下下,效果出來了就好,後期加到其他項目中方便更改0.0) 類似翻書效果,原本的意思是使用JS來控制的,點擊一次之後使用setInterval去控制書頁翻過去的動畫,當書頁翻轉1 ...
  • 前端之MVC應用 1、indexedDB(Model) -- 前端瀏覽器對象型資料庫,一般我們後臺用的資料庫都是關係型資料庫。那麼indexeddb有什麼特點呢: 首先,從字義上它是索引型資料庫,從實際使用中可以體現為,它需要為表創建索引才可以根據某個欄位來獲取數據,而在關係型資料庫中,這明顯是不需 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...