关于js模拟c#的Delegate(委托)实现
Posted 风邪取鸟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于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,2 10 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(); //第一次执行打印1 d2(); //第二次执行打印1,2 d2(); //第三次执行打印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 arguments 28 ); 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(委托)实现的主要内容,如果未能解决你的问题,请参考以下文章