預設回調對象設計
不傳入任何參數,調用add的時候将函數add到内部的list中,調用fire的時候順序觸發list中的回調函數:
function fn1(val) {
console.log('fn1 says:' + val);
}
function fn2(val) {
console.log('fn2 says ' + val);
}
var cbs = $.Callbacks();
cbs.add(fn1);
cbs.fire('foo');
console.log('........')
cbs.add(fn2);
cbs.fire('bar')
結果就是按照順序疊加觸發,如下清單:
fn1 says:foo
………………………
fn1 says:bar
fn2 says bar
這種就是最簡單的處理了,可以直接模拟,代碼如下:
function Callbacks() {
var list = [];
var self;
self = {
add: function(fn) {
list.push(fn)
},
fire: function(args) {
list.forEach(function(fn) {
fn(args);
})
}
}
return self;
}
代碼:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>
<title></title>
</head>
<body>
<script type="text/javascript">
function Callbacks() {
var list = [];
var self;
self = {
add: function(fn) {
list.push(fn)
},
fire: function(args) {
list.forEach(function(fn) {
fn(args);
})
}
}
return self;
}
function fn1(val) {
show('fn1 says:' + val);
}
function fn2(val) {
show('fn2 says ' + val);
}
var cbs = Callbacks();
cbs.add(fn1);
cbs.fire('foo');
cbs.add(fn2);
cbs.fire('bar')
</script>
</body>
</html>
once的設計
once的作用確定回調清單隻執行(.fire())一次(像一個遞延 Deferred),如下代碼:
function fn1(val){
console.log('fn1 says ' + val);
}
var cbs = $.Callbacks('once');
cbs.add(fn1);
cbs.fire('foo');
cbs.fire('foo');
結果你會發現cbs.fire('foo')隻執行了一次。
fn1 says foo //隻顯示一次
once定義是很明确的,確定這個回調清單隻執行( .fire() )一次(像一個遞延 Deferred),是以針對這種once的處理可以有多種不同的途徑實作。
1、add的時候抛棄
2、在fire的時候抛棄多個。
但是jQuery是在執行第一個fire的時候直接給清空list清單了,然後在add的地方給判斷下list是否存在,進而達到這樣的處理。
function Callbacks(options) {
var list = [];
var self;
self = {
add: function(fn) {
list.push(fn)
},
fire: function(args) {
if (list) {
list.forEach(function(fn) {
fn(args);
})
if (options === 'once') {
list = undefined;
}
}
}
}
return self;
}
在fire之後,判斷參數是否為once,直接把list給清理掉,是以之後的所有fire都被抛棄掉了,而從達到了once的效果。
jQuery.Callbacks的處理
在fire中調用了 self.disable(); 方法
// 禁用回調清單中的回調。
disable: function() {
list = stack = memory = undefined;
return this;
},
執行個體代碼:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>
<title></title>
</head>
<body>
<script type="text/javascript">
function Callbacks(options) {
var list = [];
var self;
self = {
add: function(fn) {
list.push(fn)
},
fire: function(args) {
if(list){
list.forEach(function(fn) {
fn(args);
})
if(options === 'once'){
list = undefined;
}
}
}
}
return self;
}
function fn1(val) {
show('fn1 says:' + val);
}
function fn2(val) {
show('fn2 says ' + val);
}
var cbs = Callbacks('once');
cbs.add(fn1);
cbs.fire('foo');
cbs.fire('foo');
</script>
</body>
</html>
memory的設計
memory:保持以前的值,将添加到這個清單的後面的最新的值立即執行調用任何回調 (像一個遞延 Deferred)。
回調函數是從異步隊列Deferred分離出來的,是以很多的接口設計都是為了契合Deferred接口,memory用的很多,這個緩存的設計這裡提及一下
主要是用來實作deferred的異步收集與pipe管道風格的資料傳遞的,具體在Deferred有詳解,這裡大概了解下作用範圍。
memory這個有點不好了解,我們還是通過列子說明下,看下面的代碼:
var cbs = Callbacks('once');
cbs.add(fn1);
cbs.fire('foo');
cbs.fire('foo');
function fn1(val) {
console.log('fn1 says ' + val);
}
function fn2(val) {
console.log('fn2 says ' + val);
}
function fn3(val) {
console.log('fn3 says ' + val);
}
var cbs = $.Callbacks('memory');
cbs.add(fn1);
cbs.fire('foo');
console.log('..........')
cbs.add(fn2);
cbs.fire('bar');
console.log('..........')
cbs.add(fn3);
cbs.fire('aaron');
結果可以看出,我們在執行cbs.add(fn2);的時候,此時除了把fn2添加到了回調隊列之外而且還立刻執行了這個方法,唯一的差別就是,參數是用的之前的。是以解釋就叫“保持以前的值”。
fn1 says foo
..........
fn2 says foo
fn1 says bar
fn2 says bar
..........
fn3 says bar
fn1 says aaron
fn2 says aaron
fn3 says aaron
是以這個
memory
設計需要解決的問題就是:
1:如何取到上一個參數
2:add後如何執行
看看我們實作的代碼:
function Callbacks(options) {
var list = [];
var self;
var firingStart;
var memory;
function _fire(data) {
memory = options === 'memory' && data;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
for (; list && firingIndex < firingLength; firingIndex++) {
list[firingIndex](data)
}
}
self = {
add: function(fn) {
var start = list.length;
list.push(fn)
if (memory) {
firingStart = start; //擷取最後一值
_fire(memory);
}
},
fire: function(args) {
if (list) {
_fire(args)
}
}
}
return self;
}
首先add之後要能觸發fire的動作,是以我們把fire作為内部的一個私有方法實作_fire,比較合邏輯,這樣外部的fire隻是一個門面方法的調用。
私有變量memory緩存這上一個參數的屬性,我們靠firingStart用來定位最後通過add增加的回調資料的索引。在周遊的時候直接通過firingStart的起始索引定位,然後傳遞memory的參數,而且實作這種“保持以前的值”的設計。
執行個體代碼:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>
<title></title>
</head>
<body>
<script type="text/javascript">
function Callbacks(options) {
var list = [];
var self;
var firingStart;
var memory;
function _fire(data) {
memory = options === 'memory' && data;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
for (; list && firingIndex < firingLength; firingIndex++) {
list[firingIndex](data)
}
}
self = {
add: function(fn) {
var start = list.length;
list.push(fn)
if (memory) {
firingStart = start; //擷取最後一值
_fire(memory);
}
},
fire: function(args) {
if (list) {
_fire(args)
}
}
}
return self;
}
function fn1(val) {
show('fn1 says ' + val);
}
function fn2(val) {
show('fn2 says ' + val);
}
function fn3(val) {
show('fn3 says ' + val);
}
var cbs = Callbacks('memory');
cbs.add(fn1);
cbs.fire('foo');
cbs.add(fn2);
cbs.fire('bar');
cbs.add(fn3);
cbs.fire('aaron')
</script>
</body>
</html>
unique的設計
Unique:確定一次隻能添加一個回調(是以在清單中沒有重複的回調)
function fn1(val) {
console.log('fn1 says ' + val);
}
var callbacks = $.Callbacks( "unique" );
callbacks.add( fn1 );
callbacks.add( fn1 ); // repeat addition
callbacks.add( fn1 );
callbacks.fire( "foo" );
結果:過濾了相同的add操作
fn1 says foo
過濾重複的比較簡單,因為是數組的儲存方式,我們可以在入口處通過indexOf判斷即可
function Callbacks(options) {
var list = [];
var self;
var firingStart;
var memory;
function _fire(data) {
memory = options === 'memory' && data;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
for (; list && firingIndex < firingLength; firingIndex++) {
list[firingIndex](data)
}
}
self = {
add: function(fn) {
var start = list.length;
if (options == 'unique') {
if (-1 === list.indexOf(fn)) {
list.push(fn)
}
} else {
list.push(fn)
}
if (memory) {
firingStart = start; //擷取最後一值
_fire(memory);
}
},
fire: function(args) {
if (list) {
_fire(args)
}
}
}
return self;
}
執行個體代碼:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>
<title></title>
</head>
<body>
<script type="text/javascript">
function Callbacks(options) {
var list = [];
var self;
var firingStart;
var memory;
function _fire(data) {
memory = options === 'memory' && data;
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
for (; list && firingIndex < firingLength; firingIndex++) {
list[firingIndex](data)
}
}
self = {
add: function(fn) {
var start = list.length;
if (options == 'unique') {
if (-1 === list.indexOf(fn)) {
list.push(fn)
}
} else {
list.push(fn)
}
if (memory) {
firingStart = start; //擷取最後一值
_fire(memory);
}
},
fire: function(args) {
if (list) {
_fire(args)
}
}
}
return self;
}
function fn1(val) {
show('fn1 says ' + val);
}
var callbacks = Callbacks( "unique" );
callbacks.add( fn1 );
callbacks.add( fn1 ); // 重複添加
callbacks.add( fn1 );
callbacks.fire( "foo" );
</script>
</body>
</html>
stopOnFalse
stopOnFalse: 當一個回調傳回false 時中斷調用
function fn1(value) {
console.log(value);
return false;
}
function fn2(value) {
fn1("fn2 says: " + value);
return false;
}
var callbacks = $.Callbacks("stopOnFalse");
callbacks.add(fn1);
callbacks.fire("foo");
callbacks.add(fn2);
callbacks.fire("bar");
結果雖然fn1被添加到了回調清單,但是因為fn1傳回了false,那麼意思之後的回調都不會被調用了。如果還有fn3,在f2上傳回false,fn3也将不會被調用。
foo
bar
這個設計我們隻要控制好函數傳回的處理的布爾值,通過這個值用來判斷是否需要下一個周遊
if (list[firingIndex](data) === false && options === 'stopOnFalse') {
break;
}
源碼如下:
function Callbacks(options) {
var list = [];
var self;
var firingStart;
var memory;
function _fire(data) {
memory = options === 'memory' && data;
firingIndex =
firingStart || 0;
firingStart = 0;
firingLength = list.length;
for (; list && firingIndex < firingLength; firingIndex++) {
if (list[firingIndex](data) === false && options === 'stopOnFalse') {
break;
}
}
}
self = {
add: function(fn) {
var start = list.length;
if (options == 'unique') {
if (-1 === list.indexOf(fn)) {
list.push(fn)
}
} else {
list.push(fn)
}
if (memory) {
firingStart = start; //擷取最後一值
_fire(memory);
}
},
fire: function(args) {
if (list) {
_fire(args)
}
}
}
return self;
}
以上是幾種單獨的處理情況的用法,我們可以看到jQuery都是組合使用的,最常見的就是
jQuery.Callbacks("once memory")的組合了,其實以上的思路都講解過了,無非就是組合起來的時候要考慮一些判斷了。
代碼示例:
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script src="http://img.mukewang.com/down/540812440001e40e00000000.js" type="text/javascript"></script>
<script src="http://img.mukewang.com/down/541f6ff70001a0a500000000.js" type="text/javascript"></script>
<title></title>
</head>
<body>
<script type="text/javascript">
function Callbacks(options) {
var list = [];
var self;
var firingStart;
var memory;
function _fire(data) {
memory = options === 'memory' && data;
firingIndex =
firingStart || 0;
firingStart = 0;
firingLength = list.length;
for (; list && firingIndex < firingLength; firingIndex++) {
if (list[firingIndex](data) === false && options === 'stopOnFalse') {
break;
}
}
}
self = {
add: function(fn) {
var start = list.length;
if (options == 'unique') {
if (-1 === list.indexOf(fn)) {
list.push(fn)
}
} else {
list.push(fn)
}
if (memory) {
firingStart = start; //擷取最後一值
_fire(memory);
}
},
fire: function(args) {
if (list) {
_fire(args)
}
}
}
return self;
}
function fn1( value ){
show( value );
return false;
}
function fn2( value ){
fn1( "fn2 says: " + value );
return false;
}
var callbacks = Callbacks('stopOnFalse');
callbacks.add(fn1);
callbacks.fire("foo1");
callbacks.add(fn2);
callbacks.fire("foo2");
</script>
</body>
</html>