D005+技术|jQuery-$.Callbacks()实现原理
$.Callbacks用于管理函数队列,通过add添加处理函数到队列中,通过fire去执行这些处理函数。
本节向大家介绍$.Callbacks的实现的原理,并简单实现一个自己的callbacks。
概念解读
从事件函数了解Callbacks,事件通常与函数配合使用,这样就可以通过触发事件来驱动函数的执行。
原则上,一个事件对应一个事件函数。在一个事件对应多个事件函数的情况下,后者会覆盖前者。
ele.onclick = function(){
console.log("code")
}
ele.onclick = function(){
console.log("code1")
}
上边这个Demo中后面绑定的这个事件函数会覆盖前边的,事件触发时会打印"code1"。
事件驱动改造
如果想让触发事件时执行多个函数,是否可行呢?
当然可以,我们可以把需要执行的多个函数放在一个数组里,事件触发时循环执行这个数组里的函数。
下面看一下伪代码:
var callbacks = [function a(){}, function b(){}, function c(){}];
ele.onclick = function(){
var _this = this;
callbacks.forEach(function(fn){
fn.call(_this);
});
}
而Callbacks并不仅仅是一个数组,而是一个容器。
$.Callbacks API的使用
基础应用
// 1. $.Callbacks()返回Callbacks的实例对象
var cb = $.Callbacks();
// 2. 方法add()向内部队列添加函数
cb.add(() => {
console.log(1)
});
// 3. 方法fire()传入参数执行队列里的函数
cb.fire();
// 控制台结果:1
Callbacks参数
$.Callbacks()通过字符串参数的形式,支持4种特定的功能:once
,unique
,stopOnFalse
,memory
- once 函数队列只执行一次
// 不添加参数
var cb = $.Callbacks();
cb.add(() => {
console.log(1)
});
cb.fire(); // 控制台打印: 1
cb.fire(); // 控制台打印: 1
如果不指定参数,调用fire()方法两次,控制台将打印两次1
// 添加参数
var cb = $.Callbacks("once");
cb.add(() => {
console.log(1)
});
cb.fire(); // 控制台打印: 1
cb.fire();
如果不指定参数为字符串once
,调用fire()方法两次,控制台只打印一次1,需要注意:这里的字符串是区分大小写
- unique 使添加到内部函数队列里的函数保持唯一,不能重复添加
// 不加参数
var cb = $.Callbacks();
var test = function () {
console.log("unique test");
};
cb.add(test, test);
cb.fire();
// 控制台打印:
// unique test
// unique test
我们通过add()方法往函数队列里添加了两个test
函数,调用fire()方法,控制台打印了两次“unique test”。
// 不加参数
var cb = $.Callbacks(“unique”);
var test = function () {
console.log("unique test");
};
cb.add(test, test);
cb.fire(); // 控制台打印: unique test
指定参数unique
,即使我们往函数队列里添加了两个test
函数,调用fire()方法,控制台也只会打印一次“unique test”。
- stopOnFalse 内部函数队列顺序依次执行,当某个函数返回值为false时,停止该函数后边的函数继续执行
// 不添加参数
var cb = $.Callbacks();
var test1 = function () {
console.log("stopOnFalse one");
};
var test2 = function () {
console.log("stopOnFalse two");
return false;
};
var test3 = function () {
console.log("stopOnFalse three");
};
cb.add(test1, test2, test3);
cb.fire()
// 控制台打印:
// stopOnFalse one
// stopOnFalse two
// stopOnFalse three
不添加参数时函数队列依次执行
// 添加参数
var cb = $.Callbacks("stopOnFalse");
var test1 = function () {
console.log("stopOnFalse one");
};
var test2 = function () {
console.log("stopOnFalse two");
return false;
};
var test3 = function () {
console.log("stopOnFalse three");
};
cb.add(test1, test2, test3);
cb.fire()
// 控制台打印:
// stopOnFalse one
// stopOnFalse two
指定参数为stopOnFalse
,当函数执行到test2时,因为该函数返回了false,所以后边的test3将不再执行
- memory 当函数队列fire一次过后,内部会记录当前fire方法的参数。当下次调用add时,会把记录的参数传递给新添加的函数并立即执行这个新添加的函数。
// 不添加参数
var cb = $.Callbacks();
var test1 = function () {
console.log("memory one");
};
var test2 = function () {
console.log("memory two");
};
cb.add(test1);
cb.fire(); // 控制台打印:memory one
cb.add(test2);
// 添加参数
var cb = $.Callbacks("memory");
var test1 = function () {
console.log("memory one");
};
var test2 = function () {
console.log("memory two");
};
cb.add(test1);
cb.fire(); // 控制台打印:memory one memory two
cb.add(test2);
$.Callbacks 实现
参考jQuery的Callbacks,我们自己来实现一下。
基本结构
(function (root) {
var _ = {
callbacks: function () {
console.log("test");
}
}
root._ = _;
})(this);
_.callbacks(); // test
通过一个闭包把执行上下文传入,因为在浏览器里执行这里传入的是Window对象,把_
做为root的属性,这样我们在全局就可以访问_
。
(function (root) {
var optionsCache = {};
var _ = {
callbacks: function (options) {
// 判断传入的参数类型
// 字符串:存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
console.log(options); // {"once":true,"memory":true}
console.log(optionsCache) // {"once":true,"memory":true}
}
}
root._ = _;
var createOptions = function (options) {
var object = {};
// 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
options.split(/\s+/).forEach(value => {
object[value] = optionsCache[value] = true;
});
return object;
}
})(this);
_.callbacks("once memory");
上边这个函数支持获取用户传过来的参数,并且支持用户同时指定多个类型的参数,并把这些参数存储在optionsCache
缓存对象中。接下来看一下add()
,fire()
方法的实现。
(function (root) {
var optionsCache = {};
var _ = {
callbacks: function (options) {
// 判断传入的参数类型
// 字符串:存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
var self = {
add: function () {
console.log("add");
},
fire: function () {
console.log("fire");
}
}
return self;
}
}
root._ = _;
var createOptions = function (options) {
var object = {};
// 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
options.split(/\s+/).forEach(value => {
object[value] = optionsCache[value] = true;
});
return object;
}
})(this);
var cb = _.callbacks();
cb.add(); // add
cb.fire(); // fire
在callbacks函数中创建self对象,并添加add方法和fire方法,然后把这个self返回,这样调用callbacks函数后就可以获取这个self。参考上面的代码。下一步,我们来实现一下add方法和fire方法。
add方法和fire方法的实现
(function (root) {
var optionsCache = {};
var _ = {
callbacks: function (options) {
// 判断传入的参数类型是否为字符串
// 存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
var fnList = [];
var index, length;
var fire = function (data) {
index = 0;
length = fnList.length;
for (; index < length; index++) {
fnList[index].apply(data[0], data[1]);
}
}
var self = {
add: function () {
// 将传入的参数转成数组
var argArr = Array.prototype.slice.call(arguments);
argArr.forEach((fn) => {
// 判断传入的参数是否为function
if (toString.call(fn) === "[object Function]") {
fnList.push(fn);
}
})
},
fireWith: function (context, arguments) {
var args = [context, arguments];
fire(args);
},
fire: function () {
self.fireWith(this, arguments);
}
}
return self;
}
}
root._ = _;
var createOptions = function (options) {
var object = {};
// 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
options.split(/\s+/).forEach(value => {
object[value] = optionsCache[value] = true;
});
return object;
}
})(this);
var cb = _.callbacks();
cb.add(function a(params) {
console.log("a");
}, function b(params) {
console.log("b");
});
cb.fire(); // a b
add()可以往函数队列里添加函数,fire()可以依次执行队列的函数。上面的代码fire()是顺序执行队列,接下来实现通过指定callbacks的参数来控制函数队列的执行。
参数stopOnFalse功能
(function (root) {
var optionsCache = {};
var _ = {
callbacks: function (options) {
// 判断传入的参数类型是否为字符串
// 存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
var fnList = [];
var index, length;
var fire = function (data) {
index = 0;
length = fnList.length;
for (; index < length; index++) {
// 判断函数执行结果是否为false并且设置了stopOnFalse
// data[0]:执行上下文
// data[1]:执行时传入的参数
if (!(fnList[index].apply(data[0], data[1])) && options['stopOnFalse']) {
break;
}
}
}
var self = {
add: function () {
// 将传入的参数转成数组
var argArr = Array.prototype.slice.call(arguments);
argArr.forEach((fn) => {
// 判断传入的参数是否为function
if (toString.call(fn) === "[object Function]") {
fnList.push(fn);
}
})
},
fireWith: function (context, arguments) {
var args = [context, arguments];
fire(args);
},
fire: function () {
self.fireWith(this, arguments);
}
}
return self;
}
}
root._ = _;
var createOptions = function (options) {
var object = {};
// 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
options.split(/\s+/).forEach(value => {
object[value] = optionsCache[value] = true;
});
return object;
}
})(this);
// 不指定参数
var cb = _.callbacks();
cb.add(function a(params) {
console.log("a");
return false;
}, function b(params) {
console.log("b");
});
cb.fire(); // a b
// 指定参数为stopOnFalse
var cb = _.callbacks("stopOnFalse");
cb.add(function a(params) {
console.log("a");
return false;
}, function b(params) {
console.log("b");
});
cb.fire(); // a
上面代码实现了calLbacks指定参数为stopOnFalse时的效果。
参数once功能
(function (root) {
var optionsCache = {};
var _ = {
callbacks: function (options) {
// 判断传入的参数类型是否为字符串
// 存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
var fnList = [];
var index, length, isFire;
var fire = function (data) {
index = 0;
length = fnList.length;
for (; index < length; index++) {
// 判断函数执行结果是否为false并且设置了stopOnFalse
// data[0]:执行上下文
// data[1]:执行时传入的参数
if (!(fnList[index].apply(data[0], data[1])) && options['stopOnFalse']) {
break;
}
}
isFire = true;
}
var self = {
add: function () {
// 将传入的参数转成数组
var argArr = Array.prototype.slice.call(arguments);
argArr.forEach((fn) => {
// 判断传入的参数是否为function
if (toString.call(fn) === "[object Function]") {
fnList.push(fn);
}
})
},
fireWith: function (context, arguments) {
var args = [context, arguments];
if (!(options["once"] && isFire)) {
fire(args);
}
},
fire: function () {
self.fireWith(this, arguments);
}
}
return self;
}
}
root._ = _;
var createOptions = function (options) {
var object = {};
// 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
options.split(/\s+/).forEach(value => {
object[value] = optionsCache[value] = true;
});
return object;
}
})(this);
// 不添加参数
var cb = _.callbacks();
cb.add(function a(params) {
console.log("a");
return false;
}, function b(params) {
console.log("b");
});
cb.fire(); // ab
cb.fire(); // ab
// 添加参数once
var cb = _.callbacks("once");
cb.add(function a(params) {
console.log("a");
return false;
}, function b(params) {
console.log("b");
});
cb.fire(); // ab
cb.fire();
上面的代码实现了当设置参数为once
时,fire调用多次,只会执行1次。
参数memory功能
(function (root) {
var optionsCache = {};
var _ = {
callbacks: function (options) {
// 判断传入的参数类型是否为字符串
// 存储在optionsCache对象里,如果该对象已经存在这个属性直接使用,如果不存在则创建这个属性值为true
options = typeof options === "string" ? (optionsCache[options] || createOptions(options)) : {};
var fnList = [];
var index, length, isFire, memory, start;
var fire = function (data) {
// 如果设置"memory",则记录当前传入的参数
memory = options["memory"] && data;
index = start || 0;
start = 0
length = fnList.length;
for (; index < length; index++) {
// 判断函数执行结果是否为false并且设置了stopOnFalse
// data[0]:执行上下文
// data[1]:执行时传入的参数
if (fnList[index].apply(data[0], data[1]) === false && options['stopOnFalse']) {
break;
}
}
isFire = true;
}
var self = {
add: function () {
// 将传入的参数转成数组
var argArr = Array.prototype.slice.call(arguments);
argArr.forEach((fn) => {
// 判断传入的参数是否为function
if (toString.call(fn) === "[object Function]") {
fnList.push(fn);
}
})
// 如果设置了memory参数,并且参数存在,则调用
if (memory) {
// start为次一次执行时的顺序
start = fnList.length - 1;
fire(memory)
}
},
fireWith: function (context, arguments) {
var args = [context, arguments];
if (!(options["once"] && isFire)) {
fire(args);
}
},
fire: function () {
self.fireWith(this, arguments);
}
}
return self;
}
}
root._ = _;
var createOptions = function (options) {
var object = {};
// 如果同时指定两种类型,传入的参数为“once memory”,需拆解成单独的字符串
options.split(/\s+/).forEach(value => {
object[value] = optionsCache[value] = true;
});
return object;
}
})(this);
var cb = _.callbacks("memory");
cb.add(function a(params) {
console.log("a");
return false;
});
cb.add(function c(params) {
console.log("c");
}, function d(params) {
console.log("d");
})
cb.fire(); // a c d b
cb.add(function b(params) {
console.log("b");
})
上面代码实现了memory
的功能。