jQuery源码 Callbacks
2020-04-15 本文已影响0人
柠檬果然酸
官方demo
function fn1(value) {
console.log(value);
}
function fn2(value) {
fn1("fn2 says: " + value);
return false;
}
可以将上述两个方法作为回调函数,并添加到 $.Callbacks 列表中,并按下面的顺序调用它们:
var callbacks = $.Callbacks();
callbacks.add(fn1);
// outputs: foo!
callbacks.fire("foo!");
callbacks.add(fn2);
// outputs: bar!, fn2 says: bar!
callbacks.fire("bar!")
这样做的结果是,当构造复杂的回调函数列表时,将会变更很简单。可以根据需要,很方面的就可以向这些回调函数中传入所需的参数。
上面的例子中,我们使用了 $.Callbacks() 的两个方法: .add() 和 .fire()。 .add() 和 .fire() .add() 支持添加新的回调列表, 而.fire() 提供了一种用于处理在同一列表中的回调方法的途径。
另一种方法是$.Callbacks 的.remove()方法,用于从回调列表中删除一个特定的回调。下面是.remove()使用的一个例子
var callbacks = $.Callbacks();
callbacks.add( fn1 );
// outputs: foo!
callbacks.fire( "foo!" );
callbacks.add( fn2 );
// outputs: bar!, fn2 says: bar!
callbacks.fire( "bar!" );
callbacks.remove( fn2 );
// only outputs foobar, as fn2 has been removed.
callbacks.fire( "foobar" );
jQuery.Callbacks还提供“once memory”等参数用来处理
1.once: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred)。
2.memory: 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred)。
3.unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调)。
4.stopOnFalse: 当一个回调返回false 时中断调用。
var callbacks = $.Callbacks('once');
callbacks.add(function() {
alert('a');
})
callbacks.add(function() {
alert('b');
})
callbacks.fire(); //输出结果: 'a' 'b'
callbacks.fire(); //未执行
jQuery回调模块结构
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 rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );
// Convert String-formatted options into Object-formatted ones
function createOptions( options ) {
var object = {};
jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
object[ flag ] = true;
} );
return object;
}
源码
// 回调模块
jQuery.extend( {
// once: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred)
// memory: 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred)
// unique: 确保一次只能添加一个回调(所以在列表中没有重复的回调)
// stopOnFalse: 当一个回调返回false 时中断调用
Callbacks: function( options ) {
options = typeof options === 'string' ?
createOptions( options ) :
jQuery.extend( {}, options )
var
firing, // list是否使用中
memory, // { Callbacks对象, fire()的参数 }
fired, // list是否使用过
locked,
list = [], // 观察者集合
queue = [], // 保存着上一次调用fire()时传入的参数
firingIndex = -1, // 当传入参数有memory时要用到这个参数定位
fire = function() {
locked = locked || options.once;
// list使用中
fired = firing = true;
// 这个外层循环是个摆设,只循环1次
// 最重要的是执行完循环后firingIndex = -1
for ( ; queue.length; firingIndex = -1 ) {
memory = queue.shift();
// 循环执行list中的函数
// 这代码的可读性真让人头皮发麻
// 有时候为了追求极简写法会让人摸不着头脑
while ( ++firingIndex < list.length ) {
// 执行函数并且检查是否需要提前终止
if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
options.stopOnFalse ) {
firingIndex = list.length;
memory = false;
}
}
}
// 如果options中没有memory,清空memory
// memory要用于add方法的判断,如果里面有值就会触发add中fire方法的调用
if ( !options.memory ) {
memory = false;
}
// list使用结束
firing = false;
// Clean up if we're done firing for good
if ( locked ) {
// Keep an empty list if we have data for future add calls
if ( memory ) {
list = [];
// Otherwise, this object is spent
} else {
list = "";
}
}
},
self = {
// Add a callback or a collection of callbacks to the list
add: function() {
if ( list ) {
if ( memory && !firing ) {
firingIndex = list.length - 1;
queue.push( memory );
}
// 这是一个立即执行的函数,而且声明的add方法也和自身没有任何关系,就是名字相同而已,不存在函数重写
( function add( args ) {
// 传入的可能不止一个函数,多个函数就需要each一下
jQuery.each( args, function( _, arg ) {
if ( isFunction( arg ) ) {
// 两种情况
// 1.如果options中没有unique
// 2.如果options中有unique,但list中没有该函数
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && toType( arg ) !== "string" ) { // arg有值 && arg长度不为0 && arg不为字符串(arg可能是个函数数组)
// 递归下去
add( arg );
}
} );
} )( arguments );
// 如果options中有memory,add过程中会立即执行当前函数,并且传入当前函数的参数是上一轮调用fire留下来的参数
if ( memory && !firing ) {
fire();
}
}
return this;
},
// Remove a callback from the list
remove: function() {
// 老规矩,要移除的方法不止一个
jQuery.each( arguments, function( _, arg ) {
var index;
while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
// splice()方法从数组中删除项目
list.splice( index, 1 );
// Handle firing indexes
// firingIndex的值基本上为-1
// 如果出现index <= firingIndex,说明此时fire()方法正在运行
if ( index <= firingIndex ) {
firingIndex--;
}
}
} );
return this;
},
// 查找list中是否有传入的函数
has: function( fn ) {
return fn ?
jQuery.inArray( fn, list ) > -1 :
list.length > 0;
},
// Remove all callbacks from the list
empty: function() {
if ( list ) {
list = [];
}
return this;
},
// Disable .fire and .add
// Abort any current/pending executions
// Clear all callbacks and values
disable: function() {
locked = queue = [];
list = memory = "";
return this;
},
disabled: function() {
return !list;
},
// Disable .fire
// Also disable .add unless we have memory (since it would have no effect)
// Abort any pending executions
// 上锁:其表现结果就是不能调用fire()方法,但是能调用add
// 如果option是memory依然会在add()之后调用fire()方法
lock: function() {
// 设置locked和queue指向同一个地址
locked = queue = [];
if ( !memory && !firing ) {
list = memory = "";
}
return this;
},
locked: function() {
return !!locked;
},
// Call all callbacks with the given context and arguments
fireWith: function( context, args ) {
if ( !locked ) {
args = args || [];
args = [ context, args.slice ? args.slice() : args ];
// 把[ context, 参数 ]放进queue里面
queue.push( args );
if ( !firing ) {
fire();
}
}
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;
}
} )