深入理解jQuery中的callback
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解jQuery中的callback相关的知识,希望对你有一定的参考价值。
本文要讲的是jQuery.Callback() ,这是一个构造函数。在讲解这个之前,我们需要先讲讲javascript中的回调函数。
什么是回调?
所谓回调函数,我的理解:回头再调用,即:不是立刻运行,而是在特定时刻触发调用。
js中的同步和异步
在javascript中,回调函数和同步 异步有很深的渊源,所以我们先来理解一下在js中的同步和异步的概念:
同步: 后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。
异步:,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。
更确切的说:回调和异步有很深的渊源,js中的 事件 定时器 ajax animate等等都用到了回调函数。
为什么jQuery搞了个Callback()?
我们上面说到:js中的 事件 定时器 ajax animate等等都用到了回调函数,而且回调函数可能有多个。
我们来举个例子:
function a(arg){
console.log(‘吃饭‘ + arg)
}
function b(arg){
console.log(‘睡觉‘ + arg)
}
function c(arg){
console.log(‘打豆豆‘ + arg)
}
var xiaoming = setTimeout(function(){
a(‘ing‘);
b(‘ing‘);
c(‘ing‘);
},1000);
console.log(‘------------‘)
我们在这里用定时器函数实现了异步,在一秒钟后(这句话并不准确,明白为啥不准确的同学们就不要在意这句话了,我是为了方便,不明白的请自行查阅setTimeout函数详解)运行a b c三个函数。
但是这里有个问题,a b c三个函数和小明耦合是不是有些深? 小明现在每天是吃饭睡觉打豆豆,如果某天他不仅要吃饭睡觉打豆豆,还要学习的话,我们还得修改xiaoming的内部结构。 所以我们改进一下上面的格式:
function a(arg){ console.log(‘吃饭‘ + arg) } function b(arg){ console.log(‘睡觉‘ + arg) } function c(arg){ console.log(‘打豆豆‘ + arg) } function d(arg){ console.log(‘学习‘ + arg) } function callback(){ var list = []; var self = {}; self.add = function(thing){ list.push(thing) }; self.fire = function(arg){ for(var i in list){ list[i](arg) } }; return self } var xiaomingDo = callback(); xiaomingDo.add(a); xiaomingDo.add(b); xiaomingDo.add(c); xiaomingDo.add(d); var xiaoming = setTimeout(function(){ xiaomingDo.fire(‘ing‘); // 弱化耦合 },1000);
console.log(‘------------‘)
这样我们就弱化了耦合,将每天具体做的事从xiaoming中分离。
上例中的callback函数建立了一个用来保存函数的数组,最终返回了一个对象,这个对象具有两个方法:add 用来向数组中添加函数 ; fire 用来将数组中的函数全部执行。看到这里是不是感觉很熟悉? 没错,这里采用了观察者模式。
再来个小红:
var xiaohongDo = callback(); xiaohongDo.add(a); xiaohongDo.add(b); var xiaohong = setTimeout(function(){ xiaohongDo.fire() },1000);
是不是感觉逻辑清晰很多。本例中的callback函数与jQuery中的Callback函数的作用以及实现原理是类似的。
jQuery.Callback()大体原理:
jQuery.Callback()需要在内部维护着一个存储函数的队列数组。
每当我们调用Callback时,都会返回一个新的对象。
使用这个对象的方法我们可以对队列数组进行操作。
当然jQuery.Callback()并不是我们想象的那么简单,他还提供了很多可供选择的参数:
once:保证数组内的函数只执行一遍
memory:保持以前的fire参数值,每当添加一个新的函数时,会将之前的fire参数值作为参数立即调用新函数
unique:确保数组中的函数无重复
stopOnFalse:当一个函数返回false时中断
源码:
jQuery.Callbacks = function( options ) { // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) //通过字符串在optionsCache寻找有没有相应缓存,如果没有则创建一个,有则引用 //如果是对象则通过jQuery.extend深复制后赋给options。 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为true,则记录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 ) { // 阻止未来可能由于add所产生的回调 memory = false; // To prevent further calls using add break; //由于参数stopOnFalse为true,所以当有回调函数返回值为false时退出循环 } } //标记回调结束 firing = false; if ( list ) { if ( stack ) { if ( stack.length ) { //从堆栈头部取出,递归fire fire( stack.shift() ); } } else if ( memory ) {//否则,如果有记忆 list = []; } else {//再否则阻止回调列表中的回调 self.disable(); } } }, // Actual Callbacks object // 暴露在外的Callbacks对象,对外接口 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传进来的列表的每一个对象执行操作 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? // 如果回调列表中的回调正在执行时,其中的一个回调函数执行了Callbacks.add操作 // 上句话可以简称:如果在执行Callbacks.add操作的状态为firing时 // 那么需要更新firingLength值 if ( firing ) { firingLength = list.length; // With memory, if we‘re not firing then // we should call right away } else if ( memory ) { //如果options.memory为true,则将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循环的意义在于借助于强大的jQuery.inArray删除函数列表中相同的函数引用(没有设置unique的情况) // jQuery.inArray将每次返回查找到的元素的index作为自己的第三个参数继续进行查找,直到函数列表的尽头 // splice删除数组元素,修改数组的结构 while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 ); // Handle firing indexes // 在函数列表处于firing状态时,最主要的就是维护firingLength和firgingIndex这两个值 // 保证fire时函数列表中的函数能够被正确执行(fire中的for循环需要这两个值 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; }; 复制代码
本文参考:http://www.cnblogs.com/aaronjs/p/3342344.html
以上是关于深入理解jQuery中的callback的主要内容,如果未能解决你的问题,请参考以下文章