jQuery使用():Callbacks回调函数列表之异步编程(含源码分析)
- Callbacks的基本功能回调函数缓存与调用
- 特定需求模式的Callbacks
- Callbacks的模拟源码
一、Callbacks的基本功能回调函数缓存与调用
Callbacks即回调函数集合,在jQeury中配合异步操作实现异步回调任务,同时也具备函数解耦的基础特性。简单的说就是缓存回调函数的集合,所以这个方法应该具备的两个基础功能就是向集合中添加回调函数和触发回调函数的方法。先来看一下jQuery的这两个方法的使用:
- Callbacks.add() —— 添加回调函数
- Callbacks.fire() —— 执行回调函数
1 function fon1(){ 2 console.log("函数一"); 3 } 4 function fon2(a){ 5 console.log("函数二"); 6 } 7 var call = $.Callbacks();//创建回调函数对象 8 call.add(fon1,fon2);//添加回调函数 9 call.fire();//执行回调函数
基于这样的基本功能模仿实现Callbacks的功能:
1 function Callback(){ 2 var list = []; 3 var self = { 4 add:function(){ 5 if(arguments.length > 0){ 6 for(var ele in arguments){ 7 list.push(arguments[ele]); 8 } 9 } 10 return this; 11 }, 12 fire:function(){ 13 if(list.length > 0){ 14 for(var ele in list){ 15 list[ele](); 16 } 17 } 18 return this; 19 } 20 } 21 return self; 22 }
上面的简单模仿可以实现添加回调函数和执行回调函数,接着来看jQuery实现的不同模式的回调列表对象。
二、特定需求模式的回调函数
1. 如果Callback只是上面模仿的那样的话,那就等于是回调函数裸奔,如果有需求是回调函数只能被触发一次,也就是说重复调用fire无效,要实现这个功能只需要在回调函数执行完以后将回调函数列的值修改成undefined即可,然后再在添加和执行函数上加一个判断阻断执行就可以。下面是修改过后的代码:
1 //Callback源码 2 function Callback(options){ 3 //list为undefined的时候(函数列表被禁用),主动触发disable禁用 4 //once模式下回调函数列表执行一轮后禁用回调函数列表 5 var list = []; 6 options = options || ""; 7 var fire = function(data){ 8 //实现回调函数执行的方法 9 if(list.length > 0){ 10 for(var ele in list){ 11 list[ele].apply(data[0],data[1]); 12 } 13 } 14 if(options.indexOf("once") != -1){ 15 //清空函数列表,实现once只能调用fire执行一次的功能 16 list = undefined; 17 } 18 } 19 var self = { 20 add:function(){ 21 if(list){//函数被锁定或者禁用就不能再添加回调函数了 22 if(arguments.length > 0){ 23 for(var ele in arguments){ 24 list.push(arguments[ele]); 25 } 26 } 27 } 28 return this; 29 }, 30 disable:function(){//禁用回调函数 31 list = undefined; 32 return this; 33 }, 34 disabled:function(){//查看回调函数是否被禁用 35 return !list; 36 }, 37 fireWith:function(context,args){ 38 //因为fire作为作用域上的方法this指向window或者undefined 39 //这里将this指向继续指向self,然后将类数组的args转换成数组(fireWith的功能) 40 if(list){ 41 args = args || []; 42 var data = [context,args.slice ? args.slcie() : args]; 43 fire(data); 44 } 45 return this; 46 }, 47 fire:function(){ 48 //fire作为回调函数执行的API 49 self.fireWith(this,arguments); 50 return this; 51 } 52 } 53 return self; 54 }
实现“once”模式的回调函数列表之际代码只在14行~17行,然后add和fireWith方法内加一层判断即可。在实现“once”模式的时候考虑到后面还有其他模式和一些回调函数对象工具方法的实现,将fire()方法模仿jQuery源码提取到了对象作用域作为普通方法,回调函数对象上保留操作接口只负责调用执行,fireWith()用来调整执行的上下文和参数类型。顺便还实现了一个禁用回调函数方法disble方法。
2. 接着来看看第二个回调函数模式“memory”以及“once memory”的混合模式回调函数对象的实现,“memory”模式就是在触发一次函数列表以后,后面添加的函数会被自动触发,这个实现非常的简单,只要记录一下上一次执行的列表的最后一个回调函数的下标,不过要注意的是,如果是"once memory"是混合模式需要缓存参数,还有下标要置为0。具体代码如下,模仿的代码相对于jQuery源码中的Callbacks工具方法表面逻辑清晰一些,但是没有像jQuery源码那样极致的避免冗余,所以如果是学习源码的最优设计方案而非Callbacks的设计结构请绕路。
1 //Callback源码 2 function Callback(options){ 3 //list为undefined的时候(函数列表被禁用),主动触发disable禁用 4 //once模式下回调函数列表执行一轮后禁用回调函数列表 5 var list = []; 6 options = options || ""; 7 var fired = false; 8 var fireStart; 9 var fireIndex; 10 var memoryData = null; 11 var fire = function(data){ 12 fireIndex = fireStart || 0; 13 fireStart = 0; 14 //实现回调函数执行的方法 15 if(list.length > 0){ 16 for(;fireIndex < list.length; fireIndex++){ 17 list[fireIndex].apply(data[0],data[1]); 18 } 19 } 20 if(options.indexOf("once") != -1){ 21 //清空函数列表,实现once只能调用fire执行一次的功能 22 list = undefined; 23 fireIndex = 0; 24 } 25 if(options.indexOf("memory") != -1){ 26 fireStart = fireIndex; 27 fired = true; 28 list = list || []; 29 memoryData = data; 30 } 31 32 } 33 var self = { 34 add:function(){ 35 if(list){//函数被锁定或者禁用就不能再添加回调函数了 36 if(arguments.length > 0){ 37 for(var ele in arguments){ 38 list.push(arguments[ele]); 39 } 40 } 41 } 42 if(options.indexOf("memory") != -1 && fired){ 43 fire(memoryData); 44 } 45 return this; 46 }, 47 disable:function(){//禁用回调函数 48 list = undefined; 49 return this; 50 }, 51 disabled:function(){//查看回调函数是否被禁用 52 return !list; 53 }, 54 fireWith:function(context,args){ 55 //直接调用API时表示fire()非add触发,所以fireStart要设置为0 56 fireStart = 0; 57 //因为fire作为作用域上的方法this指向window或者undefined 58 //这里将this指向继续指向self,然后将类数组的args转换成数组(fireWith的功能) 59 if(list){ 60 args = args || []; 61 var data = [context,args.slice ? args.slcie() : args]; 62 fire(data); 63 } 64 return this; 65 }, 66 fire:function(){ 67 //fire作为回调函数执行的API 68 self.fireWith(this,arguments); 69 return this; 70 } 71 } 72 return self; 73 }
三、Callbacks源码模仿实现
在第二部分主要是是实现了once、memory模式,再在这个基础上实现unique、stopOnFalse模式,本来项将remove、lock等这些回调对象的工具方法都实现,我还是放弃了,因为太简单了,实现起来又会占用很多页面,不方便理解更核心的代码,所以下面是在第二部分的基础上添加了unique、stopOnFalse模式的全部代码:
1 //Callback源码 2 function Callback(options){ 3 //list为undefined的时候(函数列表被禁用),主动触发disable禁用 4 //once模式下回调函数列表执行一轮后禁用回调函数列表 5 var list = []; 6 options = options || ""; 7 var fired = false; 8 var fireStart; 9 var fireIndex; 10 var memoryData = null; 11 var memory = true; 12 //stopOnFalse 模式表示中断执行,即使"memory stopOnFalse"混合模式,后面添加方法也不会触发执行,而必须callback.fire()方法来触发。 13 var stopOnFalse = options.indexOf("stopOnFalse") != -1 ? true : false; 14 var fire = function(data){ 15 fireIndex = fireStart || 0; 16 fireStart = 0; 17 //实现回调函数执行的方法 18 if(list.length > 0){ 19 for(;fireIndex < list.length; fireIndex++){ 20 if( list[fireIndex].apply(data[0],data[1]) === false && stopOnFalse ){ 21 memory = false; 22 break; 23 } 24 } 25 } 26 if(options.indexOf("once") != -1){ 27 //清空函数列表,实现once只能调用fire执行一次的功能 28 list = undefined; 29 fireIndex = 0; 30 } 31 if(options.indexOf("memory") != -1 && memory){ 32 fireStart = fireIndex; 33 fired = true; 34 list = list || []; 35 memoryData = data; 36 } 37 } 38 var self = { 39 add:function(){ 40 if(list){//函数被锁定或者禁用就不能再添加回调函数了 41 if(arguments.length > 0){ 42 if(options.indexOf("unique") != -1){//unique模式--不重复添加同一个函数 43 for(var ele in arguments){ 44 if(list.indexOf(arguments[ele]) == -1){ 45 list.push(arguments[ele]); 46 } 47 } 48 }else{ 49 for(var ele in arguments){ 50 list.push(arguments[ele]); 51 } 52 } 53 } 54 } 55 if(options.indexOf("memory") != -1 && fired){ 56 fire(memoryData); 57 } 58 return this; 59 }, 60 disable:function(){//禁用回调函数 61 list = undefined; 62 return this; 63 }, 64 disabled:function(){//查看回调函数是否被禁用 65 return !list; 66 }, 67 fireWith:function(context,args){ 68 //直接调用API时表示fire()非add触发,所以fireStart要设置为0 69 fireStart = 0; 70 //因为fire作为作用域上的方法this指向window或者undefined 71 //这里将this指向继续指向self,然后将类数组的args转换成数组(fireWith的功能) 72 if(list){ 73 args = args || []; 74 var data = [context,args.slice ? args.slice() : args]; 75 fire(data); 76 } 77 return this; 78 }, 79 fire:function(){ 80 //fire作为回调函数执行的API 81 self.fireWith(this,arguments); 82 return this; 83 } 84 } 85 return self; 86 }
最后,这篇博客主要就是为了实现了Callbacks的源码,并非对其应用进行解析,毕竟在很多文档中都有很详细的说明,如果觉得文档不是很容易明白,可以参考这篇博客:jQuery.Callbacks源码及其应用分析