读Zepto源码之Callbacks模块
2017-07-24 10:34
302 查看
Callbacks 模块并不是必备的模块,其作用是管理回调函数,为 Defferred 模块提供支持,Defferred 模块又为 Ajax 模块的
Ajax模块,在此之前,先看 Callbacks 模块和 Defferred 模块的实现。
本文阅读的源码为 zepto1.2.0
将 Callbacks 模块的代码精简后,得到的结构如下:
其实就是向
构造函数的配置,默认为空对象
回调函数列表
列表可以重复触发时,用来缓存触发过程中未执行的任务参数,如果列表只能触发一次,
记忆模式下,会记住上一次触发的上下文及参数
回调函数列表已经触发过
回调函数列表正在触发
回调任务的开始位置
当前回调任务的索引
我用
上面的例子只是简单的调用,也有了注释,下面开始分析
如果
将
将当前回调任务的索引值
将回调列表的开始位置
将回调任务的长度
将回调的开始状态
执行回调的整体逻辑是遍历回调列表,逐个执行回调。
循环的条件是,列表存在,并且当前回调任务的索引值
如果回调执行后显式返回
并且
回调任务执行完毕后,将
列表任务执行完毕后,先检查
其他情况直接调用
用
在下一个判断中,如果
如果
调用
如果回调任务正在执行中,则修正回调任务的长度
否则,如果为
删除列表中指定的回调。
用
然后调用
如果回调任务正在执行中,因为回调列表的长度已经有了变化,需要修正回调任务的控制参数。
如果
如果
这样做是防止回调函数执行到最后时,没有找到对应的任务执行。
以指定回调函数的上下文的方式来触发回调函数。
先将
再重新组合成新的变量
如果回调正处在触发的状态,则将上下文对象和参数先储存在
否则,触发
这个三元表达式前面的是判断指定的
回调是否已经被禁止,其实就是检测
锁定回调列表,其实是禁止
其实是将
为什么
所以,
回调列表是否被锁定。
其实就是检测
回调列表是否已经被触发过。
回调列表触发一次后
Callbacks 模块并不是必备的模块,其作用是管理回调函数,为 Defferred 模块提供支持,Defferred 模块又为 Ajax 模块的
promise风格提供支持,接下来很快就会分析到
Ajax模块,在此之前,先看 Callbacks 模块和 Defferred 模块的实现。
源码版本
本文阅读的源码为 zepto1.2.0
整体结构
将 Callbacks 模块的代码精简后,得到的结构如下:;(function($){ $.Callbacks = function(options) { ... Callbacks = { ... } return Callbacks }})(Zepto)
其实就是向
zepto对象上,添加了一个
Callbacks函数,这个是一个工厂函数,调用这个函数返回的是一个对象,对象内部包含了一系列的方法。
options参数为一个对象,在源码的内部,作者已经注释了各个键值的含义。
// Option flags: // - once: Callbacks fired at most one time. // - memory: Remember the most recent context and arguments // - stopOnFalse: Cease iterating over callback list // - unique: Permit adding at most one instance of the same callbackonce: 回调至多只能触发一次 memory: 记下最近一次触发的上下文及参数列表,再添加新回调的时候都立刻用这个上下文及参数立即执行 stopOnFalse: 如果队列中有回调返回 `false`,立即中止后续回调的执行 unique: 同一个回调只能添加一次
全局变量
options = $.extend({}, options)var memory, // Last fire value (for non-forgettable lists) fired, // Flag to know if list was already fired firing, // Flag to know if list is currently firing firingStart, // First callback to fire (used internally by add and fireWith) firingLength, // End of the loop when firing firingIndex, // Index of currently firing callback (modified by remove if needed) list = [], // Actual callback list stack = !options.once && [], // Stack of fire calls for repeatable lists
options:
构造函数的配置,默认为空对象
list:
回调函数列表
stack:
列表可以重复触发时,用来缓存触发过程中未执行的任务参数,如果列表只能触发一次,
stack永远为
false
memory:
记忆模式下,会记住上一次触发的上下文及参数
fired:
回调函数列表已经触发过
firing:
回调函数列表正在触发
firingStart:
回调任务的开始位置
firingIndex:
当前回调任务的索引
firingLength:回调任务的长度
基础用法
我用 jQuery和
Zepto的时间比较短,之前也没有直接用过
Callbacks模块,单纯看代码不易理解它是怎样工作的,在分析之前,先看一下简单的
API调用,可能会有助于理解。
var callbacks = $.Callbacks({memory: true})var a = function(a) { console.log('a ' + a)}var b = function(b) { console.log('b ' + b)}var c = function(c) { console.log('c ' + c)}callbacks.add(a).add(b).add(c) // 向队列 list 中添加了三个回调callbacks.remove(c) // 删除 ccallbacks.fire('fire') // 到这步输出了 `a fire` `b fire` 没有输出 `c fire`callbacks.lock()callbacks.fire('fire after lock') // 到这步没有任何输出// 继续向队列添加回调,注意 `Callbacks` 的参数为 `memory: true`callbacks.add(function(d) { console.log('after lock')})// 输出 `after lock`callbacks.disable()callbacks.add(function(e) { console.log('after disable')}) // 没有任何输出
上面的例子只是简单的调用,也有了注释,下面开始分析
API
内部方法
fire
fire = function(data) { memory = options.memory && data fired = true firingIndex = firingStart || 0 firingStart = 0 firingLength = list.length firing = true for ( ; list && firingIndex < firingLength ; ++firingIndex ) { if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) { memory = false break } } firing = false if (list) { if (stack) stack.length && fire(stack.shift()) else if (memory) list.length = 0 else Callbacks.disable() }}
Callbacks模块只有一个内部方法
fire,用来触发
list中的回调执行,这个方法是
Callbacks模块的核心。
变量初始化
memory = options.memory && data fired = truefiringIndex = firingStart || 0firingStart = 0firingLength = list.lengthfiring = true
fire只接收一个参数
data,这个内部方法
fire跟我们调用
API所接收的参数不太一样,这个
data是一个数组,数组里面只有两项,第一项是上下文对象,第二项是回调函数的参数数组。
如果
options.memory为
true,则将
data,也即上下文对象和参数保存下来。
将
list是否已经触发过的状态
fired设置为
true。
将当前回调任务的索引值
firingIndex指向回调任务的开始位置
firingStart或者回调列表的开始位置。
将回调列表的开始位置
firingStart设置为回调列表的开始位置。
将回调任务的长度
firingLength设置为回调列表的长度。
将回调的开始状态
firing设置为
true
执行回调
for ( ; list && firingIndex < firingLength ; ++firingIndex ) { if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) { memory = false break }}firing = false
执行回调的整体逻辑是遍历回调列表,逐个执行回调。
循环的条件是,列表存在,并且当前回调任务的索引值
firingIndex要比回调任务的长度要小,这个很容易理解,当前的索引值都超出了任务的长度,就找不到任务执行了。
list[firingIndex].apply(data[0], data[1])就是从回调列表中找到对应的任务,绑定上下文对象,和传入对应的参数,执行任务。
如果回调执行后显式返回
false,
并且
options.stopOnFalse设置为
true,则中止后续任务的执行,并且清空
memory的缓存。
回调任务执行完毕后,将
firing设置为
false,表示当前没有正在执行的任务。
检测未执行的回调及清理工作
if (list) { if (stack) stack.length && fire(stack.shift()) else if (memory) list.length = 0 else Callbacks.disable()}
列表任务执行完毕后,先检查
stack中是否有没有执行的任务,如果有,则将任务参数取出,调用
fire函数执行。后面会看到,
stack储存的任务是
push进去的,用
shift取出,表明任务执行的顺序是先进先出。
memory存在,则清空回调列表,用
list.length = 0是清空列表的一个方法。在全局参数中,可以看到,
stack为
false,只有一种情况,就是
options.once为
true的时候,表示任务只能执行一次,所以要将列表清空。而
memory为
true,表示后面添加的任务还可以执行,所以还必须保持
list容器的存在,以便后续任务的添加和执行。
其他情况直接调用
Callbacks.disable()方法,禁用所有回调任务的添加和执行。
.add()
add: function() { if (list) { var start = list.length, add = function(args) { $.each(args, function(_, arg){ if (typeof arg === "function") { if (!options.unique || !Callbacks.has(arg)) list.push(arg) } else if (arg && arg.length && typeof arg !== 'string') add(arg) }) } add(arguments) if (firing) firingLength = list.length else if (memory) { firingStart = start fire(memory) } } return this},
start为原来回调列表的长度。保存起来,是为了后面修正回调任务的开始位置时用。
内部方法add
add = function(args) { $.each(args, function(_, arg){ if (typeof arg === "function") { if (!options.unique || !Callbacks.has(arg)) list.push(arg) } else if (arg && arg.length && typeof arg !== 'string') add(arg) })}
add方法的作用是将回调函数
push进回调列表中。参数
arguments为数组或者伪数组。
用
$.each方法来遍历
args,得到数组项
arg,如果
arg为
function类型,则进行下一个判断。
在下一个判断中,如果
options.unique不为
true,即允许重复的回调函数,或者原来的列表中不存在该回调函数,则将回调函数存入回调列表中。
如果
arg为数组或伪数组(通过
arg.length是否存在判断,并且排除掉
string的情况),再次调用
add函数分解。
修正回调任务控制变量
add(arguments)if (firing) firingLength = list.lengthelse if (memory) { firingStart = start fire(memory)}
调用
add方法,向列表中添加回调函数。
如果回调任务正在执行中,则修正回调任务的长度
firingLength为当前任务列表的长度,以便后续添加的回调函数可以执行。
否则,如果为
memory模式,则将执行回调任务的开始位置设置为
start,即原来列表的最后一位的下一位,也就是新添加进列表的第一位,然后调用
fire,以缓存的上下文及参数
memory作为
fire的参数,立即执行新添加的回调函数。
.remove()
remove: function() { if (list) { $.each(arguments, function(_, arg){ var index while ((index = $.inArray(arg, list, index)) > -1) { list.splice(index, 1) // Handle firing indexes if (firing) { if (index <= firingLength) --firingLength if (index <= firingIndex) --firingIndex } } }) } return this},
删除列表中指定的回调。
删除回调函数
用 each遍历参数列表,在
each遍历里再有一层
while循环,循环的终止条件如下:
(index = $.inArray(arg, list, index)) > -1
$.inArray()最终返回的是数组项在数组中的索引值,如果不在数组中,则返回
-1,所以这个判断是确定回调函数存在于列表中。关于
$.inArray的分析,见《读zepto源码之工具函数》。
然后调用
splice删除
list中对应索引值的数组项,用
while循环是确保列表中有重复的回调函数都会被删除掉。
修正回调任务控制变量
if (firing) { if (index <= firingLength) --firingLength if (index <= firingIndex) --firingIndex}
如果回调任务正在执行中,因为回调列表的长度已经有了变化,需要修正回调任务的控制参数。
如果
index <= firingLength,即回调函数在当前的回调任务中,将回调任务数减少
1。
如果
index <= firingIndex,即在正在执行的回调函数前,将正在执行函数的索引值减少
1。
这样做是防止回调函数执行到最后时,没有找到对应的任务执行。
.fireWith
fireWith: function(context, args) { if (list && (!fired || stack)) { args = args || [] args = [context, args.slice ? args.slice() : args] if (firing) stack.push(args) else fire(args) } return this},
以指定回调函数的上下文的方式来触发回调函数。
fireWith接收两个参数,第一个参数
context为上下文对象,第二个
args为参数列表。
fireWith后续执行的条件是列表存在并且回调列表没有执行过或者
stack存在(可为空数组),这个要注意,后面讲
disable方法和
lock方法区别的时候,这是一个很重要的判断条件。
args = args || [] args = [context, args.slice ? args.slice() : args]
先将
args不存在时,初始化为数组。
再重新组合成新的变量
args,这个变量的第一项为上下文对象
context,第二项为参数列表,调用
args.slice是对数组进行拷贝,因为
memory会储存上一次执行的上下文对象及参数,应该是怕外部对引用的更改的影响。
if (firing) stack.push(args)else fire(args)
如果回调正处在触发的状态,则将上下文对象和参数先储存在
stack中,从内部函数
fire的分析中可以得知,回调函数执行完毕后,会从
stack中将
args取出,再触发
fire。
否则,触发
fire,执行回调函数列表中的回调函数。
add和
remove都要判断
firing的状态,来修正回调任务控制变量,
fire方法也要判断
firing,来判断是否需要将
args存入
stack中,但是
javascript是单线程的,照理应该不会出现在触发的同时
add或者
remove或者再调用
fire的情况。
.fire()
fire: function() { return Callbacks.fireWith(this, arguments)},
fire方法,用得最多,但是却非常简单,调用的是
fireWidth方法,上下文对象是
this。
.has()
has: function(fn) { return !!(list && (fn ? $.inArray(fn, list) > -1 : list.length))},
has有两个作用,如果有传参时,用来查测所传入的
fn是否存在于回调列表中,如果没有传参时,用来检测回调列表中是否已经有了回调函数。
fn ? $.inArray(fn, list) > -1 : list.length
这个三元表达式前面的是判断指定的
fn是否存在于回调函数列表中,后面的,如果
list.length大于
0,则回调列表已经存入了回调函数。
.empty()
empty: function() { firingLength = list.length = 0 return this},
empty的作用是清空回调函数列表和正在执行的任务,但是
list还存在,还可以向
list中继续添加回调函数。
.disable()
disable: function() { list = stack = memory = undefined return this},
disable是禁用回调函数,实质是将回调函数列表置为
undefined,同时也将
stack和
memory置为
undefined,调用
disable后,
add、
remove、
fire、
fireWith等方法不再生效,这些方法的首要条件是
list存在。
.disabled()
disabled: function() { return !list},
回调是否已经被禁止,其实就是检测
list是否存在。
.lock()
lock: function() { stack = undefined if (!memory) Callbacks.disable() return this},
锁定回调列表,其实是禁止
fire和
fireWith的执行。
其实是将
stack设置为
undefined,
memory不存在时,调用的是
disable方法,将整个列表清空。效果等同于禁用回调函数。
fire和
add方法都不能再执行。
.lock() 和 .disable() 的区别
为什么 memory存在时,
stack为
undefined就可以将列表的
fire和
fireWith禁用掉呢?在上文的
fireWith中,我特别提到了
!fired || stack这个判断条件。在
stack为
undefined时,
fireWith的执行条件看
fired这个条件。如果回调列表已经执行过,
fired为
true,
fireWith不会再执行。如果回调列表没有执行过,
memory为
undefined,会调用
disable方法禁用列表,
fireWith也不能执行。
所以,
disable和
lock的区别主要是在
memory模式下,回调函数触发过后,
lock还可以调用
add方法,向回调列表中添加回调函数,添加完毕后会立刻用
memory的上下文和参数触发回调函数。
.locked()
locked: function() { return !stack},
回调列表是否被锁定。
其实就是检测
stack是否存在。
.fired()
fired: function() { return !!fired}
回调列表是否已经被触发过。
回调列表触发一次后
fired就会变为
true,用
!!的目的是将
undefined转换为
false返回。
相关文章推荐
- 读Zepto源码之Callbacks模块
- Zepto源码分析-callbacks模块
- 读Zepto源码之Callbacks模块
- Zepto事件模块源码分析
- 一步一步DIY zepto库,研究zepto源码6 -- deferred模块
- 读Zepto源码之assets模块
- 一步一步DIY zepto库,研究zepto源码1--基础模块
- 读Zepto源码之assets模块
- 读Zepto源码之Touch模块
- 读Zepto源码之Gesture模块
- Zepto源码之data模块
- 一步一步DIY zepto库,研究zepto源码7 -- 动画模块(fx,fx_method)
- 读Zepto源码之Touch模块
- jQuery源码解析--回调模块($.Callbacks())
- 读Zepto源码之Event模块
- 一步一步DIY zepto库,研究zepto源码8 -- touch模块
- 读Zepto源码之Event模块
- 读Zepto源码之Event模块
- 一个普通的 Zepto 源码分析(三) - event 模块
- Zepto源码之form模块