首页 > 代码库 > 深入jQuery中的Callback()
深入jQuery中的Callback()
引入
初看Callback函数很不起眼,但仔细一瞅,发现Callback函数是构建jQuery大厦的无比重要的一个基石。jQuery中几乎所有有关异步的操作都会用到Callback函数。
为什么搞了个Callback函数?
1 在 js 开发中,经常会遇到同步和异步这两个概念。
2 在javascript中神马是同步?神马是异步? 听我讲一个相亲的故事(本故事并不准确,仅供参考):
1 藤篮是一个漂亮姑娘,明年就要30岁了可现在还没有对象,于是,她的母亲给她报名了两家相亲机构,一家名叫同步相亲机构,另一家叫异步相亲机构。
2 同步相亲机构:这个机构的负责人很呆板,严格遵从“先来后到”的理念。
负责人首先给藤篮一个小册子,里面记录了很多的男士资料,让藤篮从里面找一个心仪的男士,然后安排藤篮与他见面。
藤兰很快选中了令狐冲。负责人告诉藤篮:“令狐冲明天和任女士有约,你们只能后天见面了“。藤篮说:“好的,我正好准备准备”。
结果两天过后,负责人告诉藤篮:因为昨天任女士有事,没能和令狐冲见面,所以我们安排今天任女士与令狐冲见面,你到明天再约吧!
藤篮很生气:既然昨天任女士有事,为啥不安排让我昨天和令狐冲见面呢?
负责人说:不行!俺们讲究的是先来后到! 因为任女士先约的令狐冲,甭管怎么着,你都得排在任女士后面
藤篮很生气! 于是来到了异步相亲机构。
3 异步相亲机构:这个机构的负责人则很灵活:
一般情况下遵从先来后到的理念,特殊情况特殊对待。
藤篮很喜欢这个负责人的理念,于是和这里的负责人携手一生.......
4 再来总结一下:Javascript语言的执行环境是"单线程",所谓"单线程",就是指一次只能完成一件任务。
同步就是: 后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的
异步就是:一个任务可能有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而执行回调函数,
后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的
3 上面异步中讲到,一个任务可能有一个或多个回调函数,假如a任务有a1,a2,a3这三个回调函数,b也需要a1,a2,a3这三个回调函数。我们需要这样做:
function a1(){ } function a2(){ } function a3(){ } var a = setTimeout(function(){ a1(); a2(); a3(); },1000); var b = setTimeout(function(){ a1(); a2(); a3(); },2000)
4 上面的代码很麻烦是不是?Callback就是来解决这种麻烦!!
Callback如何解决这种麻烦?
先来说一下大体思路:
1 首先我们每次调用Callback(),都会返回一个callback对象,这个对象有一个仅仅只有自己才能访问的数组(就是用了闭包呗)
2 这个数组就是用来存储 回调函数的。
3 callback对象有许多方法,可以对数组进行操作,比如说:添加(add),清空(empty),删除(remove),运行数组中的所有回调函数(fire).......
4 那么,我们上面---为什么搞了个Callback函数?---的第三条 任务a,任务b可以如下改写:
function a1(){ } function a2(){ } function a3(){ } var allCallback = $.Callbacks(); allCallback.add(a1,a2,a3); var a = setTimeout(function(){ allCallback.fire() },1000); var b = setTimeout(function(){ allCallback.fire() },2000)
升华:
Callback可不仅仅只实现了这些,他还提供了很多参数: var a = Callback(‘once memory unique stopOnFalse ‘)
once: 如果创建Callback时加入该参数,则运行数组中的所有回调函数之后,也就是fire()之后,会清空数组。
memory: 会保存上一次运行fire(args)时的参数args,每当添加一个新的回调函数到数组中,会立即使用args作为参数调用新加的函数一次
unique: 确保数组中的回调函数互不相同
stopOnFalse: 当运行fire()时,若某个回调函数返回false,则立即终止余下的回调函数执行
尽管这些参数可能有些初看没吊用,长时间使用jQuery之后发现:确实没吊用。 但是不得要说,jQuery的开发人员真的很细致!
源码讲解:
define([ "./core", "./var/rnotwhite"], function( jQuery, rnotwhite ) {// String to Object options format cachevar optionsCache = {};// Convert String-formatted options into Object-formatted ones and store in cache/*如果: var a = $.Callback(‘once memory‘)则 optionsCache中会有这么一项:"once memory":{memory:true,once:true}*/function createOptions( options ) { var object = optionsCache[ options ] = {}; jQuery.each( options.match( 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 list是否已经被fire函数调用过 fired, // Flag to know if list is currently firing 当前是否正在调用fire函数 firing, // First callback to fire (used internally by add and fireWith) 第一个被执行的回调函数在list的位置 firingStart, // End of the loop when firing fire函数要运行的回调函数的个数 firingLength, // Index of currently firing callback (modified by remove if needed) 当前正在执行的回调函数的索引 firingIndex, //回调函数数组 list = [], // Stack of fire calls for repeatable lists 可重复的回调函数栈。我们可能会短时间内执行多次fire(),若当前的fire()正在迭代执行回调函数,而紧接着又执行了一次fire()时,会将下一次的fire()参数等保存至stack中,等待当前的fire()执行完成后,将stack中的fire()进行执行 stack = !options.once && [], // Fire callbacks fire = function( data ) { // data[0] 是一个对象,data[1]则是回调函数的参数 memory = options.memory && data; // 很精妙,仔细体会一下这句代码,如果调用Calbacks时传入了memory,则memory = data,否则memory = false fired = true; // 在调用本函数时,将fired状态进行修改 firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; firing = true; // 迭代回调函数之前,将firing状态进行修改 for ( ; list && firingIndex < firingLength; firingIndex++ ) { if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { // 运行回调函数的同时,检测回调函数是否返回false,若返回false,且调用Callbacks时传入stopOnFalse参数,则终止迭代 memory = false; // To prevent further calls using add 既然终止迭代了,那么之后添加的回调函数都不应该被调用,将memory设置为false break; } } firing = false; // 迭代回调函数完成后,将firing状态进行修改 if ( list ) { if ( stack ) { // 没有使用once参数 if ( stack.length ) { fire( stack.shift() ); } } else if ( memory ) { // 使用了once memory参数,则在迭代完回调函数之后清空list list = []; } else { // 其他 self.disable(); } } }, // Actual Callbacks object self = { // 将一个新的回调函数添加至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 ); // 若参数中的元素为函数且(无unique参数或者list中没有该函数),则将该函数添加至list末尾 } } else if ( arg && arg.length && type !== "string" ) { // arg的长度不为0且每项的类型不为字符串,也就是args为这种情况:[[fun1,fun2...],[fun3,fun4]](不仅限于这种情况) // Inspect recursively add( arg ); } }); })( arguments ); // Do we need to add the callbacks to the // current firing batch? // 当Callback中的firingLength变为 动态的! 也就是:只要我们向list中添加了一个新的回调函数,即使在fire()运行过程中,改变也能立即体现出来 if ( firing ) { firingLength = list.length; // With memory, if we‘re not firing then // we should call right away } else if ( memory ) { // 如果当前没有执行回调函数,且存在memory参数,则执行新添加的回调函数 firingStart = start; fire( memory ); } } return this; }, // Remove a callback from the list 将一个回调函数从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 // 使用传入的context作为当前函数的执行上下文 fireWith: function( context, args ) { if ( list && ( !fired || stack ) ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; if ( firing ) { stack.push( args ); // 如果当前正在迭代执行回调函数,则将新的fire参数推入stack中 } 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 // 用来确定当前callback对象是否被fire()过 fired: function() { return !!fired; } }; return self;};return jQuery;});
深入jQuery中的Callback()