$.Callbacks 是 jQuery 提供的可以方便地处理各种回调(callback)列表的类,其源代码是闭包的经典实现。

基本原理就是通过在闭包环境内保存一个 list = [] 数组用于存储回调列表,并用 firing,firingStart,firingLength,firingIndex等标志位来控制闭包的有序执行,下面是最重要的2个内部函数,触发函数 fire 和 添加函数 add。

         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; // To prevent further calls using add
break;
}
}
firing = false;
if ( list ) {
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) {
list = [];
} else {
self.disable();
}
}
}

fire 函数

             add: function () {
if ( list ) {
// First, we save the current length
var start = list.length;
(function add( args ) {
jQuery.each( args, function( _, arg ) {
var type = jQuery.type( arg );
if ( type === "function" ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && type !== "string" ) {
// Inspect recursively
add( arg );
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch? if (firing) {
console.log('firing');
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
} else if ( memory ) {
firingStart = start;
fire( memory );
}
}
return this;
}

add 函数

引起我思考的是遍历回调列表时, firing 标志位的使用,遍历前赋值 firing = true,遍历完赋值 firing = false。在 add 函数内如果检查到 firing === true,则将回调函数加入到还没遍历完的列表末端即可。

可是问题来了,什么情况下会出现for 循环没执行完的情况下 add 函数被调用呢?即 add 函数执行时 firing 有没可能为 true?

我在 add 函数碰上 firing === true 时加上一句调试 console.log('firing')

1. 第一种情况,在某个回调函数内部再嵌套添加另一个回调。代码如下:

var callBacks = $.Callbacks();
callBacks.add(function () {
console.log('callBacks 0'); callBacks.add(function () {
console.log('callBacks 2');
});
}); callBacks.add(function () {
console.log('callBacks 1');
}); callBacks.fire();
//执行结果
//callBacks 0
//firing
//callBacks 1
//callBacks 2

还有没其他的可能呢?js 不是单线程的吗?是不是只要在每个回调函数内不再调用 callBacks.add 就不会碰上 firing === true 呢?

2. 利用浏览器对页面事件的响应处理

<input id="text1" type="text" />
<script type="text/javascript">
var text1 = document.getElementById('text1'); text1.onblur = function () {
console.log('.onblur() is called'); callBacks.add(function () {
console.log('callBacks 2');
});
}; text1.focus(); var callBacks = $.Callbacks(''); callBacks.add(function () {
console.log('callBacks 0');
text1.blur();
console.log('.blur() is called');
}); callBacks.add(function () {
console.log('callBacks 1');
}); callBacks.fire();
//IE下执行结果
//callBacks 0
//.blur() is called
//callBacks 1
//.blur() is called //非IE浏览器下执行结果
//callBacks 0
//.onblur() is called
//firing
//.blur() is called
//callBacks 1
//callBacks 2
</script>

这就是浏览器处理事件流程时带来的 js 执行流混乱。

在非 IE 下 .blur() 的调用会使当前执行堆栈挂起,转而执行 onblur 事件的回调函数,等 onblur处理完了才回头执行 .blur() 后面的代码。

IE则会等 .blur() 所在的上下文执行完之后才执行 onblur 事件的回调函数。

总结就是,浏览器无法保证 JavaScript 的单线程线性执行。详细可以看看这篇文章

Is javascript guaranteed to be single-threaded?

里面还讲到一个有趣的点,不要认为 alert 函数会挂起这个js 执行,window.onresize 事件在这种情况下还是可以被触发的,Linux 很容易, window 下可以通过改变分辨率的方式做到。

最后,我开始思考另一个问题,脱离了浏览器的 nodeJs 会出现这种问题吗?nodeJs能保证一个函数的执行不因为另一个函数而挂起吗?

jQuery 源码细读 -- $.Callbacks的更多相关文章

  1. JQuery源码解析--callbacks

    (function (global, factory) { factory(global); })(this, function (window, noGlobal) { var rootjQuery ...

  2. 读jQuery源码 - Deferred

    Deferred首次出现在jQuery 1.5中,在jQuery 1.8之后被改写,它的出现抹平了javascript中的大量回调产生的金字塔,提供了异步编程的能力,它主要服役于jQuery.ajax ...

  3. 读jQuery源码有感2

    那么就来读读jQuery源码的Callbacks部分. 一上来看原版源码 jQuery.Callbacks = function( options ) { // Convert options fro ...

  4. 读jQuery源码 - Callbacks

    代码的本质突出顺序.有序这一概念,尤其在javascript——毕竟javascript是单线程引擎. javascript拥有函数式编程的特性,而又因为javascript单线程引擎,我们的函数总是 ...

  5. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  6. jQuery源码 Ajax模块分析

    写在前面: 先讲讲ajax中的相关函数,然后结合函数功能来具体分析源代码. 相关函数: >>ajax全局事件处理程序 .ajaxStart(handler) 注册一个ajaxStart事件 ...

  7. jQuery源码笔记(一):jQuery的整体结构

    jQuery 是一个非常优秀的 JS 库,与 Prototype,YUI,Mootools 等众多的 Js 类库相比,它剑走偏锋,从 web 开发的实用角度出发,抛除了其它 Lib 中一些中看但不实用 ...

  8. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  9. 读Zepto源码之Callbacks模块

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

随机推荐

  1. Yii 打造带有缓存功能的AR

    继承AR类 重写 findByPk方法为pk  还有afterSave afterDelete 通过对象主键缓存其属性  在insert update delete 操作时候 都会自动更新缓存还是挺方 ...

  2. lightoj 1094 Farthest Nodes in a Tree 【树的直径 裸题】

    1094 - Farthest Nodes in a Tree PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: ...

  3. 在网上浏览.NET的所有代码,并且让你的Visual Studio的go to definition(F12)指向在线代码

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:在网上浏览.NET的所有代码,并且让你的Visual Studio的go to definition(F ...

  4. Javascript从入门到精通(一)

    第一篇   基础知识 一.JavaScript的主要特点:1.解释性:不同于一些编译性的程序语言(如C.C++等),它是一种解释性的程序语言,它的源代码不需要经过编译,而是直接在浏览器中运行时被解释. ...

  5. [rxjs] Shares a single subscription -- publish()

    If have an observable and you subscribe it twice, those tow subscritions have no connection. console ...

  6. Entity FrameWork 指导文章

    Entity FrameWork学习指导: 转:http://www.entityframeworktutorial.net/code-first/configure-many-to-many-rel ...

  7. 管理http服务的脚本

    因为老是须要又一次安装系统,重一次都要又一次设置http服务的启动脚本.麻烦,所以这一次就把脚本备份出来. httpd for Ubuntu system: nginx + php-fpm #! /b ...

  8. Makefile详解--隐含规则

    Makefile详解--隐含规则(转) Makefile系列文章,这里有个前辈连续洗了一个系列来介绍,共有26篇博客文章. http://www.cppblog.com/ivenher/archive ...

  9. CCControlSlider和CCControlStepper用法

    一开始,size的大小是系统分配.玩家可以通过setDefaultSize()更改值 1. CCControlSlider用法 CCSpriteFrameCache::sharedSpriteFram ...

  10. 让iOS开发变得更有效率-分类、工具类

    在工作中整理的一些分类与工具类,分享给大家.这些工具类可以减少项目中的代码量,让代码变得更简洁,可以大大的提升项目的效率,直接拖到项目中使用即可.下载地址:https://github.com/lee ...