jQuery Callbacks:
简单理解为:一个容器,里面可以存放多个函数,有add方法往容器里面填加函数,有fire方法把容器里面的所有函数都依次执行一遍。
创建容器:$.Callback([string])
创建一个Callback实例,可选传参数:
- once : 当调用fire方法时,容器里的函数只执行1次
- unique:去重,执行add方法时,检测有没有添加过
- stopOnfalse :当调用fire方法时,容器里的函数返回值为false时,停止执行后面的函数
- memory:当执行过一次fire后,再执行add时,会立即执行添加的新函数
Note:也可以理解为一个队列,不过队列的概念比较大,Callbacks并不具有队列的所有特性
使用方法:参数可以组合使用,例如:”once memory”
let cb = $.Callbacks();
$.Callbacks('once')
$.Callbacks('unique')
$.Callbacks('stopOnfalse')
$.Callbacks('memory')
$.Callbacks('memory once')
实现:
- 第一个版本:尝试写一个简单的Callbacks。
function Callbacks(){
let list = [];
//返回一个有add和fire方法的对象,因为每次调用callbacks都需要是一个全新的对象
return {
add:function(fn){
if(toString.call(fn)==='[object Function]'){
list.push(fn);
}
},
fire:function(){
list.forEach(fn=>fn());
}
};
}
let cbs = Callbacks();
cbs.add(function(){
console.log('Hello!');
})
cbs.add(function(){
console.log('How are you?');
})
cbs.fire() //Hello! How are you?
cbs.fire() //Hello! How are you? (再次调用fire,则再次执行容器中所有的方法)
在上面的例子中,并没有考虑到Callbacks的可选参数,也就是要告诉容器要遵循的运行规则,共有四种规则:once、unique、stopOnfalse、memory,而且这些规则可以组合使用。
- 第二个版本: 考虑4种规则。
// rules: once, unique, memory, stopOnFalse
function Callbacks(rules) {
var list = [];
var rObj = createRules(rules);
var isFired = false;
return {
add: function(fn) {
if(toString.call(fn) === '[object Function]') {
// 需要检查unique
if(rObj.unique) {
list.indexOf(fn) === -1 && list.push(fn);
}
else {
list.push(fn);
}
// 如果有memory,且fire执行过,则立即执行当前fn
if(rObj.memory && isFired) {
fn();
}
}
return this;
},
fire: function() {
if(!rObj.once || !isFired) {
for(var i = 0; i< list.length; i++) {
if(list[i]() === false && rObj.stopOnFalse) {
break;
}
}
isFired = true;
}
}
};
//通过callback('once memory')创建容器时,此时obj:{once:true,memory:true}
function createRules(rules) {
var rs = {};
if(typeof rules === 'string') {
rules.split(/\s+/).forEach((r) => {
rs[r] = true;
});
}
return rs;
}
}
var cb1 = new Callbacks('unique');
var f1 = function() {
console.log('hello');
return false;
};
var f2 = function() {
console.log('How are you!');
return false;
};
cb1.add(f1);
cb1.add(f2);
cb1.add(f1);
cb1.fire();
在上面的例子中,Callbacks功能已经很完善了,支持了4中规则,还可以组合搭配使用。
- Callbacks在 jQuery中的实现
var cb = _.callbacks('memory');
cb.add(function(){
console.log('AAA');
});
cb.add(function(){
console.log('BBB');
return false;
});
cb.fire();
cb.add(function(){
console.log('CCC');
});
cb.add(function(){
console.log('DDD');
});
(function(root) {
var optionsCache = {};
var _ = {
callbacks: function(options){
options = typeof options === 'string' ? (optionsCache[options] || createOptions(options)) : {};
var list = [], testing, length, index, start, starts, memory;
var fire = function(data) {
memory = options.memory && data;
testing = true;
length = list.length;
index = starts || 0; // 执行下标
start = 0;
for(; index < length; index++) {
if(list[index].apply(data[0], data[1]) === false && options.stopOnFalse) {
break;
}
}
}
var self = {
add: function() {
// add可以传入一个function,也可能多个function; 将auguments转成真正的数组
var args = Array.prototype.slice.call(arguments);
start = list.length;
args.forEach((fn) => {
if(toString.call(fn) === '[object Function]') {
list.push(fn);
}
});
// 是否为memory,如果是,执行下标要从队列的最后一个函数执行
// 如果是'memory',第一次add的时候,memory为空,因为没有fire过,所以不会执行这里
if(memory) {
starts = start;
fire(memory);
}
},
fireWith: function(context, arguments) {
var agrs = [context, arguments];
if(!options.once || !testing) {
fire(agrs);
}
},
fire: function() {
self.fireWith(this, arguments);
}
};
return self;
}
}
//通过callback('once memory')创建容器时,此时obj:{once:true,memory:true}
function createOptions(options) {
var object = optionsCache[options] = {};
if(typeof options === 'string') {
options.split(/\s+/).forEach((r) => {
object[r] = true;
});
}
return object;
}
root._ = _;
})(this);
jQuery Callbacks 设计图:
可以把Callbacks理解为一个容器,或者一个队列(但队列的概念相对比较复杂),理解为容器更为简单和贴切。
Callbacks:并不是给用户专门使用的,而是对Deferred对象提供一种支持。
和ES6的Promise很像,设计灵感来自Deferred对象。