前端设计模式之观察者模式(五)
- 观察者模式是前端最常用的一个设计模式,也是 UI 编程最重要的思想
- 例如在星巴克叫了杯咖啡,制作好了服务员会自动通知领取的
- DOM 事件就是最常用的也是观察者模式
<button id="btn1">btn</button>
<script>
const $btn1 = document.querySelector("#btn1");
$btn1?.addEventListener("click", () => {});
$btn1?.addEventListener("click", () => {});
</script>
实现主题和观察者
// 主题
class Subject {
private state: number = 0;
private observers: Observer[] = [];
getState(): number {
return this.state;
}
setState(newState: number) {
this.state = newState;
this.notify();
}
// 添加观察者
attach(observer: Observer) {
this.observers.push(observer);
}
// 通知所有观察者
private notify() {
for (const observer of this.observers) {
observer.update(this.state);
}
}
}
// 观察者
class Observer {
name: string;
constructor(name: string) {
this.name = name;
}
update(state: number) {
console.log(`${this.name} update, state is ${state}`);
}
}
const sub = new Subject();
const observer1 = new Observer("A");
sub.attach(observer1);
const observer2 = new Observer("B");
sub.attach(observer2);
sub.setState(1); // 更新状态,触发观察者 update
是否符合设计原则
-
Observer 和 Target 分离,解耦
-
Observer 可自由扩展
-
Target 可自由扩展
场景
Vue React 组件生命周期
Vue watch API
// Vue 组件配置
{
data() {
name: "yunmu"
},
watch: {
name(newVal, val) {
console.log(newValue, val);
}
}
}
自定义事件
- Vue2本身支持,Vue3不支持,Vue3 推荐使用
mitt
(没有once
绑定)文档:github.com/developit/m… - 注意组件销毁之前,要及时 off 自定义事件,否则可能会导致内存泄漏
- off 时要传入原来的函数,而不能是匿名函数
import mitt from "mitt";
const emitter = mitt(); // 工厂函数
emitter.on("change", () => {
console.log("change1");
});
emitter.on("change", () => {
console.log("change2");
});
emitter.emit("change");
如果需要 once
绑定,可以使用 www.npmjs.com/package/eve…
import eventEmitter from "event-emitter"; // 还要安装 @types/event-emitter
const emitter = eventEmitter();
emitter.on("change", (value: string) => {
console.log("change1", value);
});
emitter.on("change", (value: string) => {
console.log("change2", value);
});
emitter.once("change", (value: string) => {
console.log("change3", value);
});
emitter.emit("change", "张三");
emitter.emit("change", "李四");
各种异步的回调
- 定时器
setTimeout
setInterval
Promise then
nodejs stream
nodejs readline
nodejs http server 回调
// nodejs stream
const fs = require("fs");
const readStream = fs.createReadStream("./data/file1.txt"); // 读取文件的 stream
let length = 0;
readStream.on("data", function (chunk) {
length += chunk.toString().length;
});
readStream.on("end", function () {
console.log(length);
});
// nodejs readline
const readline = require("readline");
const fs = require("fs");
const rl = readline.createInterface({
input: fs.createReadStream("./data/file1.txt"),
});
let lineNum = 0;
rl.on("line", function (line) {
lineNum++;
});
rl.on("close", function () {
console.log("lineNum", lineNum);
});
// nodejs http server 回调
const http = require("http");
function serverCallback(req, res) {
console.log("get 请求不处理", req.url);
res.end("hello");
}
http.createServer(serverCallback).listen(8081);
console.log("监听 8081 端口……");
- Vue 组件更新过程
MutationObserver
function callback(records: MutationRecord[], observer: MutationObserver) {
for (let record of records) {
console.log("record", record);
}
}
const observer = new MutationObserver(callback);
const containerElem = document.getElementById("container");
const options = {
attributes: true, // 监听属性变化
attributeOldValue: true, // 变化之后,记录旧属性值
childList: true, // 监听子节点变化(新增删除)
characterData: true, // 监听节点内容或文本变化
characterDataOldValue: true, // 变化之后,记录旧内容
subtree: true, // 递归监听所有下级节点
};
// 开始监听
observer.observe(containerElem!, options);
// 停止监听
// observer.disconnect()
postMessage 通讯
- 通过
window.postMessage
发送消息。注意第二个参数,可以限制域名,如发送敏感信息,要限制域名
// 父页面向 iframe 发送消息
window.iframe1.contentWindow.postMessage("hello", "*");
// iframe 向父页面发送消息
window.parent.postMessage("world", "*");
可监听 message
来接收消息。可使用 event.origin
来判断信息来源是否合法,可选择不接受
window.addEventListener("message", event => {
console.log("origin", event.origin); // 通过 origin 判断是否来源合法
console.log("child received", event.data);
});
同类型的还有
-
nodejs 多进程通讯
-
WebWorker 通讯
-
WebSocket 通讯
观察者模式 VS 发布订阅模式
- 发布订阅模式可以相当于观察者模式的另一个版本
- 观察者:
Subject
和Observer
直接绑定,中间无媒介,如addEventListener
绑定事件 - 发布订阅:
Publisher
和Observer
相互不认识,中间有媒介,如event
自定义事件
发布订阅模式会主动 emit
,而观察者模式则没有体现
// 绑定
event.on("event-key", () => {
// 事件1
});
event.on("event-key", () => {
// 事件2
});
// 触发执行
event.emit("event-key");
转载自:https://juejin.cn/post/7197424371991527479