Skip to content

Commit 1b6cf3b

Browse files
committed
2020.02.27
1 parent 306b08b commit 1b6cf3b

File tree

2 files changed

+592
-0
lines changed

2 files changed

+592
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
在前端开发世界中,JavaScript 和 HTML 之间往往通过 **事件** 来实现交互。其中多数为内置事件,本文主要介绍 JS**自定义事件概念和实现方式**,并结合案例详细分析自定义事件的原理、功能、应用及注意事项。
2+
3+
# 📚一、什么是自定义事件
4+
在日常开发中,我们习惯监听页面许多事件,诸如:点击事件( `click` )、鼠标移动事件( `mousemove` )、元素失去焦点事件( `blur` )等等。<br />
5+
<br />事件本质是一种通信方式,是一种消息,只有在多对象多模块时,才有可能需要使用事件进行通信。在多模块化开发时,可以使用**自定义事件**进行模块间通信。<br />
6+
<br />当某些基础事件无法满足我们业务,就可以尝试 **自定义事件**来解决。
7+
8+
# 📚二、实现方式介绍
9+
10+
目前实现**自定义事件**的两种主要方式是 JS 原生的 `Event()` 构造函数和 `CustomEvent()` 构造函数来创建。<br />
11+
12+
## 1. Event()
13+
`Event()` 构造函数, 创建一个新的事件对象 `Event`
14+
15+
### 1.1 语法
16+
```javascript
17+
let myEvent = new Event(typeArg, eventInit);
18+
```
19+
20+
### 1.2 参数
21+
`typeArg`  : `DOMString` 类型,表示创建事件的名称;<br />`eventInit` :可选配置项,包括:
22+
23+
| 字段名称 | 说明 | 是否可选 | 类型 | 默认值 |
24+
| :---: | :---: | :---: | :---: | :---: |
25+
| `bubbles` | 表示该事件**是否冒泡**| 可选 | `Boolean`  | false |
26+
| `cancelable` | 表示该事件**能否被取消**| 可选 | `Boolean`  | false |
27+
| `composed` | 指示事件是否会在**影子DOM根节点之外**触发侦听器。  | 可选 | `Boolean`  | false |
28+
29+
30+
### 1.3 演示示例
31+
```javascript
32+
// 创建一个支持冒泡且不能被取消的 pingan 事件
33+
let myEvent = new Event("pingan", {"bubbles":true, "cancelable":false});
34+
document.dispatchEvent(myEvent);
35+
36+
// 事件可以在任何元素触发,不仅仅是document
37+
testDOM.dispatchEvent(myEvent);
38+
```
39+
40+
### 1.4 兼容性
41+
![image.png](https://user-gold-cdn.xitu.io/2020/2/22/1706b7199abd2e9e?w=1265&h=326&f=png&s=50137)<br />图片来源:[https://caniuse.com/](https://caniuse.com/)
42+
43+
## 2. CustomEvent()
44+
`CustomEvent()` 构造函数, 创建一个新的事件对象 `CustomEvent`
45+
46+
### 2.1 语法
47+
48+
```javascript
49+
let myEvent = new CustomEvent(typeArg, eventInit);
50+
```
51+
52+
### 2.2 参数
53+
`typeArg`  : `DOMString` 类型,表示创建事件的名称;<br />`eventInit` :可选配置项,包括:
54+
55+
| 字段名称 | 说明 | 是否可选 | 类型 | 默认值 |
56+
| :---: | :---: | :---: | :---: | :---: |
57+
| `detail` | 表示该事件中需要被传递的数据,在 `EventListener` 获取。 | 可选 | `Any`  | null |
58+
| `bubbles` | 表示该事件**是否冒泡**| 可选 | `Boolean`  | false |
59+
| `cancelable` | 表示该事件**能否被取消**| 可选 | `Boolean`  | false |
60+
61+
### 2.3 演示示例
62+
```javascript
63+
// 创建事件
64+
let myEvent = new CustomEvent("pingan", {
65+
detail: { name: "wangpingan" }
66+
});
67+
68+
// 添加适当的事件监听器
69+
window.addEventListener("pingan", e => {
70+
alert(`pingan事件触发,是 ${e.detail.name} 触发。`);
71+
});
72+
document.getElementById("leo2").addEventListener(
73+
"click", function () {
74+
// 派发事件
75+
window.dispatchEvent(pingan2Event);
76+
}
77+
)
78+
```
79+
![image.png](https://user-gold-cdn.xitu.io/2020/2/22/1706b7199ae8f81e?w=457&h=140&f=png&s=7499)<br />
80+
<br />我们也可以给自定义事件添加属性:
81+
82+
```javascript
83+
myEvent.age = 18;
84+
```
85+
86+
### 2.4 兼容性
87+
![image.png](https://user-gold-cdn.xitu.io/2020/2/22/1706b7199af32342?w=1261&h=331&f=png&s=54279)<br />图片来源:[https://caniuse.com/](https://caniuse.com/)
88+
89+
### 2.5 IE8 兼容
90+
分发事件时,需要使用 `dispatchEvent` 事件触发,它在 IE8 及以下版本中需要进行使用 `fireEvent` 方法兼容:
91+
92+
```javascript
93+
if(window.dispatchEvent) {
94+
window.dispatchEvent(myEvent);
95+
} else {
96+
window.fireEvent(myEvent);
97+
}
98+
```
99+
100+
## 3. Event() 与 CustomEvent() 区别
101+
从两者支持的参数中,可以看出:<br />`Event()` 适合创建简单的自定义事件,而 `CustomEvent()` 支持参数传递的自定义事件,它支持 `detail` 参数,作为事件中**需要被传递的数据**,并在 `EventListener` 获取。
102+
103+
**注意:**<br />当一个事件触发时,若相应的元素及其上级元素没有进行事件监听,则不会有回调操作执行。 <br />当需要对于子元素进行监听,可以在其父元素进行事件托管,让事件在事件冒泡阶段被监听器捕获并执行。此时可以使用 `event.target` 获取到具体触发事件的元素。
104+
105+
# 📚三、使用场景
106+
**事件本质是一种消息**,事件模式本质上是**观察者模式**的实现,即能用**观察者模式**的地方,自然也能用**事件模式**。<br />
107+
108+
## 1.场景介绍
109+
比如这两种场景:<br />
110+
111+
- **场景1:单个目标对象发生改变,需要通知多个观察者一同改变。**
112+
113+
如:当微博列表中点击“关注”,此时会同时发生很多事:推荐更多类似微博,个人关注数增加...<br />![image.png](https://user-gold-cdn.xitu.io/2020/2/22/1706b7199d02caa1?w=1394&h=550&f=png&s=329270)
114+
115+
- **场景2:解耦多模块开协作。**
116+
117+
如:小王负责A模块开发,小陈负责B模块开发,模块B需要模块A正常运行之后才能执行。
118+
119+
## 2. 代码实现
120+
121+
### 2.1 场景1实现
122+
**场景1:单个目标对象发生改变,需要通知多个观察者一同改变。**<br />本例子模拟三个页面进行演示:<br />1.微博列表页(Weibo.js)<br />2.粉丝列表页(User.js)<br />3.微博首页(Home.js)
123+
124+
**微博列表页(Weibo.js)**中,我们导入其他两个页面,并且监听【关注微博】按钮的点击事件,在回调事件中,创建一个自定义事件 `focusUser`,并在 `document` 上使用 `dispatchEvent` 方法派发自定义事件。
125+
```javascript
126+
// Weibo.js
127+
import UserModule from "./User.js";
128+
import HomeModule from "./Home.js";
129+
const eventButton = document.getElementById("eventButton");
130+
eventButton.addEventListener("click", event => {
131+
const focusUser = new Event("focusUser");
132+
document.dispatchEvent(focusUser);
133+
})
134+
```
135+
136+
接下来两个页面实现的代码基本一致,这里为了方便观察,设置了两者不同输出日志。
137+
138+
```javascript
139+
// User.js
140+
const eventButton = document.getElementById("eventButton");
141+
document.addEventListener("focusUser", event => {
142+
console.log("【粉丝列表页】监听到自定义事件触发,event:",event);
143+
})
144+
145+
// Home.js
146+
const eventButton = document.getElementById("eventButton");
147+
document.addEventListener("focusUser", event => {
148+
console.log("【微博首页】监听到自定义事件触发,event:",event);
149+
})
150+
```
151+
152+
点击【关注微博】按钮后,看到控制台输出如下日志信息:
153+
154+
![image.png](https://user-gold-cdn.xitu.io/2020/2/22/1706b7199d2f7350?w=715&h=267&f=png&s=29435)
155+
156+
最终实现了,在 **微博列表页(Weibo.js)**组件负责派发事件,其他组价负责监听事件,这样三个组件之间耦合度非常低,完全不用关系对方,互相不影响。<br />**其实这也是实现了观察者模式。**
157+
158+
### 2.2 场景2实现
159+
**场景2:解耦多模块开协作。**<br />举个更直观的例子,当微博需要加入【**一键三连**】新功能,需要产品原型和UI设计完后,程序员才能开发。<br />本例子模拟四个模块:<br />1.流程控制(Index.js)<br />2.产品设计(Production.js)<br />3.UI设计(Design.js)<br />4.程序员开发(Develop.js)
160+
161+
![image.png](https://user-gold-cdn.xitu.io/2020/2/22/1706b7199d71549f?w=1031&h=336&f=png&s=46607)
162+
163+
**流程控制(Index.js)模块**中,我们需要将其他三个流程的模块都导入进来,然后监听【开始任务】按钮的点击事件,在回调事件中,创建一个自定义事件 `startTask`,并在 `document` 上使用 `dispatchEvent` 方法派发自定义事件。
164+
165+
```javascript
166+
// Index.js
167+
import ProductionModule from "./Production.js";
168+
import DesignModule from "./Design.js";
169+
import DevelopModule from "./Develop.js";
170+
171+
const start = document.getElementById("start");
172+
start.addEventListener("click", event => {
173+
console.log("开始执行任务")
174+
const startTask = new Event("startTask");
175+
document.dispatchEvent(startTask);
176+
})
177+
```
178+
179+
在 Production 产品设计模块中,监听任务开始事件 `startTask` 后,模拟1秒后原型设计完成,并派发一个新的事件 `productionSuccess` ,开始接下来的UI稿设计。
180+
```javascript
181+
// Production.js
182+
document.addEventListener("startTask", () => {
183+
console.log("产品开始设计...");
184+
setTimeout(() => {
185+
console.log("产品原型设计完成");
186+
console.log("--------------");
187+
document.dispatchEvent(new Event("productionSuccess"));
188+
}, 1000);
189+
});
190+
```
191+
192+
在UI稿设计和程序开发模块,其实也类似,代码实现:
193+
```javascript
194+
// Dedign.js
195+
document.addEventListener("productionSuccess", () => {
196+
console.log("UI稿开始设计...");
197+
setTimeout(() => {
198+
console.log("UI稿设计完成");
199+
console.log("--------------");
200+
document.dispatchEvent(new Event("designSuccess"));
201+
}, 1000);
202+
});
203+
204+
// Production.js
205+
document.addEventListener("designSuccess", function (e) {
206+
console.log("开始开发功能...");
207+
setTimeout(function () {
208+
console.log("【一键三连】开发完成");
209+
}, 2000)
210+
});
211+
```
212+
213+
开发完成后,我们点击【开始任务】按钮后,看到控制台输出如下日志信息:
214+
215+
![image.png](https://user-gold-cdn.xitu.io/2020/2/22/1706b719c40359b3?w=717&h=268&f=png&s=23968)
216+
217+
最终实现了在 **流程控制(Index.js)模块**负责派发事件,其他组件负责监听事件,按流程完成其他任务。<br />**可以看出,原型设计、UI稿设计和程序开发任务,互不影响,易于任务拓展。**
218+
219+
# 📚四、总结
220+
本文详细介绍 JS**自定义事件概念和实现方式**,并结合两个实际场景进行代码演示。细心的小伙伴会发现,这两个实际场景都是用 `Event()` 构造函数实现,当然也是可以使用 `CustomEvent` 构造函数来代替。<br />另外本文也详细介绍两种实现方式,包括其区别和兼容性。<br />最后也希望大家能在实际开发中,多思考代码解耦,适当使用**自定义事件**来提高代码质量。
221+
222+
如有错误,欢迎指点。
223+
224+
# 📚五、参考文章
225+
226+
-[javascript自定义事件功能与用法实例分析](https://m.jb51.net/article/127776.htm)
227+
-[Event - MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Event/Event)
228+
-[CustomEvent - MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/CustomEvent)
229+
230+
231+
232+
|Author|王平安|
233+
|---|---|
234+
235+
|博 客|www.pingan8787.com|
236+
|微 信|pingan8787|
237+
|每日文章推荐|https://github.com/pingan8787/Leo_Reading/issues|
238+
|ES小册|js.pingan8787.com|
239+
240+
## 微信公众号
241+
![bg](http://images.pingan8787.com/2019_07_12guild_page.png)

0 commit comments

Comments
 (0)