欢迎来我的专栏检察系列文章。
讲真,Sizzle 的源码真的太压制了,以至于写 Sizzle 文章的这段时刻里都异常的痛楚,刚开始以为它还挺有意义的,越到背面越以为代码很难读懂,烦。
寒假也过完了,在家里待了两周的时刻,觉得不错,这时期进修的事变都抛在脑后,学得异常少,把 cctv 的《中国通史》系列节目给看完了,关于汗青迷的我来讲,也算是一种心安吧。
本日的主题不在时 Sizzle,停顿了两周,觉得清醒了许多,之前被 Sizzle 安排的痛楚已消去泰半,本日来引见一下 jQuery 的 Callbacks 函数。
Callbacks 的运用
jQuery 内部供应了许多基础功用的要领,比方 $.ajax()、$.each() 和 $.Callbacks(),这些要领既能够在内部举行运用,又能够被开发者拿到外部零丁运用。
Callbacks 的支撑的要领有几个重要的,add、fire、remove 和 disable,比方官方有一个例子:
// 这两个作为 callback 函数
function fn1( value ) {
console.log( value );
}
function fn2( value ) {
fn1("fn2 says: " + value);
return false;
}
// 挪用 jQuery 的 Callbacks 天生 callbacks
var callbacks = $.Callbacks();
callbacks.add( fn1 );
callbacks.fire( "foo!" );
// 'foo!'
callbacks.add( fn2 );
callbacks.fire( "bar!" );
// 'bar!'
// 'fn2 says: bar!'
从基础 demo 能够看出,$.Callbacks()
函数天生了一个 callbacks 对象,这个对象的 .add()
要领是增加回调函数,而 .fire()
要领则是实行回调函数。
.remove()
要领是移除回调函数:
var callbacks = $.Callbacks();
callbacks.add( fn1 );
callbacks.fire( "foo!" );
// 'foo!'
callbacks.add( fn2 );
callbacks.fire( "bar!" );
// 'bar!'
// 'fn2 says: bar!'
callbacks.remove( fn2 );
callbacks.fire( "foobar" );
// 'foobar'
$.Callbacks()
还支撑几个参数,示意实行回调的几种结果,$.Callbacks('once')
:
once
: 确保这个回调列表只实行 .fire() 一次(像一个递延 Deferred)memory
: 坚持之前的值,将增加到这个列表的背面的最新的值马上实行挪用任何回调 (像一个递延 Deferred)unique
: 确保一次只能增加一个回调(所以在列表中没有反复的回调)stopOnFalse
: 当一个回调返回false 时中缀挪用
此要领还支撑多个参数,比方$.Callbacks('once memory')
,详细的运用请参考这个链接。
Callbacks 的源码
在放 jQuery 3.0 的源码之前,我们先来简朴的模仿一下 Callbacks 函数,来完成其基础的功用:
var Callbacks = function(){
var Cb = {
callbacks: [],
add: function(fn){
this.callbacks.push(fn);
return this;
},
fire: function(value){
this.callbacks.forEach(function(fn){
fn(value);
});
return this;
}
}
return Cb;
}
// 测试
var callbacks = Callbacks();
callbacks.add(fn1);
callbacks.fire('test'); //'test'
能够看到实在一个简朴的 Callbacks 函数完成起来照样异常简朴的。
全部的 Callbacks 源码实在大抵以下:
jQuery.Callbacks = function(options){
// 先对参数举行处置惩罚,比方 once、unique 等
options = createOptions(options);
// 参数定义,包含一些 flag 和 callbacks 数组
var list = [], queue = [] ...
// fire 是遍历数组,回掉函数的实行
var fire = function(){
...
}
// self 是终究返回的对象
var self = {
add: function(){...},
remove: function(){...},
has: function(){...},
disable: function(){...},
fireWith: function(){...},//这个实际上是 fire 函数的实行
fire: function(){...}
...
}
return self;
}
由于前面已简朴的引见过了怎样完成一个基础的 Callbacks 函数,这里轻微清楚了一点,来看下 createOptions
函数,这个函数重如果对类似于 $.Callbacks('once memory')
范例对 callback 举行 flag 星散:
function createOptions(options) {
var object = {};
jQuery.each(options.match(rnothtmlwhite) || [], function (_, flag) {
object[flag] = true;
});
return object;
}
个中 rnothtmlwhite
是一个正则表达式 /[^\x20\t\r\n\f]+/g
,用来取得一切的 flag 标志。createOptions 的结果是一个对象,键值分别是 flag 和 boolean。
那末如今的重要的题目,就全在那些 flag 上面来,"once memory unique stopOnFalse"
。
源码送上:
jQuery.Callbacks = function(options) {
// flag 处置惩罚
options = typeof options === "string" ? createOptions(options) : jQuery.extend({}, options);
var // Flag to know if list is currently firing
firing,
// Last fire value for non-forgettable lists
memory,
// Flag to know if list was already fired
fired,
// Flag to prevent firing
locked,
// Actual callback list
list = [],
// Queue of execution data for repeatable lists
queue = [],
// Index of currently firing callback (modified by add/remove as needed)
firingIndex = -1,
// Fire callbacks
fire = function() {
// 只实行一次,今后都不实行了
locked = locked || options.once;
// Execute callbacks for all pending executions,
// respecting firingIndex overrides and runtime changes
fired = firing = true;
for (; queue.length; firingIndex = -1) {
memory = queue.shift();
while (++firingIndex < list.length) {
// 回调实行函数,并搜检是不是 stopOnFalse,并阻挠继承运转
if (list[firingIndex].apply(memory[0], memory[1]) === false && options.stopOnFalse) {
// Jump to end and forget the data so .add doesn't re-fire
firingIndex = list.length;
memory = false;
}
}
}
// Forget the data if we're done with it
if (!options.memory) {
memory = false;
}
firing = false;
// locked 在这里完成
if (locked) {
// 虽然锁住然则是 memory,保存 list 今后运用
if (memory) {
list = [];
// 拜拜...
} else {
list = "";
}
}
},
// Actual Callbacks object
self = {
// Add a callback or a collection of callbacks to the list
add: function() {
if (list) {
// If we have memory from a past run, we should fire after adding
if (memory && !firing) {
firingIndex = list.length - 1;
queue.push(memory);
}
(function add(args) {
jQuery.each(args, function(_, arg) {
if (jQuery.isFunction(arg)) {
if (!options.unique || !self.has(arg)) {
list.push(arg);
}
} else if (arg && arg.length && jQuery.type(arg) !== "string") {
// Inspect recursively
add(arg);
}
});
})(arguments);
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) {
list.splice(index, 1);
// Handle firing indexes
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.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
lock: function() {
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];
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;
};
总的来讲,这类 pub/sub 形式的代码照样比较轻易看懂的,有些疑问的处所,比方源码中实在有两个数组,list 是行列数组,本应当叫做 queue,然则 queue 数组已被定义,且 queue 的作用是用来存储 fire 实行时的参数,这点不能搞混。
另有就是当全部代码 firing 这个参数,致使当函数正在运转的时刻,即实行两次 fire 的时刻,须要补充 queue 元素,但 fire()
函数只实行一次。
总结
jQuery.Callbacks 相沿 jQuery 一向的套路,末了 return self
,刚看第一遍第二遍的时刻,有点迷迷糊糊的,重要照样 once、memory 等 flag 参数滋扰我的视线,尤其是其这些 flag 标志的完成,难熬痛苦。
参考
本文在 github 上的源码地点,欢迎来 star。
欢迎来我的博客交换。