//     Zepto.js
// (c) 2010-2015 Thomas Fuchs
// Zepto.js may be freely distributed under the MIT license. /**
* 回调函数管理:添加add() 移除remove()、触发fire()、锁定lock()、禁用disable()回调函数。它为Deferred异步队列提供支持
* 原理:通过一个数组保存回调函数,其他方法围绕此数组进行检测和操作
*
*
* 标记:
* once: 回调只能触发一次
* memory 记录上一次触发回调函数列表时的参数,之后添加的函数都用这参数立即执行
* unique 一个回调函数只能被添加一次
* stopOnFalse 当某个回调函数返回false时中断执行
*/
;(function($){
// Create a collection of callbacks to be fired in a sequence, with configurable behaviour
// 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 callback
$.Callbacks = function(options) {
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//回调只能触发一次的时候,stack永远为false /**
* 回调底层函数
*/
fire = function(data) {
memory = options.memory && data //记忆模式,触发过后,再添加新回调,也立即触发。
fired = true
firingIndex = firingStart || 0
firingStart = 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里还缓存有未执行的回调
if (stack) stack.length && fire(stack.shift()) //执行stack里的回调
else if (memory) list.length = 0 //memory 清空回调列表 list.length = 0清空数组的技巧
else Callbacks.disable() //其他情况如 once 禁用回调
}
}, Callbacks = {
//添加一个或一组到回调列表里
add: function() {
if (list) { //回调列表已存在
var start = list.length, //位置从最后一个开始
add = function(args) {
$.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) //如果列表正在执行中,修正长度,使得新添加的回调也可以执行
if (firing) firingLength = list.length
else if (memory) {
//memory 模式下,修正开始下标,
firingStart = start
fire(memory) //立即执行所有回调
}
}
return this
}, //从回调列表里删除一个或一组回调函数
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
}, /**
* 检查指定的回调函数是否在回调列表中
* @param fn
* @returns {boolean}
*/
has: function(fn) {
//
return !!(list && (fn ? $.inArray(fn, list) > -1 : list.length))
}, /**
* 清空回调函数
* @returns {*}
*/
empty: function() {
firingLength = list.length = 0
return this
}, //禁用回调函数
disable: function() {
list = stack = memory = undefined
return this
}, /**
* 是否已禁用回调函数
* @returns {boolean}
*/
disabled: function() {
return !list
},
/**
* 锁定回调函数
* @returns {*}
*/
lock: function() {
stack = undefined; //导致无法触发 //非memory模式下,禁用列表
if (!memory) Callbacks.disable()
return this
},
/**
* 是否是锁定的
* @returns {boolean}
*/
locked: function() {
return !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)
}, /**
* 回调列表是否被回调过
* @returns {boolean}
*/
fired: function() {
return !!fired
}
} return Callbacks
}
})(Zepto)

  

用法

  本身单独用于管理回调函数列表。

    另外作为Deferred异步队列的基础。

      

 生命周期

设计原理

    

  设计上的疑问

列表触发列表stack是基于,当列表正在触发函数,而又需正执行添加函数的操作。这意味着两个线程,线程A在触发fire列表,线程B往列表add函数。理论上这样设计是合理的。

但实际上,我们分析下...

浏览器解析页面的主要线程如下

  •     JS引擎线程
  •     GUI渲染线程 和JS互斥,原因是JS操作DOM
  •     浏览器事件触发线程
  •     计时线程
  •     HTTP请求线程

   由于JS引擎是单线程的,任何JS的执行一个时间片段只能执行一段代码。如setTimeout,虽然开辟了计时线程,但是一旦响应时间到了,将执行JS函数时,立刻遵循单线程原则。函数塞入执行队列。事件线程也一样。一旦响应时,JS响应函数也会塞入执行队列。 问题来了,回调列表Callbacks怎么可能在触发fire的同时,再add操作呢?只有真正的多线程才能碰到这样的问题。

Worker?Work它是无法访问本页面的$.Callbacks对象。因为它访问不了window对象。

   除非它自己再通过importScripts('zepto-callbacks.js')加载一个JS,但它和页面的Callbacks是两个不同的对象。页面向Worker发送数据,Worker加载新的Callbacks,执行完毕后,再返回数据给页面。这多么麻烦啊?(Worker用法

所以Callbacks的这个多线程设计究竟是基于什么场景?

举例

此观察者案例来自  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')
}, )
setTimeout(function() {
    observer.publish('mailSend')
}, )

Zepto源码分析-callbacks模块的更多相关文章

  1. 读Zepto源码之Callbacks模块

    Callbacks 模块并不是必备的模块,其作用是管理回调函数,为 Defferred 模块提供支持,Defferred 模块又为 Ajax 模块的 promise 风格提供支持,接下来很快就会分析到 ...

  2. zepto源码分析·ajax模块

    准备知识 在看ajax实现的时候,如果对ajax技术知识不是很懂的话,可以参看下ajax基础,以便读分析时不会那么迷糊 全局ajax事件 默认$.ajaxSettings设置中的global为true ...

  3. zepto源码分析·event模块

    准备知识 事件的本质就是发布/订阅模式,dom事件也不例外:先简单说明下发布/订阅模式,dom事件api和兼容性 发布/订阅模式 所谓发布/订阅模式,用一个形象的比喻就是买房的人订阅楼房消息,售楼处发 ...

  4. zepto源码分析·core模块

    准备说明 该模块定义了库的原型链结构,生成了Zepto变量,并将其以'Zepto'和'$'的名字注册到了window,然后开始了其它模块的拓展实现. 模块内部除了对选择器和zepto对象的实现,就是一 ...

  5. Zepto源码分析-deferred模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  6. Zepto源码分析-ajax模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  7. Zepto源码分析-event模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  8. Zepto源码分析-form模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...

  9. zepto源码分析系列

    如果你也开发移动端web,如果你也用zepto,应该值得你看看.有问题请留言. Zepto源码分析-架构 Zepto源码分析-zepto(DOM)模块 Zepto源码分析-callbacks模块 Ze ...

随机推荐

  1. 程序设计 之 C#实现《拼图游戏》

    功能描述: 1.用户自定义上传图片 2.游戏难度选择:简单(3*3).一般(5*5).困难(9*9)三个级别 3.纪录完成步数 模块: 1.拼图类 2.配置类 3.游戏菜单窗口 4.游戏运行窗口 -- ...

  2. Virtualbox 复制 CentOS 虚拟机无法联网

    Centos刚装好后无法联网 复制虚拟机后,出现 No such device eth0 我们要处理的三个问题: 在Virtualbox上安装好Centos后如何联网 如何在Virtualbox上复制 ...

  3. 【linux 爱好者群】程序猿的那些聊天记录

    分享&&交流&&开放 you should get it 声明:好吧,我们的群只有5个人,但是有句话不是说的很对吗,一个项目最理想的不就是5个人么.我是写文本那个. 下 ...

  4. Unity属性的封装、继承、方法隐藏

    (一)Unity属性封装.继承.方法隐藏的学习和总结 一.属性的封装 1.属性封装的定义:通过对属性的读和写来保护类中的域. 2.格式例子: private string departname; // ...

  5. FreeSWITCH 安装配置的 各种坑, 填坑

    个人安装环境: OS:CentOS6.7 64位 FreeSWITCH Ver:1.6.17 一. 编译出错 安装 之前, 最好 先安装 这几个东西(如果有, 请忽略): yasm (有nasm的话 ...

  6. 交作业啊,python爬取58的页面

    第一次写博文,好紧张啊,写这么烂怎么给别人看啊先做下总结: 刚开始学习python,自我感觉python写起来确实很方便,各种库,各种语法糖,不过刚接触,一下子记不下来这么多东西,总感觉乱乱的,用的多 ...

  7. Google Code Jam 2016 Round 1B Problem C. Technobabble

    题目链接:https://code.google.com/codejam/contest/11254486/dashboard#s=p2 大意是教授的学生每个人在纸条上写一个自己的topic,每个to ...

  8. Java匿名内部类使用与示例

    首先说为什么有匿名类 两个原因(产生的使命) 1.简化代码编写 某种情况下,类只需要扩展一个方法,没必要为了一个方法单独去写一个子类,然后然后调用子类,此时需要匿名类 2.在不同的包内的类内调用类的p ...

  9. 用Visual Studio2017写静态库

    造轮子是一件有趣的事情,VS是一个强大的工具,能胜任超大规模的工程,但是讲真,对不那么大的项目配置起来不是那么友好(网上的其他教程也一点都不友好Orz).这里就展示一下构建一个简单的静态库的正确姿势. ...

  10. [系统启动]Printk与sched_clock_init的一点分析

     作者:Younger Liu, 本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可. 在分析Linu内核启动的过程中,发现一段"不平常" ...