事件委托,事件代理,pub/sub 是工作常用的工作模式,也许只是你没有注意到而已。

事件委托

事件委托:当你通过动态添加元素的时候,如果想要为这个元素绑定事件不能够直接使用诸如onclick方法,只能在其父元素绑定事件,常见于通过 ajax 请求数据。

简单地写如下示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 通过target来实现来辨别是不是那个元素
function delegate(dom, eventType, selector, fn) {
dom.addEventListener(eventType, function (e) {
// var target = e.target;
// for (target !== dom; target = e.target;) {
//
// }
var cur = e.target, target = e.target, cur;
for (; cur != this; cur = target.parentNode || this) {
if (cur.classList.contains(selector)) {
fn.call(cur, e);
break;
}
}
}, false);
}

即监听事件的 target 属性来和 selector 进行比对,若是则应用回调函数。

事件代理

事件代理: 即为改变函数的上下文。

使用场景:常见于 DOM 事件的绑定上下文的改变。之前有在用 Backbone 的时候经常会这样用:

this.$el.on('click', '.item', $.proxy(this, this.itemClick)) 。

用于在 itemClick 绑定的事件中让其上下文为对象。而不是 DOM 元素。翻看 jQuery 源码可见如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
proxy: function( fn, context ) {
var tmp, args, proxy;
if ( typeof context === "string" ) {
tmp = fn[ context ];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
args = slice.call( arguments, 2 );
proxy = function() {
return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
}

还是很容易理解的, 说到改变函数上下文还可以使用 bind

具体可查看 这里

发布者订阅者模式

发布进行订阅模式是常用的模式比如在 Dom 事件监听的时候就是这种模式。

1
2
$dom.on('click', fn);
$dom.trigger('click', args);

以上 on 即为订阅,trigger 即为发布。这个对于理解 JavaScript 框架中的双向监听是有益处的。可以手写一个如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var pubSub = {
subscribers: [],
publish: function (eventType) {
var slice = Array.prototype.slice;
var args = slice.apply(arguments);
this.subscribers.forEach(function (subscriber) {
if (subscriber.eventType === eventType) {
subscriber.cb.apply(undefined, args.slice(1))
}
})
},
subscribe: function (eventType, cb) {
var isSubscribed = false;
for (var i = 0; i < this.subscribers.length; i++) {
if (this.subscribers.eventType === eventType) {
isSubscribed = true;
this.subscribers[i].cb = cb;
break;
}
}
if (!isSubscribed) {
this.subscribers.push({
eventType: eventType,
cb: cb
})
}
}
}

使用:

1
2
3
4
5
6
7
// 不带参数
pubSub.subscribe('event1', function () { console.log('订阅者执行'); });
pubSub.publish('event1');
// 带参数
pubSub.subscribe('event2', function (p1, p2) { console.log('p1', p1); });
pubSub.publish('event2', 2, 3);

总结

以上为自己的对于常用的事件委托,事件代理的理解及发布者订阅进行的理解。

Todolist:

  • 那么Nodejs中的事件机制又是如何的呢?