原文链接:https://www.cnblogs.com/kazetotori/p/5842379.html
一、什么是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 }
原文链接:https://www.cnblogs.com/kazetotori/p/5842379.html
转来自己看,上面代码中的片段如下,这里不会造成死循环,但是会造成多次添加事件。
11 //不规范使用,造成死循环
d2.add(function () {
console.log(1);
d2.add(function () {
console.log(2);
})
})
因为每次调用d2()的时候都会把添加的函数全部执行一遍,这样每次调用d2()就会执行add函数,比如第一次调用,如果i<_fnQuene.length这样的(for(var i=0;i<_fnQuene.length;i++){ 5 _fnQuene[i].apply(this,arguments); 6 })首先会调用输出1的回调函数,然后事件队列_fnQuene长度加1,会继续执行代码:
d2.add( function(){
console.log(2);
} )
因此博客才会说要将事件队列保存起来, var len = _fnQuene.length; 如果需求是执行事件队列中函数时不可以添加函数,那么就需要这样做!