首页 > 代码库 > 关于js模拟c#的Delegate(委托)实现

关于js模拟c#的Delegate(委托)实现

这是我的第一篇博文,想来讲一讲js的函数。我的标题是js模拟c#的Delegate。

 

一、什么是Delegate(委托)

在jquery中有delegate函数,作用是将某个dom元素的标签的事件委托给一个函数队列,在触发这个事件的时候会触发这个函数队列中的所有函数。而c#中的Delegate对象也是如此,将多个方法添加至一个委托对象,或称将多个方法委托给一个委托对象,当调用委托对象的Invoke方法时会调用所有被委托的方法。由此可以看出Delegate的本质应该是一个函数队列,执行委托对象就是遍历执行函数队列。

 

二、实现委托构造函数

明白了委托对象的本质是一个函数队列,就可以着手建立委托对象了。先写一个简单的委托的构造函数,这个构造函数返回的对象实现以下功能

1、add();                             添加函数

2、remove();                        移除函数

3、();                                  调用委托

 1 function SimpleDelegate() { 2     var _fnQuene = []; 3     var delegate=function(){ 4         for(var i=0;i<_fnQuene.length;i++){ 5             _fnQuene[i].apply(this,arguments); 6         } 7     } 8     delegate.add = function (fn) { 9         //只有函数才能被添加到函数队列10         if(Object.prototype.toString.call(fn)===‘[object Function]‘){11             _fnQuene.push(fn);12         }13     }14     delegate.remove = function (fn) {15         _fnQuene.splice(_fnQuene.indexOf(fn), 1);16     }17     return delegate;18 }

 

这个函数已经能实现基本的添加移除和调用,如果使用规范,这个函数没有任何问题。但如果使用不规范,会造成严重的后果,比如下面的代码。在后面的代码中执行的时候会不断添加函数去调用add函数,这样一来就造成了死循环。

 1 //正确的使用委托 2 var d1=new SimpleDelegate(); 3 d1.add(function(){ 4     console.log(1); 5 }) 6 d1.add(function(){ 7     console.log(2); 8 }) 9 d1();   //打印1,210 11 //不规范使用,造成死循环12 var d2=new SimpleDelegate();13 d2.add(function(){14     d2.add(function(){15         console.log(1);16     })17 })18 d2();

 

我们需要修改这个函数,首先我们发现在调用委托的时候会获取_fnQuene的length值,而这个length是不断在变化的,每添加一个函数长度就会增加,于是就产生了循环调用add造成死循环的问题,如果我们在调用前获取length的值并缓存起来就不用担心添加的问题了

1 var delegate = function () {2         var len = _fnQuene.length;3         for (var i = 0; i < len; i++) {4             _fnQuene[i].apply(this, arguments);5         }6     }

执行结果变成如下,不再死循环

var d2 = new SimpleDelegate();d2.add(function () {    console.log(1);    d2.add(function () {        console.log(2);    })})d2();   //第一次执行打印1d2();   //第二次执行打印1,2d2();   //第三次执行打印1,2,2

 

但这样还是不够,我们只解决了add的问题,如果在函数中调用remove呢,这样好像缓存length也不行

 1 var d=new SimpleDelegate(); 2 function f1(){ 3     console.log(1); 4     d.remove(f2); 5 } 6 function f2(){ 7     console.log(2); 8 } 9 d.add(f1);10 d.add(f2)11 d();        //执行到索引为1的函数,函数已经被删除,报错

 

add和remove函数并不靠谱,我们应该修改的是Delegate的机制,之前,我们调用add和remove会直接将操作直接作用在fnQuen上,但当我们开始执行委托后就不应该对fnQuene进行操作。如果添加的函数对fnQuene进行操作,应当把操作缓存下来,并在下一次调用委托之前执行这些操作,并在执行后立刻删除这些操作。如此一想,为什么我们不把每一次操作都缓存下来呢?每次委托执行前添加和移除,委托一旦开始执行,调用的add和remove函数又会缓存相应的操作。

 1 function SimpleDelegate() { 2     var _fnQuene = []; 3     var _waitingQuene = []; 4  5     var delegate = function () { 6         var len = _waitingQuene.length; //缓存本次执行委托时已有的操作数量 7         var i; 8         //首先调用所有缓存的操作 9         for (i = 0; i < len; i++) {10             _waitingQuene[i]();11         }12 13         len = _fnQuene.length;    //缓存当前函数队列的长度14         for (i = 0; i < len; i++) {15             _fnQuene[i].apply(this, arguments);16         }17     }18 19     delegate.add = function (fn) {20         _waitingQuene.push(function(){21             _fnQuene.push(fn);22         })23     }24 25     delegate.remove = function (fn) {26         _waitingQuene.push(function(){27             _fnQuene.splice(_fnQuene.indexOf(fn),1);28         })29     }30     return delegate;31 }

 

最后为了这个函数使用更加方便,添加链式编程并支持一些重载,以及为对象添加一些属性

 1 function cloneArray(arr) { 2     var ret = []; 3     //这里不用map是因为arr可以是类数组对象 4     for (var i = 0; i < arr.length; i++) { 5         ret[i] = arr[i]; 6     } 7     return ret; 8 } 9 10 function Delegate() {11     var _fnQuene = [];12     var _waitingQuene = [];13 14     var delegate = function () {15         var len = _waitingQuene.length; //缓存本次执行委托时已有的操作数量16         var i;17         //首先调用所有缓存的操作18         for (i = 0; i < len; i++) {19             _waitingQuene[i]();20         }21         _waitingQuene.length = 0;22 23         len = _fnQuene.length;    //缓存当前函数队列的长度24         for (i = 0; i < len; i++) {25             _fnQuene[i].apply(this, delegate._argIntercept ?26                 cloneArray(arguments) :27                 arguments28             );29         }30     }31 32     delegate.add = function (fn) {33         var args = arguments;34         var arg;35         var type;36         var self = this;37         function add() {38             for (var i = 0; i < args.length; i++) {39                 arg = args[i];40                 type = Object.prototype.toString.call(arg);41                 if (type === [object Array]) {42                     add.apply(self, arg);43                 }44                 else if (type === [object Function]) {45                     _fnQuene.push(arg);46                 }47             }48         }49         _waitingQuene.push(add);50         return this;51     }52 53     delegate.remove = function (fn) {54         var args = arguments;55         var arg;56         var type;57         var self = this;58         function remove() {59             for (var i = 0; i < args.length; i++) {60                 arg = args[i];61                 type = Object.prototype.toString.call(arg);62                 if (type === [object Array]) {63                     remove.apply(self, arg);64                 }65                 else if (type === [object Function]) {66                     var idx = _fnQuene.indexOf(arg);67                     if (idx === -1) {68                         continue;69                     }70                     _fnQuene.splice(idx, 1);71                 }72             }73         }74         _waitingQuene.push(remove);75         return this;76     }77 78     //检查某个函数是否委托给了当前委托对象79     delegate.has = function (fn) {80         return _fnQuene.indexOf(fn) !== -1;81     }82 83     Object.defineProperties(delegate, {84         //委托中函数的数量85         _length: {86             get: function () {87                 return _fnQuene.length;88             }89         },90 91         //是否拦截参数,如果为true,则委托被调用时传给函数的参数为副本92         _argIntercept: {93             value: false,94         }95     })96 97     delegate.constructor = Delegate;98     return delegate;99 }

 

关于js模拟c#的Delegate(委托)实现