logo

 1.数据双向绑定


观察者模式,数据的双向绑定,手写一个基本的数据双向绑定,通过使用 defineProperty 去实现数据的双向绑定。

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数据双向绑定</title>
</head>

<body>
<input type="text" id="input" />
<span id="output"></span>
</body>
<script>
// 定义一个数据对象
const userInfo = {};
// 获取输入框
const input = document.getElementById("input");
// 当输入框输入的时候赋值,更新视图到数据上
input.oninput = () => {
userInfo.name = input.value;
};
// 获取输出的dom
const output = document.getElementById("output");
// 更新数据到视图
Object.defineProperty(userInfo, "name", {
get: () => {
console.log("get", userInfo);
},
set: (val) => {
output.innerHTML = val;
input.value = val;
console.log("set", val);
},
});
</script>
</html>

 2.事件总线程

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class EventBus {
constructor() {
this.eventContainer = this.eventContainer || new Map(); //用一个容器存放事件
}
on(type, callback) {
if (!this.eventContainer.has(type)) {
//如果容器里面没有这种类型的事件,就增加
this.eventContainer.set(type, callback);
}
}
off(type) {
if (this.eventContainer.has(type)) {
this.eventContainer.delete(type);
}
}
emit(type) {
let fn = this.eventContainer.get(type);
fn.apply(this, [...arguments].slice(1));
}
}

es5 手写:

思路:事件队列存在很对的事件名称,所以创建的事件队列是一个对象。
订阅事件,首先判断事件队列里面是否含有该事件名称,没有的话就创建一个,将订阅的事件加入队列。
订阅一次事件,在订阅事件的基础之上,首先执行一次回调,然后取消订阅即可。
发布事件,如果存在传入的事件名称,则遍历队列中该事件名称的所有事件,并给事件传入对应参数。
取消订阅,如果存在传入的事件名称,则删除本事件。

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
59
60
61
62
63
64
65
66
class EventBus {
constructor() {
// 创建事件队列,因为可能会存在很多的eventbus,所以使用对象的形式,键为名,值为函数。
this.tasks = {};
}
/**
* 订阅事件
* @param {String} type 事件名称
* @param {Function} fn 回调函数
*/
on(type, fn) {
// 如果事件队列中不包含传入的type类型,则需要创建一个。
if (!this.tasks[type]) {
this.tasks[type] = [];
}
// 如果事件队列中存在,则将回调函数加入队列
this.tasks[type].push(fn);
}
/**
* 订阅一次的事件
* @param {String} type
* @param {Function} fn
*/
once(type, fn) {
// 如果事件队列中不包含传入的type类型,则需要创建一个。
if (!this.tasks[type]) {
this.tasks[type] = [];
}
const _this = this;
// 只执行一次,然后注销移除回调
const _once = (...args) => {
fn(...args);
_this.off(type, _once);
};
this.tasks[type].push(_once);
}
/**
* 发布事件
* @param {String} type 事件名称
* @param {...any} args 传入的参数
* @returns
*/
emit(type, ...args) {
// 如果队列中不存在传入的type时候,则不执行任何操作
if (!this.tasks[type]) {
return;
}
// 如果队列中含有传入的type时,遍历队列中的函数,并传入参数
this.tasks[type].forEach((fn) => fn(...args));
}
/**
* 取消订阅
* @param {String} type 事件名称
* @param {Function} fn 回调函数
* @returns
*/
off(type, fn) {
const tasks = this.tasks[type];
// 如果队列中不存在传入的type时候,则不执行任何操作
if (!Array.isArray(tasks)) {
return;
}
// 利用 filter 删除队列中的指定函数
this.tasks[type] = tasks.filter((cb) => fn !== cb);
}
}

使用:

1
2
3
4
5
6
7
let eventBus = new EventBus();
eventBus.on("testEvent", (name) => {
setTimeout(() => {
console.log("This is a", name);
}, 1000);
});
eventBus.emit("testEvent", "testEvent");