zepto源码研究 - callback.js
简要:$.Callbacks是一个生成回调管家Callback的工厂,Callback提供一系列方法来管理一个回调列表($.Callbacks的一个私有变量list),包括添加回调函数,
删除回调函数等等...,话不多说看正文:
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//回调只能触发一次的时候,stack永远为false
memory的值由传入$.Callbacks的形参对象决定,具有状态记忆功能。当为null||false时,callback.add仅仅是添加方法,而当为true时,则添加之后会立即执行。
请参考如下代码(来自: http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
}
var callbacks = $.Callbacks('memory');
callbacks.add(fn1);
callbacks.fire(); // 必须先fire
callbacks.add(fn2); // 此时会立即触发fn2
如下是触发回调任务的底层函数:
fire = function(data) {
memory = options.memory && data //记忆模式,触发过后,再添加新回调,也立即触发。
fired = true
firingIndex = firingStart || 0 //回调任务开始的索引赋值给将要执行函数的索引
firingStart = 0 //回调任务的触发会将列表里剩下的所有函数执行,因此下一次任务触发肯定是从0开始的,这里重置一下
firingLength = list.length
firing = true //标记正在回调 //遍历回调列表
for ( ; list && firingIndex < firingLength ; ++firingIndex ) {
//如果 list[ firingIndex ] 为false,且stopOnFalse(中断)模式
//list[firingIndex].apply(data[0], data[1]) 这是执行回调
if (list[firingIndex].apply(data[0], data[1]) === false && options.stopOnFalse) {
memory = false //中断回调执行
break
}
}
firing = false //标记回调执行完毕
if (list) {
//stack里还缓存有未执行的回调,如果回调任务只能执行一次则stack为false
if (stack) stack.length && fire(stack.shift()) //执行stack里的回调
else if (memory) list.length = 0 //memory 清空回调列表 list.length = 0清空数组的技巧
else Callbacks.disable(); //其他情况如 once 禁用回调
}
},
fire方法在回调执行前首先初始化回调索引和回调状态,然后循环顺序执行回调函数,传递参数为data[1],以data[0]为上下文执行,执行完后根据once是否只执行一次
,处理和回收list,memory,stack等变量。
var ca = {
name:"tom",
age:1
}
function printName() {
console.log(this.name+"-----"+arguments[0]);
}
function printAge() {
console.log(this.age+"-----"+arguments[0]);
}
list.push(printName);
list.push(printAge);
fire([ca,'test']);
结果:
tom-----test
1-----test
接下来是创建了一个Callbacks 对象以及一系列方法
//添加一个或一组到回调列表里
add: function() {
if (list) { //回调列表已存在
var start = list.length, //位置从最后一个开始
add = function(args) { //参数可以是:fn,[fn,fn],fn
$.each(args, function(_, arg){
if (typeof arg === "function") { //是函数
//非unique,或者是unique,但回调列表未添加过
if (!options.unique || !Callbacks.has(arg)) list.push(arg)
}
//是数组/伪数组,添加,重新遍历
else if (arg && arg.length && typeof arg !== 'string') add(arg)
})
} //添加进列表
add(arguments) //如果列表正在执行中,修正长度,使得新添加的回调也可以执行,
//firing:true表明fire中的循环执行还未结束,此时可以修改length;为false则表示循环执行结束了
if (firing) firingLength = list.length
else if (memory) {
//memory 模式下,修正开始下标,start为list.length,这里只循环一次
firingStart = start
fire(memory) //立即执行所有回调
}
}
return this
}
add方法是将一系列的fn加入到回调列表中,内部的add方法用到了递归技巧,同时对于回调任务的执行期间做出相应处理
,如下是例子(参考链接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
} var callbacks = $.Callbacks();
// 方式1
callbacks.add(fn1);
// 方式2 一次添加多个回调函数
callbacks.add(fn1, fn2);
// 方式3 传数组
callbacks.add([fn1, fn2]);
// 方式4 函数和数组掺和
callbacks.add(fn1, [fn2]);
remove方法会从回调列表中删除一个或一组fn(有去重功能)
//从回调列表里删除一个或一组回调函数,remove(fn),remove(fn,fn)
remove: function() {
if (list) { //回调列表存在才可以删除
//_作废参数
//遍历参数
$.each(arguments, function(_, arg){
var index //如果arg在回调列表里
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
}
方法中的while循环是去除回调列表中重复的函数的技巧,去除指定fn之后,如果此时回调列表正在执行回调任务,则修正回调索引(因为list中所有的回调函数的索引都改变了),这里能传多个fn做参数,它会循环删除,如下例子(参考链接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)
function fn1() {
console.log(1)
}
function fn2() {
console.log(2)
}
var callbacks = $.Callbacks();
callbacks.add(fn1, fn2);
callbacks.remove(fn1);
//此时fire只会触发fn2了。 var callbacks = $.Callbacks();
callbacks.add(fn1, fn2, fn1, fn2);
callbacks.remove(fn1);
//此时会把add两次的fn1都删掉,fire时只触发fn2两次。换成if则只删fn1一次
Callbacks里面的函数封装了fire方法,Callbacks.fire(args),回调函数将以Callbacks为this,args为参数调用,stack的作用是若回调列表处于触发状态,此时将
本次要触发的任务信息存入stack中
/**
* 用上下文、参数执行列表中的所有回调函数
* @param context
* @param args
* @returns {*}
*/
fireWith: function(context, args) {
// 未回调过,非锁定、禁用时
if (list && (!fired || stack)) { args = args || []
args = [context, args.slice ? args.slice() : args]
if (firing) stack.push(args) //正在回调中 ,存入static else fire(args) //否则立即回调
}
return this
}, /**
* 用参数执行列表中的所有回调函数
* @param context
* @param args
* @returns {*}
*/
fire: function() {
//执行回调
return Callbacks.fireWith(this, arguments)
}
如下:(参考链接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html)
function fn() {
console.log(this); // 上下文是callbacks
console.log(arguments); // [3]
}
var callbacks = $.Callbacks();
callbacks.add(fn);
callback.fire(3);
//前面已经提到了,fire方法用来触发回调函数,默认的上下文是callbacks对象,
//还可以传参给回调函数。
function fn() {
console.log(this); // 上下文是person
console.log(arguments); // [3]
}
var person = {name: 'jack'};
var callbacks = $.Callbacks();
callbacks.add(fn);
callback.fireWith(person, 3); //callback.fire.call(person, 3);
//其实fire内部调用的是fireWith,只是将上下文指定为this了,
//而this正是$.Callbacks构造的对象。
设计思考:
add,remove方法和fire等方法内部都加入了对回调列表的状态的判断和相应处理,比如fireWith方法内部判断当前回调任务是否正在进行,如果是,则将要执行的fn暂时加入到stack中。但这一设计思路对于单线程的js来说有点不太合理,如果是先执行回调列表,再fireWith,则实际过程是回调任务执行完之后再执行fireWith,这个时候回调任务已经结束了,不可能存在firing的情况。但为何jser还是要这么设计呢?
这里的设计是针对多线程来设计的。回调任务开始的同时,容许另一线程操作并修改回调列表内容。假想这样一个场景:有一个网络机器人R,功能是执行所有线上客户要求执行的一系列操作,某一时刻,客户A上传了一系列操作(命名为DO),当R正在执行操作时,客户A要求此时执行DO,而R正在执行其他操作,此时,R是被锁住的。DO则被加入到缓冲队列中延后执行。
这里在举个例子说明 来自链接:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html
// 观察者模式
var observer = {
hash: {},
subscribe: function(id, callback) {
if (typeof id !== 'string') {
return
}
if (!this.hash[id]) {
this.hash[id] = $.Callbacks()
this.hash[id].add(callback)
} else {
this.hash[id].add(callback)
}
},
publish: function(id) {
if (!this.hash[id]) {
return
}
this.hash[id].fire(id)
}
} // 订阅
observer.subscribe('mailArrived', function() {
alert('来信了')
})
observer.subscribe('mailArrived', function() {
alert('又来信了')
})
observer.subscribe('mailSend', function() {
alert('发信成功')
}) // 发布
setTimeout(function() {
observer.publish('mailArrived')
}, 5000)
setTimeout(function() {
observer.publish('mailSend')
}, 10000)
结束语:本文素材多来自其他文章并加上了自己的理解,感谢如下两位博客:
http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html
http://www.cnblogs.com/mominger/p/4369469.html
zepto源码研究 - callback.js的更多相关文章
- zepto源码研究 - deferred.js(jquery-deferred.js)
简要:zepto的deferred.js 并不遵守promise/A+ 规范,而在jquery v3.0.0中的defer在一定程度上实现了promise/A+ ,因此本文主要研究jquery v3. ...
- zepto源码研究 - fx_methods.js
简要:依赖fx.js,主要是针对show,hide,fadeIn,fadeOut的封装. 源码如下: // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zept ...
- zepto源码研究 - fx.js
简要:zepto 提供了一个基础方法animate来方便我们运用css动画.主要针对transform,animate以及普通属性(例如left,right,height,width等等)的trans ...
- zepto源码研究 - ajax.js($.ajax具体流程分析)
简要:$.ajax是zepto发送请求的核心方法,$.get,$.post,$.jsonp都是封装了$.ajax方法.$.ajax将jsonp与异步请求的代码格式统一起来,内部主要是先处理url,数据 ...
- zepto源码研究 - ajax.js($.ajaxJSONP 的分析)
简要:jsonp是一种服务器和客户端信息传递方式,一般是利用script元素赋值src来发起请求.一般凡是带有src属性的元素发起的请求都是可以跨域的. 那么jsonp是如何获取服务器的数据的呢? j ...
- zepto源码研究 - ajax.js(请求过程中的各个事件分析)
简要:ajax请求具有能够触发各类事件的功能,包括:触发全局事件,请求发送前事件,请求开始事件,请求结束事件等等,贯穿整个ajax请求过程,这是非常有用的,我们可以利用这些事件来做一些非常有意思的事情 ...
- zepto源码研究 - zepto.js - 1
简要:网上已经有很多人已经将zepto的源码研究得很细致了,但我还是想写下zepto源码系列,将别人的东西和自己的想法写下来以加深印象也是自娱自乐,文章中可能有许多错误,望有人不吝指出,烦请赐教. 首 ...
- 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 二八║ Nuxt 基础:面向源码研究Nuxt.js
前言 哈喽大家周五好,又是一个开开心心的周五了,接下来就是三天小团圆啦,这里先祝大家节日快乐咯,希望都没有加班哈哈,今天公司发了月饼,嗯~时间来不及了,上周应该搞个活动抽中几个粉丝发月饼的,下次吧,这 ...
- zepto源码研究 - zepto.js - 6(模板方法)
width height 模板方法 读写width/height ['width', 'height'].forEach(function(dimension){ //将width,hegih ...
随机推荐
- Intra Luma Prediction
在宏块的帧内预测过程中,有四种宏块类型:I_4x4,I_8x8,I16x16,I_PCM.他们都需要在相邻块做去块滤波之前进行帧内预测. 亮度帧内预测的总体流程 1-4获取当前block的帧内预测模式 ...
- insert 加的锁
?INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-ke ...
- C Looooops(扩展欧几里得求模线性方程)
http://poj.org/problem?id=2115 题意:对于C的循环(for i = A; i != B; i+=C)问在k位存储系统内循环多少次结束: 若循环有限次能结束输出次数,否则输 ...
- weblogic启动报错之建域时未指定AdminServer的监听IP的引起的子节点启动故障
各子节点不能启动,查看日志,报错如下: Unable to establish JMX Connectivity with the Adminstration Server AdminServer a ...
- kafka log4j配置
kafka日志文件分为5种类型,依次为:controller,kafka-request,server,state-change,log-cleaner,不同类型log数据,写到不同文件中: 区别于c ...
- Json.Net学习笔记(十) 保持对象引用
更多内容见这里:http://www.cnblogs.com/wuyifu/archive/2013/09/03/3299784.html 默认情况下,Json.Net将通过对象的值来序列化它遇到的所 ...
- bzoj 1264 [AHOI2006]基因匹配Match(DP+树状数组)
1264: [AHOI2006]基因匹配Match Time Limit: 10 Sec Memory Limit: 162 MBSubmit: 793 Solved: 503[Submit][S ...
- 《JavaScript语言精髓与编程实践》读书笔记二
第3章非函数式语言特性 这一章首先介绍了语言的分类,命令式(结构化编程,面向对象编程),说明式(函数式等).而这一章,主要介绍JS的非函数式特点. 在开始之前,首先介绍了由“结构化编程”向“面向对象编 ...
- java中的代理
package cn.itcast.day3; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHan ...
- 348. Design Tic-Tac-Toe
提示给的太直白了.. 比如player 1占据了(0,1),那么row[0]++ col[1]++ 表示第一行有1个O,第一列有1个X,假设PLAYER 1最终在第一行连成一排,那最终row[0] = ...