关于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 }
				
时间: 2024-08-27 11:14:22

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

单篇文章JS模拟分页

废话部分 前两天做了一个前台分页插件,支持ajax读取数据绑定前台 和 url带页码参数跳转两种方式.于是稍加改动,做了一个单篇文章js模拟分页的代码,为什么说是模拟分页呢?因为在服务器响应HTML请求的时候,就已经把全文回传给客户端了,只是我们通过js的方式,把全文隐藏,每次翻页至显示出我们需要的那一部分,而不是真正的按需要去发出HTML请求.所以,在做这个插件的时候去掉了ajax请求的功能及其附带参数,去掉了pageSize参数(恒等于1).这里就不讨论具体的技术细节了和上一篇的分页计算原理

由chrome剪贴板问题研究到了js模拟鼠标键盘事件

写在前面 最近公司在搞浏览器兼容的事情,所有浏览器兼容的问题不得不一个人包了.下面来说一下今天遇到的一个问题吧 大家都知道IE下面如果要获得剪贴板里面的信息的话,代码应该如下所示 window.clipboardData.getData("Text") 可是在chrome下面就行不通了,chrome下面没有类似ie的这种方法,那应该怎么办呢,百度了一下,发现还真有办法. 只要在HTML界面上放上一个text类型的控件,如下所示 <textarea id="textAre

JS模拟时钟

<html> <head> <title>js模拟时钟</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> </head> <body onLoad="setInterval(setTimeSpan,1000);"> <span style="fon

静态书架和js模拟翻书效果

书籍图片随便找了个,有点难看,须要的自己替换个好看点的png格式图片 源代码下载:http://download.csdn.net/detail/sweetsuzyhyf/7604091 静态书架和js模拟翻书效果,布布扣,bubuko.com

纯js模拟 radio和checkbox控件

代码待优化,功能实现了,不兼容ie8以上, 相同name的radio可以实现切换的操作, 分享代码,共同学习进步 <!doctype html> <html> <head> <meta charset="utf-8"> <title></title> <style> .radiobox, .checkbox { width: 10px; height: 10px; padding: 2px; borde

js模拟抛出球运动

js练手之模拟水平抛球运动 -匀加速运动 -匀减速运动 代码如下: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>js模拟抛出球运动</titl

JS~模拟表单在新窗口打开,避免广告拦截

说起广告拦截,这应该是浏览器的一个特性,它会将window.open产生的窗口默认为一个广告,将它进行拦截,但有时,这不是我们所希望的,有时,我们就是需要它在客户端的浏览器上弹出一个新窗口,以展示数据处理的更新结果,例如,一个创建商品的预览功能,它需要先保存数据,然后再在新窗口展示最新的信息,这种需求并不少,而大多数人的作法就是使用window.open去弹窗口,但它确实不是一种好的方式! 新方式来了 我们知道表单提交实际上可以把POST的结果响应到新窗口上,我们就是利用表单的这种性质,在JS中

js模拟jq获取id

js模拟jq获取id: (jquery)需要自己添加 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>js模拟jq的点击效果</title> <style> * {margin: 0; padding: 0;} #btn {display:block;height: 30px; width:

ul+js模拟select+改进

html: <div class="select_box"> <input type="text" value="还款方式" readonly="readonly"> <ul class="select_ul cur" style="display: none;"> <li class="sel_value">所有还款方