jQuery 源码剖析 - 3.Callbacks 原理分析及实现
一、Callbacks 基本概念
1.$.callbacks 用于管理函数队列。
2.通过 add 添加处理函数到队列中,通过 fire 去执行这些函数。
3.$.callbacks 是在 jQuery 内部使用的,如为 .ajax
、 $.Deffed
等组件提供基础功能函数。它也可以在类似功能的一些组件中,如自己开发的插件
二、Callbacks API
- 1.$.callbacks 获取实例
- 2.add 向内部队列添加函数
- 3.fire 依次找到并执行队列里的函数
var cb = $.callbacks();
cb.add(function(){
console.log('add one');
});
cb.add(function(){
console.log('add two');
});
cb.add(function(){
console.log('add three');
});
cb.fire()
//依次输出
add one
add two
add three
三、Callbacks 参数的特定功能
callbacks 通过字符串参数的形式,支持四种特定的功能
1.once 函数队列只执行一次
// once关键字
var cbOne = $.Callbacks('once');
cbOne.add(function(){
console.log("this is cbOne1");
});
cbOne.add(function(){
console.log("this is a cbOne2");
});
// 只输出执行一次,后面调用都不生效
cbOne.fire();
cbOne.fire();
执行结果:
this is cbOne1
this is a cbOne2
2.unique 往内部队列添加的函数保持唯一,不能重复添加
// unique
var cbUnique = $.Callbacks('unique');
function demo(){
console.log("this is a cbUnique");
}
cbUnique.add(demo,demo);
cbUnique.fire();
// 输出了一次
this is a cbUnique
3.stopOnFalse 内部队列里的函数是依次执行的,当某个函数的返回值是 false 时,停止继续执行剩下的函数。
// stopOnFalse 关键字
// 不加关键字的情况
var cbDemo = $.Callbacks();
cbDemo.add(function(){
console.log("this is cbDemo 1");
return false;
},function(){
console.log("this is cbDemo 2");
})
cbDemo.fire();
输出:
this is cbDemo 1
this is cbDemo 2
// 加关键字的情况
var cbStopOnFalse = $.Callbacks('stopOnFalse');
cbStopOnFalse.add(function(){
console.log("this is a cbStopOnFalse 1");
return false;
},function(){
console.log("this is a cbStopOnFalse 2");
});
cbStopOnFalse.fire();
输出:
this is a cbStopOnFalse 1
4.memory 当参数队列 fire 一次过后,内部会记录当前 fire 的参数。当下次调用 add 的时候,会把记录的参数传递给新添加的函数并立即执行这个新添加的函数。
// 参数 memory
// 不加参数的情况
var cbNoMemory = $.Callbacks();
cbNoMemory.add(function(){
console.log("this is a cbNoMemory 1");
});
cbNoMemory.fire();
输出:
this is a cbNoMemory 1
cbNoMemory.add(function (){
console.log("this is a cbNoMemory 2");
});
// 添加参数的情况
var cbMemory = $.Callbacks('memory');
cbMemory.add(function(){
console.log("this is a cbMemory 1");
});
cbMemory.fire();
输出:
this is a cbMemory 1
this is a cbMemory 2
cbMemory.add(function(){
console.log("this is a cbMemory 2");
})
四、从事件函数了解 Callbacks
1.事件通常与函数配合使用,这样就可以通过发生的事件来驱动函数的执行。
原则:一个事件对应一个事件函数,在一个事件对应多个函数的情况下,后者会覆盖掉前者。
问题:那么我们能否有一种方案来改变一对一的事件模型呢?
解决方案:把事件放到一个数组里,然后通过遍历数组依次执行的方式来达到一对多的事件模型。
// 一对多事件模型
function one(){
console.log("one");
};
function two(){
console.log("two");
};
function three(){
console.log("three");
};
function four(){
console.log("four");
};
var clickCallBack = [one,two,three,four];
// 在body中定义一个button <button>按钮</button>
$("#btn").click(function(){
var _this = this;
clickCallBack.forEach(function(fn){
fn.call(_this);
})
});
// 输出结果:
one
three
three
four
2.Callbacks 不仅仅是一个数组,可以把它看成一个容器。
五、开始剖析
上面我们已经通过 jQuery 来调用 Callbacks 的API并输出了内容,根据两个方法 add()、fire() 及四个参数 once、unique、menory、stopOnfalse,的相关特性我们开始反推实现过程。
首先是 add() 方法:将穿过来的 options 先把他们转为真数组,然后将数组遍历出来挑选出类型为 Function 的数据,将数据添加到一个空数组中,等待执行。
核心代码片段:
add:function(){
// Array.prototype.slice.call(arguments 伪数组转真数组
var args = Array.prototype.slice.call(arguments);
start = list.length;
// 遍历args 找出里面的Function
args.forEach(function(fn){
// 检索fn是是否是Function
if (toString.call(fn) === "[object Function]") {
// unique 不存在 且fn在list中 那么可以把fn添加到队里中
list.push(fn);
}
}
});
fire() 方法:fire 其实就是把添加到队列中的方法依次按规则输出执行,需要一个中间件 fireWith 提供上下文。
核心代码:
var fire = function(data){
index = 0;
length = list.length;
// 遍历循环list
for(; index < length; index++){
// 通过遍历查找list[index]的值为false 且options有stopOnfalse这个参数时遍历终止返回
if (list[index].apply(data[0],data[1]) == false){
break;
}
}
}
// 定义一个上下文绑定函数
fileWith:function(context,arguments){
var args = [context,arguments];
fire(args);
}
fire:function(){
self.fileWith(this,arguments);
},
到此以上代码可以实现 add() 方法和 fire() 方法,其次我们在考虑四种参数的情况,首先我们先考虑 stopOnfalse 的情况。
stopOnfalse 这个参数生效的阶段是在调用 fire() 方法后执行 add() 添加的队列函数中是否有返回 false 的情况,所以首先我们应该想到在 fire() 这个方法里做文章。
思路:直接在遍历方法的时候来判定 options 是否有 stopOnfalse 参数如果有立马退出。
核心代码:
var fire = function(data){
// memory
memory = options.memory && data;
// 为了防止memory再次调用一次定义了starts
index = 0;
length = list.length;
// 遍历循环list
for(; index < length; index++){
// 通过遍历查找list[index]的值为false 且options有stopOnfalse这个参数时遍历终止返回
if (list[index].apply(data[0],data[1]) == false && options.stopOnfalse){
break;
}
}
once 参数生效的情况是,当 once 存在执行第一次完成后,如果还有 fire() 方法,那么就直接退出不执行。
思路:首先明白受影响阶段是 fire(),定义一个参数来记录第一次执行 fire() 的方法,然后在调用执行 fire() 这个方法判断是否传入有 once 参数如果有,那么就不会再去执行 fire() 方法。
核心代码:
var fire = function(data){
index = 0;
length = list.length;
startAdd = true;// 用来记录fire()方式是否执行 便于"once"方法操作
// 遍历循环list
for(; index < length; index++){
// 通过遍历查找list[index]的值为false 且options有stopOnfalse这个参数时遍历终止返回
if (list[index].apply(data[0],data[1]) == false && options.stopOnfalse){
break;
}
}
}
// 定义一个上下文绑定函数
fileWith:function(context,arguments){
var args = [context,arguments];
// 非fire做限制调用
if(!options.once || !startAdd) {
fire(args);
}
},
memory 这个参数生效的情况是,如果执行 fire() 方法后,还存在 add() 的方法,那么后面的 add() 方法依然有效。
思路:首先要搞明白 memory 在哪个阶段会受影响,在 add() 阶段和 fire() 阶段都有影响,add() 阶段要记录传入的 options 是否有 memory 这个参数,其次在执行 fire() 的阶段,主要是要记录住它的 index 值。
核心代码:
var fire = function(data){
// memory
memory = options.memory && data;
// 为了防止memory再次调用一次定义了memoryStarts
index = memoryStarts || 0;
start = 0;
length = list.length;
startAdd = true; // 用来记录fire()方式是否执行 便于"once"方法操作
// 遍历循环list
for(; index < length; index++){
// 通过遍历查找list[index]的值为false 且options有stopOnfalse这个参数时遍历终止返回
if (list[index].apply(data[0],data[1]) == false && options.stopOnfalse){
break;
}
}
}
// memory
if (memory) {
memoryStarts = start;
fire(memory);
}
最难的是 unique 这个参数。
unique 这个参数生效的情况是,同一个方法被 add() 多次,仅执行一次改方法。
思路:unique 影响阶段是 add() 时候,所有我们在这里做拦截操作是最好的,因此我们在 add() 的时候做判断如果存在 unique 这个参数,那么我们就不让同样的这个方法 push 到队里中,没有添加到队列,那么我们就不会再次执行这个方法啦。
两种方法:
- 通过数组的
[].indexOf.call()
来查看是否存在于数组中,不存在返回-1
。 - 可以用 ES6 的 set 进行过滤重复值
我们采用方法一来完成此操作。
核心代码:
// 添加 方法
add:function(){
// Array.prototype.slice.call(arguments 伪数组转真数组
var args = Array.prototype.slice.call(arguments);
start = list.length;
// 遍历args 找出里面的Function
args.forEach(function(fn){
// 检索fn是是否是Function
if (toString.call(fn) === "[object Function]") {
// unique 不存在 且fn在list中 那么可以把fn添加到队里中
// 处理 unique 参数
if(!options.unique || !self.has(fn,list)) {
list.push(fn);
}
}
});
has:function(fn,array){
return arr = jQuery.inArray(fn,array) > -1;
}
jQuery.inArray = function (elem,arr){
return arr == null?-1:[].indexOf.call(arr,elem);
}
至此,大功告成,完成了 Callbacks() 实现原理剖析,你学会了吗?
源码下载:https://www.wenjiangs.com/wp-content/uploads/2023/01/4h6udrjw8uS4f1WJ.zip
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论