jQuery 之 Callback 实现
在 js 开发中,由于没有多线程,经常会遇到回调这个概念,比如说,在 ready 函数中注册回调函数,注册元素的事件处理等等。在比较复杂的场景下,当一个事件发生的时候,可能需要同时执行多个回调方法,可以直接考虑到的实现就是实现一个队列,将所有事件触发时需要回调的函数都加入到这个队列中保存起来,当事件触发的时候,从这个队列重依次取出保存的函数并执行。
可以如下简单的实现。
首先,实现一个类函数来表示这个回调类。在 javascript 中,使用数组来表示这个队列。
function Callbacks() {
this.list = [];
}
然后,通过原型实现类中的方法。增加和删除的函数都保存在数组中,fire 的时候,可以提供参数,这个参数将会被传递给每个回调函数。
Callbacks.prototype = {
add: function(fn) {
this.list.push(fn);
},
remove: function(fn){
var position = this.list.indexOf(fn);
if( position >=0){
this.list.splice(position, 1);
}
},
fire: function(args){
for(var i=0; i<this.list.length; i++){
var fn = this.list[i];
fn(args);
}
}
};
测试代码如下:
function fn1(args){
console.log("fn1: " + args);
}
function fn2(args){
console.log("fn2: " + args);
}
var callbacks = new Callbacks();
callbacks.add(fn1);
callbacks.fire("Alice");
callbacks.add(fn2);
callbacks.fire("Tom");
callbacks.remove(fn1);
callbacks.fire("Grace");
或者,不使用原型,直接通过闭包来实现。
function Callbacks() {
var list = [];
return {
add: function(fn) {
list.push(fn);
},
remove: function(fn){
var position = list.indexOf(fn);
if( position >=0){
list.splice(position, 1);
}
},
fire: function(args) {
for(var i=0; i<list.length; i++){
var fn = list[i];
fn(args);
}
}
};
}
这样的话,示例代码也需要调整一下。我们直接对用 Callbacks 函数就可以了。
function fn1(args){
console.log("fn1: " + args);
}
function fn2(args){
console.log("fn2: " + args);
}
var callbacks = Callbacks();
callbacks.add(fn1);
callbacks.fire("Alice");
callbacks.add(fn2);
callbacks.fire("Tom");
callbacks.remove(fn1);
callbacks.fire("Grace");
下面我们使用第二种方式继续进行。
对于更加复杂的场景来说,我们需要只能 fire 一次,以后即使调用了 fire ,也不再生效了。
比如说,可能在创建对象的时候,成为这样的形式。这里使用 once 表示仅仅能够 fire 一次。
var callbacks = Callbacks("once");
那么,我们的代码也需要进行一下调整。其实很简单,如果设置了 once,那么,在 fire 之后,将原来的队列中直接干掉就可以了。
function Callbacks(options) {
var once = options === "once";
var list = [];
return {
add: function(fn) {
if(list){
list.push(fn);
}
},
remove: function(fn){
if(list){
var position = list.indexOf(fn);
if( position >=0){
list.splice(position, 1);
}
}
},
fire: function(args) {
if(list)
{
for(var i=0; i<list.length; i++){
var fn = list[i];
fn(args);
}
}
if( once ){
list = undefined;
}
}
};
}
jQuery 中,不只提供了 once 一种方式,而是提供了四种类型的不同方式:
- once: 只能够触发一次。
- memory: 当队列已经触发之后,再添加进来的函数就会直接被调用,不需要再触发一次。
- unique: 保证函数的唯一
- stopOnFalse: 只要有一个回调返回 false,就中断后继的调用。
这四种方式可以组合,使用空格分隔传入构造函数即可,比如 $.Callbacks("once memory unique");
官方文档中,提供了一些使用的示例。
callbacks.add(fn1, [fn2, fn3,...])//添加一个/多个回调
callbacks.remove(fn1, [fn2, fn3,...])//移除一个/多个回调
callbacks.fire(args)//触发回调,将args传递给fn1/fn2/fn3……
callbacks.fireWith(context, args)//指定上下文context然后触发回调
callbacks.lock()//锁住队列当前的触发状态
callbacks.disable()//禁掉管理器,也就是所有的fire都不生效
由于构造函数串实际上是一个字符串,所以,我们需要先分析这个串,构建为一个方便使用的对象。
// String to Object options format cache
var optionsCache = {}; // Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
var object = optionsCache[ options ] = {};
jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
object[ flag ] = true;
});
return object;
}
这个函数将如下的参数 "once memory unique" 转换为一个对象。
{
once: true,
memory: true,
unique: true
}
再考虑一些特殊的情况,比如在 fire 处理队列中,某个函数又在队列中添加了一个回调函数,或者,在队列中又删除了某个回调函数。为了处理这种情况,我们可以在遍历整个队列的过程中,记录下当前处理的起始下标、当前处理的位置等信息,这样,我们就可以处理类似并发的这种状况了。
// 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 = [],
如果在 fire 处理过程中,某个函数又调用了 fire 来触发事件呢?
我们可以将这个嵌套的事件先保存起来,等到当前的回调序列处理完成之后,再检查被保存的事件,继续完成处理。显然,使用队列是处理这种状况的理想数据结构,如果遇到这种状况,我们就将事件数据入队,待处理的时候,依次出队数据进行处理。什么时候需要这种处理呢?显然不是在 once 的状况下。在 JavaScript 中,堆队列也是通过数组来实现的,push 用来将数据追加到数组的最后,而 shift 用来出队,从数组的最前面获取数据。
不过,jQuery 没有称为队列,而是称为了 stack.
// Stack of fire calls for repeatable lists
stack = !options.once && [],
入队代码。
if ( firing ) {
stack.push( args );
}
出队代码。
if ( list ) {
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) {
list = [];
} else {
self.disable();
}
}
先把基本结构定义出来,函数的开始定义我们使用的变量。
jQuery.Callbacks = function( options ) {
var options = createOptions(options);
var
memory,
// Flag to know if list was already fired
// 是否已经 fire 过
fired,
// Flag to know if list is currently firing
// 当前是否还处于 firing 过程中
firing,
// First callback to fire (used internally by add and fireWith)
// fire 调用的起始下标
firingStart,
// End of the loop when firing
// 需要 fire 调用的队列长度
firingLength,
// Index of currently firing callback (modified by remove if needed)
// 当前正在 firing 的回调在队列的索引
firingIndex,
// Actual callback list
// 回调队列
list = [],
// Stack of fire calls for repeatable lists
// 如果不是 once 的,stack 实际上是一个队列,用来保存嵌套事件 fire 所需的上下文跟参数
stack = !options.once && [],
_fire = function( data ) {
};
var self = {
add : function(){},
remove : function(){},
has : function(){},
empty : function(){},
fireWith : function(context, args){
_fire([context, args]);
};
fire : function(args){
this.fireWith(this, args);
}
/* other function */
}
return self;
};
其中的 stack 用来保存在 fire 之后添加进来的函数。
而 firingIndex, firingLength 则用来保证在调用函数的过程中,我们还可以对这个队列进行操作。实现并发的处理。
我们从 add 函数开始。
add: function() {
if ( list ) { // 如果使用了 once,在触发完成之后,就不能再添加回调了。
// 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 );
}
} else if ( arg && arg.length && type !== "string" ) {
// Inspect recursively
add( arg );
}
});
})( arguments );
// Do we need to add the callbacks to the
// current firing batch? 正在 firing 中,队列长度发生了变化
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away 如果是 memory 状态,而且已经触发过了,直接触发, memory 是保存了上次触发的状态
} else if ( memory ) {
firingStart = start;
fire( memory );
}
}
return this;
},
删除就简单一些了。检查准备删除的函数是否在队列中,while 的作用是一个回调可能被多次添加到队列中。
// Remove a callback from the 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;
},
has, empty, disable, disabled 比较简单。
// 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;
},
锁住的意思其实是不允许再触发事件,stack 本身也用来表示是否禁止再触发事件。所以,通过直接将 stack 设置为 undefined,就关闭了再次触发事件的可能。
// Lock the list in its current state
lock: function() {
stack = undefined;
if ( !memory ) {
self.disable();
}
return this;
},
// Is it locked?
locked: function() {
return !stack;
},
fire 是暴露的触发方法。fireWith 则可以指定当前的上下文,也就是回调函数中使用的 this 。第一行的 if 判断中表示了触发事件的条件,必须存在 list,必须有 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;
}
};
真正的 fire 函数。
// Fire callbacks
fire = function( 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 ) {
memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if ( list ) {
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) {
list = [];
} else {
self.disable();
}
}
},
jQuery-2.1.3.js 中的 Callback 实现。
/*
* 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
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 = 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 ) {
memory = false; // To prevent further calls using add
break;
}
}
firing = false;
if ( list ) {
if ( stack ) {
if ( stack.length ) {
fire( stack.shift() );
}
} else if ( memory ) {
list = [];
} else {
self.disable();
}
}
},
// Actual Callbacks object
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, 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?
if ( firing ) {
firingLength = list.length;
// With memory, if we're not firing then
// we should call right away
} else if ( 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 ( ( 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
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;
};
jQuery 之 Callback 实现的更多相关文章
- jquery html() callback
通过JQuery的.html()函数我们可以非常方便地加载一段HTML到指定的元素中,例如给<div></div>中放入一组图片.问题是JQuery的.html()函数是同步的 ...
- jQuery 常用速查
jQuery 速查 基础 $("css 选择器") 选择元素,创建jquery对象 $("html字符串") 创建jquery对象 $(callback) $( ...
- jQuery初级篇(一)
知识说明: jQuery库是一个javascript库文件,它比起javascript来,写的更少,但做得更多,下面便对刚开始学习jQuery,一些基础知识整理出来,供后期翻阅. 一. jQ ...
- 动态加载(异步加载)jquery/MUI类库 页面加载完成后加载js类库
动态加载Mui类库: // ==UserScript== // @name // @version 1.4.0 // @author zzdhidden@gmail.com // @namespace ...
- jquery 原理
/* * my-jquery-1.0 *//* * 网上也有很多实现的版本,不过这是我在我自己的理解下写的,加上注释,希望可以解释清楚.*//* * 整个jquery包含在一个匿名函数中,专业点叫闭包 ...
- Jquery EasyUI封装简化操作
//confirm function Confirm(msg, control) { $.messager.confirm('确认', msg, function (r) { if (r) { eva ...
- jquery源码分析
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 前段时间上班无聊之时,研究了 ...
- 一篇介绍jquery很好的
本文基于jQuery1.7.1版本,是对官方API的整理和总结,完整的官方API见http://api.jquery.com/browser/ 0.总述 jQuery框架提供了很多方法,但大致上可以分 ...
- 26 个 jQuery使用技巧
1. 禁用右键点击(Disable right-click) $(document).ready(function(){ $(document).bind("contextmenu" ...
随机推荐
- Web Service 的工作原理
Web Service基本概念 Web Service也叫XML Web Service WebService是一种可以接收从Internet或者Intranet上的其它系统中传递过来的请求,轻量级的 ...
- android + red5 + rtmp
背景:在已有的red5服务器环境下实现android客户端的视频直播 要实现客户端视频直播就先先对服务器端有所了解 Red5流媒体服务器是Adboe的产品,免费并且是开源的,与Flash搭配的时候可谓 ...
- request 、response和session的区别
request: 1.request.getParameter("key")接受的是来自客户登陆端的数据,接受的是post或get方式传送的value. 2.请求的默认字符集是IS ...
- RabbitMQ入门教程——.NET客户端使用
众所周知RabbitMQ使用的是AMQP协议.我们知道AMQP是一种网络协议,能够支持符合要求的客户端应用和消息中间件代理之间进行通信. 其中消息代理扮演的角色就是从生产者那儿接受消息,并根据既定的路 ...
- java word文档 转 html文件
一.简介 一般word文件后缀有doc.docx两种.docx是office word 2007以及以后版本文档的扩展名:doc是office word 2003文档保存的扩展名.对于这两种格式的wo ...
- shell将标准错误输出重定向到 其他地方
经常可以在一些脚本,尤其是在crontab调用时发现如下形式的命令调用: /tmp/test.sh > /tmp/test.log >& 前半部分/tmp/test.sh > ...
- boneCP原理研究
** 转载请注明源链接:http://www.cnblogs.com/wingsless/p/6188659.html boneCP是一款关注高性能的数据库连接池产品 github主页 . 不过最近作 ...
- java JedisUtils工具类
package com.sh.xrsite.common.utils; import java.util.List; import java.util.Map; import java.util.Se ...
- Ubuntu13.04安装历险记--Mono,Nginx,Asp.Net一个都不能少
----Ubuntu13.04安装历险记--新人新手新作------------------------------------------------- 注:以下操作均省略权限获取操作,如有需要,请 ...
- JavaScript日期控件,用select实现
<!doctype html> <html> <head> <title>年月日</title> </head> <bod ...