callbacks实现分析
1: callbacks实现的思想来源: 源于js中绑定事件
document.onclick(function a(){})
document.onclick(function b(){})
当触发点击事件后,a,b两个函数可以同时执行,说明原生在实现的过程中,已经对a,b两个函数做了统一管理
2: 使用场景
function a(){}
(function(){
function b(){}
})()
a()
b() // 对于b函数因为无法获取,所以无法调用
function a(){}
function b(){}
通过callbacks统一管理后,想触发a,b两个函数,就只需要通过cb.fire()调用一次,不需要分别调用a,b两个函数
3: 架构设计
经常有大佬前辈跟我说,当接到一个需求,先不要着急写,先想清楚怎么实现,如何实现,注意什么,有没有更好的办法,能不能抽离,拿笔画一画,写一写
如果抛开jquery的实现,让我们自己去实现,你会怎么设计,能否考虑全面,代码是否精简,思路是否清晰
a: 当调用callbacks函数时,需要返回一个什么,应该是一个对象,这样通过对象.的形式调用add, fire等方法,在每个方法中再次返回this,就可以实现链式调用
b: 针对过程,当操作一个函数(add, remove)等,如果是相同的函数是否允许重复添加,由此引发一个变量概念,从而控制能否添加相同的函数,命名为unique
c: 针对结果,当触发这些函数(fire,firewith)等,当多次fire的时候,是否可以重复触发所有函数,因此引入一个变量,控制是否多次触发fire 命名为once
d: 针对函数自身,如果函数返回false,是否可以控制后续的函数都不再执行了,因此引入一个变量,stoponfalse
e: (这个是最难想到的,反正我是想不到这个需求)当fire之后,希望下次add的时候,会自动触发,引入一个memory变量
f: 当拥有这么多变量后,就应该想到,这些变量之间是否能同时使用,是否有优先级的关系,比如once与memory,逻辑上有两种设计方向,
f1:once优先级高于memory,当有了once,一切都是无效的,那么add调用的时候,就要调用cb.fire,就不能直接调用单体fire函数了
f2: once跟memory无关,那么同时生效
总结: 从jquery的代码中看出来,once控制的是cb.fire(),也就是针对的这种情况的fire,memory中调用add,实际触发的是fire单体函数
![]()
4: add方法实现:当实现一个函数,需要注意哪些细节,比如:参数设计,返回值设计,是否有一些全局变量需要判断等
a: 参数设计 cb.add(f1), cb.add(f1,f2), cb.add([f1,f2]) cb.add({0:f1, 1:f2, length:2}) 需要支持所有格式
a1: cb.add(f1) 此时arg = {0: f1, length:1}, each遍历,value = f1,判断type如果是函数,文章开始分析过unique作用,控制add是否可以添加相同函数,这里进入一个误区,只是判断当
!options.unique = false,所以就不会push操作,却忽视第一次是可以push的,所以仅仅靠一个变量是不准的,这个变量的作用,通过没有传unique时,!options.unique = true,执行push操作,当传入unique时,取反后为false,通过has方法判断,list中是否拥有该函数,如果有返回true,就不会执行push操作
a2: cb.add([f1,f2]) ,如果传入数组,arg = {0: [f1, f2], length:1}, each遍历,value = [f1, f2],然后执行递归操作,此时生成新的函数作用域,当这个函数作用域结束后,然后回退到上一个函数作用域的each方法中,继续向下走
a3: if(memory)判断,执行时机必须要先cb.fire()一次,然后对memoy进行赋值,此时在add时,调用fire方法(注意: 并没有通过cb.fire的形式调用,而是直接调用fire单体函数,原因就是cb.fire()会被once控制,memory设计之初是为了不受once影响),firingstart = start设计很有意思,当cb.add(f2),虽然有memory,但是理论上需要的场景是只触发后添加进来的函数就可以,以前触发过的不需要再触发,每次进入add后,就先记录上一次list长度
a4: if(firing)判断,当执行fire的时候,如果此时配置了memory,更好的办法可以直接把add的函数添加到本次state数组中,然后一次性执行完,就不需要memory了,所以firing 与 memory是一个if else关系,因为firing中的add,希望还是本次中执行,所以直接修改list长度,list长度不是动态获取的,所以要修正
function f1(){
cb.add(f2)
}
function f2(){}
cb.fire()
a5: 关于list为什么要做if判断,此处先不说,其实是为了disabled时,丧失所有cb的操作能力
![]()
5: remove方法 相对比较简单,jquery中并没有做递归操作,所以只能支持 cb.remove(f1) 不支持cb.remove([f1]), 让我想到一件事,代码服务于业务,在真实的使用过程中,是不是添加的时候会多个添加,删除的时候,提供单独删除的功能就够了呢
a1: list的判断,是因为有个disable方法,会把list至空,使cb丧失所有操作于触发能力
a2: each遍历时,当删除一个函数前,首先要判断该函数是否在list中,如果存在,得到数组中的位置,通过位置与-1比较,splice(起始位置,删除几个),变异方法
a3: 如果处在firing中,list=[f1,f2],如果删除的是未调用的f2,此时index = 1,firinglength = 2,(第一次for循环,firingindex = 0, firinglength = 2,0 < 2,然后执行f1函数,当remove后,修改firinglength = 1,然后firingindex++ = 1, 然后跳出for循环了)
function f1(a){
console.log('f1 ' + a)
cb1.remove(f2)
}
function f2(a){
console.log('f2 ' + a)
}
function f3(a){
console.log('f3 ' + a)
}
cb1.add(f1,f2,f3)
cb1.fire()
总结: 如果删除的还没有调用的函数,首先index < firinglength 所以firinglent-- = 2; 因为list被变异方法splice修改了,所以在for循环时,list[firingindex]的时候会自动获取f3这个函数,此时firingindex不需要修正
a4: 如果处在firing中,list=[f1,f2], 如果删除的是调用过的f1,此时index = 0,firinglength = 2, (第一次for循环,当remove后,调用splice后,list=[f2], 此时0 < 2,firinglength = 1, 因为list现在只有一个函数,所以长度是1是对的,但是剩下的函数f2还是要执行的,所以可以手动修改firingindex值,为了继续触发f2函数)
function f1(a){
console.log('f1 ' + a)
cb1.remove(f1)
}
function f2(a){
console.log('f2 ' + a)
}
cb1.add(f1,f2)
cb1.fire()
总结: 删除的是调用的函数,index < firinglength 所以length = 1,因为删除了第一项,所有firingindex应该从0开始,目前firingindex就是0,但是下一次要执行firingindex++了,所以要firingindex--
a5: 如果处在firing中,list=[f1,f2],当执行第二个函数,触发remove(f1),index = 0, 0 <= 2 然后 firinglength = 1,if(0 <= 1) firingindex = 0;此时加1后,不满足条件退出
function f1(a){
console.log('f1 ' + a)
}
function f2(a){
console.log('f2 ' + a)
cb1.remove(f1)
}
cb1.add(f1,f2)
cb1.fire()
![]()
6: has方法,看代码很简单,但是真的能读懂作者真正想表达的业务需求么,通过有没有参数实现不同的业务需求
a1: 如果有参数,返回一个布尔值,用来判断结果
a2: 如果没有参数,可以用来判断list当中是否拥有至少一个函数
![]()
7: empty方法,正常使用是为了清除list数组,如果非正常使用,比如进行时,也是为了清除list数组
a1: 正常使用 empty让list清空就可以了
function f1(a){
console.log('f1 ' + a)
}
function f2(a){
console.log('f2 ' + a)
}
cb1.add(f1,f2)
cb1.fire()
cb1.empty()
a2: 非正常使用,当第一次执行for循环,firingindex = 0,firinglenth = 2,执行f1函数后,调用empty至空,下一次循环firingindex=1 此时list[firinginindex]会报错,最粗暴的方式就是修改firinglenth = 0,这样就不会进入for循环了,因为list为空数组了
function f1(a){
console.log('f1 ' + a)
}
function f2(a){
console.log('f2 ' + a)
}
cb1.add(f1,f2)
cb1.fire()
8: disable方法不容易理解:原意have this list do nothing anymore,我更倾向理解成使得cb丧失所有能力,包括,过程式(add, remove),结果时,cb.fire()等 a1: 当disable()正常调用,让list = undefined,remove失效,add失效,fire单体函数失效,因为for循环并上list
let cb1 = jQuery.Callbacks()
function f1(a){
console.log('f1 ' + a)
}
function f2(a){
console.log('f2 ' + a)
}
cb1.add(f1,f2)
cb1.disable();
cb1.fire()
a2: 非正常调用,调用disable后,list清空,导致后续循环失效了,至于为什么要把stack = memory = undefind,个人理解是复位,即使单纯通过list可以达到效果,但是还是片面,对与整体来说,如果memory有值,应该要清掉,达到恢复初始值思想
let cb1 = jQuery.Callbacks()
function f1(a){
console.log('f1 ' + a)
cb1.disable();
}
function f2(a){
console.log('f2 ' + a)
}
cb1.add(f1,f2)
cb1.fire()
a3: 需要一个表明当前cb的状态,disabled方法出现了
9: lock锁住,有很多情况,一一分析,原意Lock the list in its current state,就想问问你们,能真正意会作者的含义么???,我其实反反复复琢磨了很久,才领悟到真正的含义,两个关键词 list, state,(概念不能混淆,上一条语句,跟当前状态表达的是同一个含义,上一条语句执行结果就是当前的状态,不能说成上一个状态),比如上一次是add操作,那么当lock之后,锁住add的状态,之后不能add,或者remove,或者fire了 a1: lock后,修改stack=undefind,如果不传memory,通过调用disable函数,使得list = undefined,后续的add无效,remove也无效 lock后, 传入memory,
let cb1 = jQuery.Callbacks()
function f1(a){
console.log('f1 ' + a)
}
function f2(a){
console.log('f2 ' + a)
}
cb1.add(f1,f2)
cb1.lock() // 此时锁住的是上一条语句执行完的状态,是add这个状态
cb1.fire()
a2: lock后,如果不传入memory,调用diasble函数,导致后续的fire无效
let cb1 = jQuery.Callbacks()
function f1(a){
console.log('f1 ' + a)
}
function f2(a){
console.log('f2 ' + a)
}
cb1.add(f1,f2)
cb1.fire()
cb1.lock() // 锁住上一条语句的状态,是fire状态
cb1.fire()
a3: stack = undefined,目的是只要调用了lock,firewith这个方法肯定不能执行,至于其它方法受memory控制,如果有memory,后续的add是锁不住的
let cb1 = jQuery.Callbacks('memory')
function f1(a){
console.log('f1 ' + a)
}
function f2(a){
console.log('f2 ' + a)
}
cb1.add(f1)
cb1.fire()
cb1.lock() // 上一次的语句是fire,状态是fired状态
cb1.add(f2)
a4: 当触发lock的时候,首先把firewith锁住,因为memory,导致add的时候会继续触发函数f2
let cb1 = jQuery.Callbacks('memory')
function f1(a){
console.log('f1 ' + a)
cb1.lock() //
}
function f2(a){
console.log('f2 ' + a)
}
cb1.add(f1)
cb1.fire()
cb1.add(f2)、
总结: 当有memory的时候,使用者更多的还是希望不要锁住,因为memory可以实现add的再次触发