回调函数
一、概念
回调函数
是一个通过函数指针来调用执行的函数,如果你把一个函数的指针作为参数传递出去,那么这个指针调用这个函数的时候,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
好处:
使用回调函数进行处理,代码就可以继续进行其他任务,而无需空等。实际开发中,经常在javascript中使用异步调用。
- 异步回调
$(document).ready(callback);
$(document).on(‘click’,callback)
$.ajax({
url: "aaron.html",
context: document
}).done(function() {
//成功执行
}).fail(function() {
//失败执行
);
$('#clickme').click(function() {
$('#book').animate({
opacity: 0.25,
left: '+=50',
height: 'toggle'
}, 5000, function() {
// Animation complete.
});
});
- 同步
var test1 = function(callback) {
//执行长时间操作
callback();
}
test1(function() {
//执行回调中的方法
});
一个同步(阻塞)中使用回调的例子,目的是在test1代码执行完成后执行回调callback
所以理解回调函数最重要的2点:
1、一个回调函数作为参数传递给另一个函数是,我们仅仅传递了函数定义。我们并没有在参数中执行函数。我们并不传递像我们平时执行函数一样带有一对执行小括号()的函数
2、回调函数并不会马上被执行,它会在包含它的函数内的某个特定时间点被“回调”。
二、观察者模式
在理解jquery的回调对象之前我们先来学习一下观察者模式(SB模式):
观察者模式: 一个对象作为一个特定任务的
观察者
,当这个任务出发或者执行完毕之后通知
观察者
(Subscriber)。
观察者
也可以叫做
订阅者
,它指向
被观察者
(Publisher),当事件发生时,
被观察者
会通知
观察者
。
对于$.Callbacks
创建的Callback
对象,它的add
和fire
方法就是,其实就是基于发布订阅(Publish/Subscribe)
的观察者模式的设计。
// 模拟一下这种模式
function aa() {
console.log('aa');
}
function bb() {
console.log('bb');
}
var m_db = {
Callbacks: [],
add: function(fn) {
this.Callbacks.push(fn);
},
fire: function() {
this.Callbacks.forEach(function(fn){
fn();
})
}
}
m_db.add(aa);
m_db.add(bb);
m_db.fire();
- 设计原理
开始构建一个存放回调的数组,如this.callbacks= [] 添加回调时,将回调push进this.callbacks,执行则遍历this.callbacks执行回调,也弹出1跟2了。当然这只是简洁的设计,便于理解,整体来说设计的思路代码都是挺简单的,那么我们从简单的设计深度挖掘下这种模式的优势。
模式的实际使用
// 首先看一个场景
$.ajax({
url: '',
..
}).done(function(data) {
// 第一步处理数据
// 第二步处理DOM
$('aaron1').html(data.a)
$('aaron2').html(data.b)
$('aaron3').html(data.c)
// 其余处理
})
首先,所有的逻辑都写在done方法里面,这样确实是无可厚非的,但是问题就是逻辑太复杂了。Done
里面有数据处理
、html渲染
、还可能有其它不同场景的业务逻辑
。这样如果是换做不同的人去维护代码,增加功能就会显得很混乱而且没有扩展性。
$.ajax({
url: '',
..
}).done(function(data) {
// 第一步处理数据
processData(data);
// 第二步处理DOM
processDom(data);
// 其余处理
processOther(data);
})
这样看着时好一些了,通过同步执行来一次实现三个方面的处理,每一方面的处理都提取出来,但是这样的写法几乎就是“就事论事”的处理,达不到抽象复用。
var m_cb = {
callbacks: [],
add: function(fn){
this.callbacks.push(fn);
},
fire: function(data){
this.callbacks.forEach(function(fn){
fn(data);
})
}
}
m_cb.add(function(data){
// 数据处理
})
m_cb.add(function(data){
// DOM处理
})
m_cd.add(function(data){
// 其余处理
})
$.ajax({
url: '',
...
}).done(function(data){
m_cd.fire(data);
})
这样使用了观察者模式之后是不是感觉好多了呢,设计该模式背后的主要动力是促进形成松散耦合
。在这种模式中,并不是一个对象调用另一个对象的方法,而是一个对象订阅另一个对象的特定活动并在状态改变后获得通知。订阅者也称为观察者,而被观察的对象称为发布者或主题。当发生了一个重要的事件时,发布者将会通知(调用)所有订阅者并且可能经常以事件对象的形式传递消息。
总之、观察者模式就是将函数/业务处理管理起来,当一定的事件触发或者时某一任务执行完毕后,一次性执行。
三、$.Callbacks()
对于
$.Callbacks
创建的
Callback
对象,它的
add
和
fire
方法就是,其实就是基于发布订阅
(Publish/Subscribe)
的观察者模式的设计。
$.Callbacks
一般的开发者使用的较少,它的开发实现主要时为$.ajax
以及$.deferred
。
jQuery.Callbacks
是jquery
在1.7版本之后加入的,是从1.6版中的_Deferred
对象中抽离的,主要用来进行函数队列的add、remove、fire、lock
等操作,并提供once、memory、unique、stopOnFalse
四个option
进行一些特殊的控制。
这个函数常使用的就是在事件触发机制中,也就是观察者设计模式的订阅和发布模式中,$.Callbacks主要出现在ajax、deferred、queue中。
- 下面来仔细分析一下该方法的使用吧
1、先来跑一下流程
function aa() {
console.log('aa');
}
function bb() {
console.log('bb');
}
var cb = $.Callbacks();
cb.add(aa);
cb.add(bb);
cb.fire();
// aa
// bb
function fn1(value) {
console.log(value);
}
function fn2(value) {
fn1("fn2 says: " + value);
return false;
}
var cb1 = $.Callbacks();
cb1.add(fn1); // 添加一个进入队列
cb1.fire('foo'); // 执行一下
// foo
cb1.add(fn2); // 再添一个
cb1.fire('bar'); // 一次性执行
// bar
// fn2 says: bar
cb1.remove(fn2); // 移除一个
cb1.fire('111'); // 执行剩下的那一个
// 111
$.Callbacks()就是一个工厂函数。
- jQuery.Callbacks() 的 API 列表如下:
callbacks.add() :回调列表中添加一个回调或回调的集合。
callbacks.disable() :禁用回调列表中的回调。
callbacks.disabled() :确定回调列表是否已被禁用。
callbacks.empty() :从列表中删除所有的回调。
callbacks.fire() :用给定的参数调用所有的回调。
callbacks.fired() :访问给定的上下文和参数列表中的所有回调。
callbacks.fireWith() :访问给定的上下文和参数列表中的所有回调。
callbacks.has() :确定列表中是否提供一个回调。
callbacks.lock() :锁定当前状态的回调列表。
callbacks.locked() :确定回调列表是否已被锁定。
callbacks.remove() :从回调列表中的删除一个回调或回调集合。
- 源码结构
jQuery.Callbacks = function(options) {
// 首先对参数进行缓冲
options = typeof options === "string" ?
(optionsCache[options] || createOptions(options)) :
jQuery.extend({}, options);
// 实现代码
// 函数队列的处理
fire = function() {}
// 自身方法
self = {
add: function() {},
remove: function() {},
has: function(fn) {},
empty: function() {},
disable: function() {},
disabled: function() {},
lock: function() {},
locked: function() {},
fireWith: function(context, args) {},
fire: function() {},
fired: function() {}
};
return self;
};
- 参数处理
// 处理通过空格分隔的字符串
var str = "once queue";
var option = {};
$.each(str.match(/\S+/g) || [], function (_index, item) {
option[item] = true;
})
console.log(option);
// {once: true, queue: true}
Callbacks内部维护着一个List数组。这个数组用于存放我们订阅的对象,它是通过闭包来实现长期驻存的。添加回调时,将回调push进list,执行则遍历list执行回调。
Callbacks
有4个参数。
-
once
的作用是使callback
队列只执行一次。
-
var callbacks = $.Callbacks('once');
callbacks.add(function() {
alert('a');
})
callbacks.add(function() {
alert('b');
})
callbacks.fire(); //输出结果: 'a' 'b'
callbacks.fire(); //未执行
// 来看一下具体怎么实现
// jQuery是在执行第一个fire的时候直接给清空list列表了,然后在add的地方给判断下list是否存在,从而达到这样的处理
function Callbacks(options){
var list = [];
var self = {};
self: {
add: function(fn){
list.push(fn);
},
fire: function(data){
this.list.forEach(function(item){
item(data);
})
if(options == 'once') {
list = undefined;
}
}
}
return self;
}
// $jQuery.Callbacks的处理,在fire中调用了 self.disable(); 方法
// 禁用回调列表中的回调。
disable: function() {
list = stack = memory = undefined;
return this;
}
- memory 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调
function fn1(val) {
console.log('fn1 says ' + val);
}
function fn2(val) {
console.log('fn2 says ' + val);
}
function fn3(val) {
console.log('fn3 says ' + val);
}
var cbs = $.Callbacks('memory');
cbs.add(fn1);
cbs.fire('foo'); // fn1 says foo
console.log('..........')
cbs.add(fn2); // 这里在添加一个函数进入队列的同时,就立马执行了这个 回调了
cbs.fire('bar');
// fn2 says foo 这个东东比较特殊~
// fn1 says bar
// fn2 says bar
console.log('..........')
cbs.add(fn3);
cbs.fire('aaron');
// fn3 says bar
// fn1 says aaron
// fn2 says aaron
// fn3 says aaron
// 需要解决的问题一个就是如何获取上一个参数,以及add后的执行
function Callbacks(options) {
var list = [];
var self;
var firingStart;
var memory;
function _fire(data) {
memory = options === 'memory' && data;
firingIndex = firingStart || 0; //
firingStart = 0;
firingLength = list.length;
for (; list && firingIndex < firingLength; firingIndex++) {
list[firingIndex](data)
}
}
self = {
add: function(fn) {
var start = list.length;
list.push(fn)
// 如果参数是memory
if (memory) {
firingStart = start; //获取最后一值
_fire(memory); // 同时执行
}
},
fire: function(args) {
if (list) {
_fire(args)
}
}
}
return self;
}
- Unique:确保一次只能添加一个回调(所以在列表中没有重复的回调)
function fn1(val) {
console.log('fn1 says ' + val);
}
var callbacks = $.Callbacks( "unique" );
callbacks.add( fn1 );
callbacks.add( fn1 ); // repeat addition
callbacks.add( fn1 );
callbacks.fire( "foo" );
- stopOnFalse: 当一个回调返回false 时中断调用
function fn1(value) {
console.log(value);
return false;
}
function fn2(value) {
fn1("fn2 says: " + value);
return false;
}
var callbacks = $.Callbacks("stopOnFalse");
callbacks.add(fn1);
callbacks.fire("foo");
callbacks.add(fn2);
callbacks.fire("bar");
// foo
// bar
$.callback()的源码
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;
};
未完待续~~