惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
美团技术团队
H
Hacker News: Front Page
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
Simon Willison's Weblog
Simon Willison's Weblog
T
The Exploit Database - CXSecurity.com
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
C
CXSECURITY Database RSS Feed - CXSecurity.com
Application and Cybersecurity Blog
Application and Cybersecurity Blog
A
About on SuperTechFans
F
Fortinet All Blogs
量子位
GbyAI
GbyAI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Hacker News
The Hacker News
AWS News Blog
AWS News Blog
Forbes - Security
Forbes - Security
Help Net Security
Help Net Security
I
InfoQ
有赞技术团队
有赞技术团队
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
Engineering at Meta
Engineering at Meta
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
S
Secure Thoughts
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Webroot Blog
Webroot Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园_首页
C
Check Point Blog
T
Troy Hunt's Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Latest news
Latest news
P
Proofpoint News Feed
Jina AI
Jina AI
Last Week in AI
Last Week in AI
Martin Fowler
Martin Fowler
雷峰网
雷峰网
博客园 - Franky
L
LangChain Blog
罗磊的独立博客
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
D
Docker
G
GRAHAM CLULEY
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC

DaraW

GMTC 北京 2021 小程序开发实践专场所见所想 从一次 Bug 定位来看 Taro H5 的组件实现 浅谈 B 站新 BV 号的设计 复盘:Vue.js 是如何成功的 你可能不知道的 Git React 应用性能优化一例 Thrift RPC Mock 方案探索 前端视频质量监控 前端函数式编程 多说一句再见 《你不知道的JS上卷》阅读小记之setTimeout的this指向问题 ES6实现内部类的写法 IntelliJ IDEA在Linux下字体不正常解决方案 node-thunkify源码阅读笔记 如何监听JS变量的变化 制作列带有斑马条纹背景的表格 git用户名莫名其妙变化及挽救措施 记一次前端性能优化实战 将setTimeout函数队列
JS实现自定义事件
DaraW · 2016-08-03 · via DaraW

要求

请实现下面的自定义事件Event对象的接口,功能见注释(测试1)
该Event对象的接口需要能被其他对象拓展复用(测试2)

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

Event.on("test", function(result) {
console.log(result);
})
Event.on("test", function(result) {
console.log("test");
})
Event.emit("test", "hello world");

var person1 = {};
var person2 = {};

Object.assign(person1, Event);
Object.assign(person2, Event);

person1.on("call1", function() {
console.log("person1");
});
person2.on("call2", function() {
console.log("person2");
});

person1.emit("call1");
person1.emit("call2");
person2.emit("call1");
person2.emit("call2");

思路

这是个经典的自定义事件,实现”Pub/Sub”,Node.js中有个比较完善的实现EventEmitter

原理则是构造出一个集成队列的对象,每一个事件对应对象的一个队列,在自定义事件后将回调函数入队,触发事件后回调函数依次出队并执行。

个人偏爱IIFE+构造器模式+原型模式,于是有了下面的一个简易实现:

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
30
31
32
33
(function (exports) {

function EventEmitter() {}

EventEmitter.prototype.on = function (type, handler) {
this._cbQueue = this._cbQueue || {};
this._cbQueue[type] = this._cbQueue[type]|| [];
this._cbQueue[type].push(handler);
return this;
};

EventEmitter.prototype.emit = function (type, data) {
if (this._cbQueue[type]) {
this._cbQueue[type].forEach(function (cb) {
cb(data);
});
}
};

exports.EventEmitter = EventEmitter;

}(global));

var Event = new EventEmitter();


Event.on("test", function(result) {
console.log(result);
})
Event.on("test", function(result) {
console.log("test");
})
Event.emit("test", "hello world");

这样便满足了测试1的要求,然而这样对于测试2是不行的,因为Object.assign()方法不能复制不可遍历的属性和继承属性,也就意味着Event对象上的onemit不能被复制过去。那么复制Event.prototype对象呢?这样确实是可行的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

var person1 = {};
var person2 = {};

Object.assign(person1, EventEmitter.prototype);
Object.assign(person2, EventEmitter.prototype);

person1.on("call1", function() {
console.log("person1");
});
person2.on("call2", function() {
console.log("person2");
});

person1.emit("call1");
person1.emit("call2");
person2.emit("call1");
person2.emit("call2");

虽然差不多实现了题目中的要求,但是和题目中的要求依然有些偏差,于是我又想到了,如果放弃原型模式+构造器模式,单纯的把Event作为一个对象而不是一个函数呢?
实现很简单:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
(function (exports) {

var Event = {};

Event.on = function (type, handler) {
this._cbQueue = this._cbQueue || {};
this._cbQueue[type] = this._cbQueue[type]|| [];
this._cbQueue[type].push(handler);
return this;
};

Event.emit = function (type, data) {
if (this._cbQueue[type]) {
this._cbQueue[type].forEach(function (cb) {
cb(data);
});
}
};

exports.Event = Event;

}(global));



Event.on("test", function(result) {
console.log(result);
})
Event.on("test", function(result) {
console.log("test");
})
Event.emit("test", "hello world");



var person1 = {};
var person2 = {};

Object.assign(person1, Event);
Object.assign(person2, Event);

person1.on("call1", function() {
console.log("person1");
});
person2.on("call2", function() {
console.log("person2");
});

person1.emit("call1");
person1.emit("call2");
person2.emit("call1");
person2.emit("call2");

测试1的结果如预期一样,然而测试2却出了问题,debug看了一下Event对象,Event.on执行后上面竟然有个可以遍历的属性_cbQueue,而且_cbQueue是一个对象而不是一个字面量,所以在Object.assign()拷贝的过程中,将Event._cbQueue对象引用赋值给了person1._cbQueueperson2._cbQueue,也就是说这三者指向了内存中的同一个对象,只要修改一个,其他几个都会跟着修改;当不执行Event.on时,Event上就不会有属性_cbQueue,那么接下来person1person2执行on方法后,this指向了他们本身,会创造他们自己的互不影响的_cbQueue属性。
但不执行Event.on这不是解决的完美办法,不过问题已经定位了,解决也很简单,修改Event对象的属性_cbQueue为不可遍历,让拷贝过程不拷贝属性_cbQueue即可:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
(function (exports) {

var Event = {};

Object.defineProperty(Event, "_cbQueue", {
value : {},
writable : true,
enumerable : false,
configurable : true
});

Event.on = function (type, handler) {
this._cbQueue = this._cbQueue || {};
this._cbQueue[type] = this._cbQueue[type]|| [];
this._cbQueue[type].push(handler);
return this;
};

Event.emit = function (type, data) {
if (this._cbQueue[type]) {
this._cbQueue[type].forEach(function (cb) {
cb(data);
});
}
};

exports.Event = Event;

}(global));



Event.on("test", function(result) {
console.log(result);
})
Event.on("test", function(result) {
console.log("test");
})
Event.emit("test", "hello world");


var person1 = {};
var person2 = {};

Object.assign(person1, Event);
Object.assign(person2, Event);

person1.on("call1", function() {
console.log("person1");
});
person2.on("call2", function() {
console.log("person2");
});

person1.emit("call1");
person1.emit("call2");
person2.emit("call1");
person2.emit("call2");

这样便算完美实现了题目的要求。

总结

其实原理很简单,没想到在周边的实现上浪费了一些debug的时间,有些惭愧。