功能描述
$.Callbacks作爲jquery源碼中的一個使用非常多的工具函數,爲其他的許多模塊提供支持.它的主要功能是使用一個add方法添加函數存儲到函數隊列中,使用fire方法去依次執行隊列中的函數.另外我們可以添加各種參數對其執行隊列中的函數的種種行爲進行控制.下面我們先快速熟悉一下$.Callbacks的基本功能和用法.
var cb = $.Callback();
cb.add(function(){
conole.log("add one");
})
cb.add(function(){
conole.log("add two");
})
cb.fire() /*輸出
add one
add two
*/
$.Callbacks()生成一個實例cb,隨後調用add方法添加函數隊列,使用fire方法執行函數隊列中的函數.另外我們還可以在$.Callbacks生成實例的函數裏添加各種參數,讓其具備相應的特徵.主要有下面4種參數:
1.once:函數隊列只執行一次
2.unique:添加的重複函數保持唯一,不重複運行
3.topOnFalse :當某個函數的返回值是false ,停下來執行剩下的函數
4.memory:當函數隊列fire一次後,內部會記錄fire的參數,下次調用add的時候會將fire參數傳遞給新增的函數並且立即執行.如果fire函數添加過了參數,隊列中的所有函數運行時都會接受這個參數
$.Callbacks源碼的實現內容主要集中在上述4種參數影響下的函數體現的特徵,如果想詳細瞭解$.Callbacks具體用法可以參照jquery文檔.
源碼實現
在不考慮添加任何參數的情況下,$.Callbacks的實現邏輯十分簡單.在用戶調用add方法時將函數參數保存在list數組中,而當用戶調用fire函數時遍歷循環list數組並執行其中的函數就完成了.
once:如何保證函數隊列只執行一次呢?添加一個fired變量來記錄用戶有沒有觸發過fire函數,如果第一次觸發過了fire就置爲true,下一次用戶再調用fire函數時我們就可以根據fired變量的值和有沒有傳入"once"參數來決定要不要給他執行函數隊列中的函數.
unique:unique的實現邏輯也很簡單.在add方法中只需要判端list函數隊列中有沒有包含新加入的函數即可,再決定要不要把新加入的函數放入list隊列中.
topOnFalse:要實現此參數的功能,那就需要在遍歷執行函數隊列的過程中得到函數的返回值,如果真爲false再加上用戶添加了"topOnFalse"約束那就需要使用break語句跳出for循環
memory:memory參數的功能稍微複雜一點,因爲它要求用戶執行完fire方法後,再使用add函數添加新函數時立即並只執行新函數,而不是從頭到尾執行一遍函數隊列.處理的關鍵點在於循環遍歷list數組時起始索引值index的處理.
實現源碼:
(function(root){
/**
* 生成配置
*/
function genConfig(params){
const array = params.split(/\s+/);
const object = {};
array.forEach((column)=>{
object[column] = true;
})
return object;
}
function callback(params){
const options = (typeof params === "string")?genConfig(params):{};
const list = [];
let i,fired,start_index;//fired用來記錄是否已經使用fire函數觸發過
let memory;
function fireHandler(context,parameter){
fired = true;
memory = options["memory"] && parameter;
i = start_index?start_index:0;
for(;i<list.length;i++){
if(list[i].apply(context,parameter) === false && options["topOnFalse"]){
break;
}
}
start_index = null;
}
const result = {
add:function(){
const fn_list = Array.prototype.slice.call(arguments);
fn_list.forEach((fn)=>{
if(toString.call(fn) === "[object Function]"){
if(!list.includes(fn) || !options["unique"]){
list.push(fn)
if(options["memory"] && fired){
start_index = list.length -1;
fireHandler(result,memory);
}
}
}
})
},
fire:function(){
if(!options["once"] || !fired){
const parameter = Array.prototype.slice.call(arguments);
fireHandler(result,parameter);
}
}
}
return result;
}
const $ = {
Callbacks:callback
}
root.$ = $;
})(window)
結果驗證
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./lib/callbacks.js"></script>
</head>
<body>
</body>
<script>
var cb = $.Callbacks("memory once");
function fun(v){
console.log("add fun1:"+v);
return false;
}
cb.add(fun);
cb.add(function(v){
console.log("add fun2:"+v);
})
cb.fire("hello world11");
cb.add(function(v){
console.log("add fun3:"+v);
})
cb.add(function(v){
console.log("add fun4:"+v);
})
cb.fire("hello world22");
</script>
</html>
運行結果: