jquery源码 Callback
工具方法。对函数的统一管理。
jquery2.0.3版本$.Callback()部分的源码如下:
// String to Object options format cache
var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true;
});
return object;
} /*
* Create a callback list using the following parameters:
*
* options: an optional list of space-separated options that will change how
* the callback list behaves or a more traditional option object
*
* By default a callback list will act like an event callback list and can be
* "fired" multiple times.
*
* Possible options:
*
* once: will ensure the callback list can only be fired once (like a Deferred)
*
* memory: will keep track of previous values and will call any callback added
* after the list has been fired right away with the latest "memorized"
* values (like a Deferred)
*
* unique: will ensure a callback can only be added once (no duplicate in the list)
*
* stopOnFalse: interrupt callings when a callback returns false
*
*/
jQuery.Callbacks = function( options ) { // Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options ); var // Last fire value (for non-forgettable lists)
memory,
// Flag to know if list was already fired
fired,
// Flag to know if list is currently firing
firing,
// First callback to fire (used internally by add and fireWith)
firingStart,
// End of the loop when firing
firingLength,
// Index of currently firing callback (modified by remove if needed)
firingIndex,
// Actual callback list
list = [],
// Stack of fire calls for repeatable lists
stack = !options.once && [],
// Fire callbacks
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();
}
}
},
// Actual Callbacks object
self = {
// Add a callback or a collection of callbacks to the list
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 ) {
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;
},
// Remove a callback from the list
remove: function() {
if ( list ) {
jQuery.each( arguments, function( _, arg ) {
var index;
while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );
// Handle firing indexes
if ( firing ) {
if ( index <= firingLength ) {
firingLength--;
}
if ( index <= firingIndex ) {
firingIndex--;
}
}
}
});
}
return this;
},
// Check if a given callback is in the list.
// If no argument is given, return whether or not list has callbacks attached.
has: function( fn ) {
return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
},
// Remove all callbacks from the list
empty: function() {
list = [];
firingLength = 0;
return this;
},
// Have the list do nothing anymore
disable: function() {
list = stack = memory = undefined;
return this;
},
// Is it disabled?
disabled: function() {
return !list;
},
// Lock the list in its current state
lock: function() {
stack = undefined;
if ( !memory ) {
self.disable();
}
return this;
},
// Is it locked?
locked: function() {
return !stack;
},
// Call all callbacks with the given context and arguments
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;
},
// Call all the callbacks with the given arguments
fire: function() {
self.fireWith( this, arguments );
return this;
},
// To know if the callbacks have already been called at least once
fired: function() {
return !!fired;
}
}; return self;
};
一、$.Callback()的简单使用及应用场景
1、$.Callback()的用法。
观察者模式,添加完后统一触发。
function aaa(){
alert(1);
}
function bbb(){
alert(2);
}
var cb= $.Callbacks();
cb.add(aaa);
cb.add(bbb);
cb.fire();
2、好处,应用场景。
要统一的管理aaa和bbb。有时候如下,很难对不同作用域下的函数进行统一管理。
function aaa(){
alert(1);
}
(function(){
function bbb(){
alert(2);
}
})();
aaa();
bbb();
只能弹出1,因为bbb是局部作用域中的。
$callback可以做到。如下,只要cb是全局的。
var cb= $.Callbacks();
function aaa(){
alert(1);
}
cb.add(aaa);
(function(){
function bbb(){
alert(2);
}
cb.add(bbb);
})();
cb.fire();
对应复杂情况很有用。统一管理,通过fire统一触发。
二、原理图
Callback接收一个参数,可以有4个选项,once,memory,unique,stopOnFalse。
self单体有这些方法:add,remove,has,empty,disable,disabled,lock,locked, fireWith,fire,fired。
list=[]数组变量,用来收集回调函数。fire的时候对其循环调用。
add:push数组
fire:调用fireWith,fireWith允许传参,fire可传可不传。
fireWith:调用私有函数fire,在私有函数fire中for循环list。
remove:splice数组。
4个参数:
- once针对fire()只循环一次
- memory 针对add,作用到add上,add时判断有memory就去执行fire。
- unique 针对add,添加的时候就可以去重
- stopOnFalse 针对fire,在for循环时遇到false,立即跳出循环
三、更多用法
1、callback4个参数的作用
- once: 只能够触发一次。
- memory: 当队列已经触发之后,再添加进来的函数就会直接被调用,不需要再触发一次。
- unique: 保证函数的唯一
- stopOnFalse: 只要有一个回调返回 false,就中断后续的调用。
举例:
不传参数,fire几次就触发几次。
function aaa() {
alert(1);
} function bbb() {
alert(2);
}
var cb = $.Callbacks();
cb.add(aaa);
cb.add(bbb);
cb.fire(); //1 2
cb.fire();//1 2
- once:fire只能触发一次,源码中fire后如果有once就把list干掉了,list=undefined了。
function aaa() {
alert(1);
} function bbb() {
alert(2);
}
var cb = $.Callbacks('once');
cb.add(aaa);
cb.add(bbb);
cb.fire(); //1 2
cb.fire();
不传参数,在fire之后add的回调不能被fire。
//不写参数,只弹出1,2不会弹出
function aaa() {
alert(1);
} function bbb() {
alert(2);
}
var cb = $.Callbacks();
cb.add(aaa);
cb.fire(); //
cb.add(bbb);
- memory记忆,在fire前面后面add的方法都能得到执行。
function aaa() {
alert(1);
} function bbb() {
alert(2);
}
var cb = $.Callbacks('memory');
cb.add(aaa);
cb.fire(); //1 2
cb.add(bbb);
- unique:去重
//不加参数,add2次aaa,就会触发2次aaa
function aaa() {
alert(1);
} var cb = $.Callbacks();
cb.add(aaa);
cb.add(aaa);
cb.fire(); //1 1
function aaa() {
alert(1);
} var cb = $.Callbacks('unique');
cb.add(aaa);
cb.add(aaa);
cb.fire(); //1 加了unique参数,同样的函数不能多次add
- stopOnFalse:函数返回false跳出循环
function aaa() {
alert(1);
return false;
}
function bbb() {
alert(2);
} var cb = $.Callbacks();
cb.add(aaa);
cb.add(bbb);
cb.fire(); //1 2 不传参,第一个函数返回false时后面的函数也能正常执行
function aaa() {
alert(1);
return false;
}
function bbb() {
alert(2);
} var cb = $.Callbacks('stopOnFalse');
cb.add(aaa);
cb.add(bbb);
cb.fire(); //
//传参stopOnFalse,第一个函数返回false时后面的函数不再执行
2、callback也可以接收组合的形式
function aaa() {
alert(1);
}
function bbb() {
alert(2);
}
//组合使用,只执行一次,并且弹出1 2
var cb = $.Callbacks('once memory');
cb.add(aaa);
cb.fire(); //
cb.add(bbb);
cb.fire();
源码中:
传入了 once和memory后,
options={once:true,memory:true}
optionCache={ "once memory":{once:true,memory:true}
}
6、fire()可以传参
参数作为每个回调函数的实参
function aaa(n) {
alert("aaa "+n);
}
function bbb(n) {
alert("bbb "+n);
}
var cb = $.Callbacks();
cb.add(aaa);
cb.add(bbb);
//fire传参
cb.fire("hello"); //弹出aaa hello 和bbb hello
四、源码
Callbacks就是一个工具函数,内部定义了一个self ,add和remove还有has等挂在self上。
1、参数处理
$.Callbacks有4个可选的参数,可以组合传入,用空格分隔。比如 $.Callbacks("once memory unique");
这样传入的构造函数字符串实际上是一个字符串,源码中做了处理会把这个字符串转成对象。
// String to Object options format cache
var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true;
});
return object;
}
在构造函数中传入一个options后,先进行如下处理调用。把一个字符串处理成一个对象。
传入的options="once memory unique"处理后options={once:true,memory:true,unique:true}。
// Convert options from String-formatted to Object-formatted if needed
// (we check in cache first)
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );
过程如下:options="once memory unique"是string类型,所以先从optionsCache中获取,现在optionsCache为{}所以optionsCache[ options ]是undefined走后面的createOptions( options ) 。create操作中先新建一个以options为键的空对象,再循环给对象中填充。循环操作完
optionCache为
optionCache={ "once memory unique":{once:true,memory:true,unique:true} }
options为
options={once:true,memory:true,unique:true}
2、add源码
主要是把回调函数Push到数组list中。
add: function() {
if ( list ) { //list初始化为[],if判断会返回true
// First, we save the current length
var start = list.length;
(function add( args ) {
jQuery.each( args, function( _, arg ) { ////处理cb.add(aaa,bbb)这种调用
var type = jQuery.type( arg );//arg就是每一个函数
if ( type === "function" ) {//arg是函数就push到list中,此时有个判断有没有unique
if ( !options.unique || !self.has( arg ) ) {//有unique走后面,判断list中有没有这个函数,有就不添加了
list.push( arg );
}
} else if ( arg && arg.length && type !== "string" ) { //处理cb.add([aaa,bbb])这种调用
// Inspect recursively
add( arg );//递归分解,最终还是push到list
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch?
if ( 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;
},
3、remove源码
// Remove a callback from the list
remove: function() {
if ( list ) {
jQuery.each( arguments, function( _, arg ) {
var index;
while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
list.splice( index, 1 );//主要就是splice删除操作
// Handle firing indexes
if ( firing ) {
if ( index <= firingLength ) {
firingLength--;
}
if ( index <= firingIndex ) {
firingIndex--;
}
}
}
});
}
return this;
},
4、fire源码
1、整体调用逻辑
self的fire调用self的fireWith,fireWith把参数传递到fire()函数。
// Call all callbacks with the given context and arguments
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;
},
// Call all the callbacks with the given arguments
fire: function() {
self.fireWith( this, arguments );
return this;
},
fire()时主要是for循环
// Fire callbacks
fire = function( data ) {
memory = options.memory && data;
fired = true;//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 ) {//每次函数调用同时处理stopOnFalse的情况
memory = false; // To prevent further calls using add //stopOnFalse后有memory也不好使了
break;
}
}
firing = false;//触发结束
if ( list ) {
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) {
list = [];
} else {
self.disable();
}
}
},
2、firing特殊情况
比如在 fire 处理队列中,某个函数又在队列中添加了一个回调函数,或者,在队列中又删除了某个回调函数。 fire 处理过程中,某个函数又调用了 fire 来触发事件呢?
先通过例子来看一下效果
function aaa() {
alert(1);
cb.fire(); //在这里调用fire()会出现什么问题 死循环
}
function bbb() {
alert(2);
}
var cb = $.Callbacks();
cb.add(aaa);
cb.add(bbb); cb.fire();
在执行函数的过程中再次调用fire()的执行顺序是怎样的?
var bBtn=true;//用bBtn避免死循环
function aaa() {
alert(1);
if(bBtn){
cb.fire();//注意这里fire调用后执行顺序是1 2 1 2,而不是1 1 2 2
bBtn=false;
} }
function bbb() {
alert(2);
}
var cb = $.Callbacks();
cb.add(aaa);
cb.add(bbb); cb.fire();
结论:把函数运行过程中触发的fire()放到了运行过程的队列当中。
fire 处理过程中,某个函数又调用了 fire 来触发事件时,jQuery的处理方式如下:
将这个嵌套的事件先保存起来,等到当前的回调序列处理完成之后,再检查被保存的事件,继续完成处理。显然,使用队列是处理这种情况的理想数据结构,如果遇到这种状况,我们就将事件数据入队,待处理的时候,依次出队数据进行处理。什么时候需要这种处理呢?显然不是once的情况。在JavaScript中,堆队列也是通过数组来实现的,push用来将数据追加到数组的最后,而shift用来出队,从数据的最前面获取数据。
不过,jQuery没有称之为队列,而是取名stack。
// Stack of fire calls for repeatable lists
stack = !options.once && [],
入队
源码中,在fireWith的时候判断for循环有没有执行完
fireWith: function( context, args ) {
...if ( firing ) {//firing在for循环没有走完时一直是true
stack.push( args );//所以这句话意思就是函数执行时再去fire()调用就会push到stack数组中
} else {
fire( args );
}
}
return this;
},
出队
再去调用fire()的时候
// Fire callbacks
fire = function( data ) {
memory = options.memory && data;
fired = true;//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 ) {//每次函数调用同时处理stopOnFalse的情况
memory = false; // To prevent further calls using add //stopOnFalse后有memory也不好使了
break;
}
}
firing = false;//触发结束
if ( list ) {
if ( stack ) { //这就是出现在函数执行过程中再次fire()的时候,等循环执行完,再去按顺序执行
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) {//只执行一次的时候,有once,memory就清空list,此时fire()就相当于一个执行一个空数组
list = [];
} else {
self.disable();//disable阻止后续任何的fire()操作
}
}
},
针对下面这段源码的一个例子:
once和memory同时存在的时候,fire()无效因为list为[]了,但是add仍然有效。
当有memory的时候,把之前添加的清空;允许添加并再次运行fire后清空;当不存在memory的时候既只有once配置,fire之后既不允许做任何操作了。
else if ( memory ) {//只执行一次的时候,有once,memory就清空list,此时fire()就相当于一个执行一个空数组
list = [];
} else {
self.disable();//disable阻止后续任何的fire()操作
}
disable阻止后续任何的fire()操作。
function aaa() {
alert(1);
}
function bbb() {
alert(2);
}
//组合使用,只执行一次,并且弹出1 2 3
var cb = $.Callbacks('once memory');
cb.add(aaa);
cb.fire(); //
cb.fire();//此时list为[]
cb.add(bbb);
cb.fire();
function ccc(){
alert(3);
}
cb.add(ccc);
5、其他源码
has(fn):判断list有没有fn
empty: 清空数组list=[]
disable:全部锁住,禁止了,如下
// Have the list do nothing anymore
disable: function() {
list = stack = memory = undefined;
return this;
},
disabled:判断是不是禁止了。return !list;
lock:只是把stack锁住
// Lock the list in its current state
lock: function() {
stack = undefined;
if ( !memory ) {
self.disable();
}
return this;
},
locked:是否locked。 return !stack;
6、 lock和disable的区别
disable禁止所有操作
function aaa() {
alert(1);
}
function bbb() {
alert(2);
} var cb = $.Callbacks('memory');
cb.add(aaa);
cb.fire(); //
cb.disable();//disable()后只能弹出1 因为禁止所有操作了,虽然有Memory
cb.add(bbb);//不起作用了,此时list变为undefined了
cb.fire();//不起作用了
lock只是锁住数组
function aaa() {
alert(1);
}
function bbb() {
alert(2);
} var cb = $.Callbacks('memory');
cb.add(aaa);
cb.fire(); //1 2
cb.lock();//lock()只是把后续的fire()锁住,其他操作是锁不住的
cb.add(bbb);
cb.fire();//不起作用了 此时list为[]
参考:
http://www.cnblogs.com/haogj/p/4473477.html
本文作者starof,因知识本身在变化,作者也在不断学习成长,文章内容也不定时更新,为避免误导读者,方便追根溯源,请诸位转载注明出处:http://www.cnblogs.com/starof/p/6885500.html有问题欢迎与我讨论,共同进步。
jquery源码 Callback的更多相关文章
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
- jQuery源码 Ajax模块分析
写在前面: 先讲讲ajax中的相关函数,然后结合函数功能来具体分析源代码. 相关函数: >>ajax全局事件处理程序 .ajaxStart(handler) 注册一个ajaxStart事件 ...
- jQuery源码-dom操作之jQuery.fn.html
写在前面 前面陆陆续续写了jQuery源码的一些分析,尽可能地想要cover里面的源码细节,结果导致进度有些缓慢.jQuery的源码本来就比较晦涩,里面还有很多为了解决兼容问题很引入的神代码,如果不g ...
- jquery 源码解析
静态与实力方法共享设计 遍历方法 $(".a").each() //作为实例方法存在 $.each() //作为静态方法存在 Jquery源码 jQuery.prototype = ...
- jquery 源码学习(*)
最近在做日志统计程序,发现对方的程序是在Jquery基础上进行开发的,而公司的网站的框架是prototype.而且我也早就想了解一下Jquery源码,故决定研究Jquery源码,模拟它的方法 Jq ...
- [转]jQuery源码分析系列
文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...
- jQuery 源码基本框架
抽丝剥茧, 7000+ 行的 jQuery 源码基本可以概括为以下的伪代码 (function (window, undefined) { //将 document 封装成 jQuery 对象并缓存 ...
- jquery源码 DOM加载
jQuery版本:2.0.3 DOM加载有关的扩展 isReady:DOM是否加载完(内部使用) readyWait:等待多少文件的计数器(内部使用) holdReady():推迟DOM触发 read ...
- jquery源码解读
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐进增强)优雅的处理能 ...
随机推荐
- 读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字
1. 问题的引入——派生类不会发现模板基类中的名字 假设我们需要写一个应用,使用它可以为不同的公司发送消息.消息可以以加密或者明文(未加密)的方式被发送.如果在编译阶段我们有足够的信息来确定哪个信息会 ...
- (转)Java并发编程:并发容器之CopyOnWriteArrayList
原文链接:http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容, ...
- webService请求方式快速生成代码 (Postman)
Postman 这个东西只能在外网下载,是Goole一个插件. 1.FQ到外网,这里就不具体介绍怎么FQ了 2.上到谷歌浏览器,找到更过工具--->扩张程序--->获取更多扩张程序 3.在 ...
- EDP转换IC NCS8801S:RGB/LVDS转EDP芯片
RGB/LVDS-to-eDP Converter1 Features Embedded-DisplayPort (eDP) Output 2-lane/4-lane eDP @ 1.62 ...
- jquery 基础变量定义
var $i=$("#D1"); var i=$("#D2"); function(){ //这里获取控件对象值 var d1=$i.val ...
- 自定义一个EL函数
自定义一个EL函数 一般就是一下几个步骤,顺便提供一个工作常用的 案例: 1.编写一个java类,并编写一个静态方法(必需是静态方法),如下所示: public class DateTag { pri ...
- 浅谈css中单位px和em,rem的区别-转载
px是你屏幕设备物理上能显示出的最小的一个点,这个点不是固定宽度的,不同设备上点的长宽.比例有可能会不同.假设:你现在用的显示器上1px宽=1毫米,但我用的显示器1px宽=两毫米,那么你定义一个div ...
- HibernateTemplate的使用
HibernateTemplate 提供了非常多的常用方法来完成基本的操作,比如增加.删除.修改及查询等操作,Spring 2.0 更增加对命名 SQL 查询的支持,也增加对分页的支持.大部分情况下, ...
- hibernate持久化框架
Hibernate是一个优秀的持久化框架 瞬时状态:保存在内存的程序数据,程序退出后,数据就消失了,称为瞬时状态 持久状态:保存在磁盘上的程序数据,程序退出后依然存在,称为程序数据的持久状态 持久化: ...
- Redis和Spring整合
Redis和Spring整合 Redis在这篇里就不做介绍了~以后系统的学学,然后整理写出来. 首先是环境的搭建 通过自己引包的方式,将redis和spring-redis的包引到自己的项目中,我项目 ...