观察者模式:会有两个类,观察者会被存储到被观察者中,如果被观察者状态变化,会主动通知观察者,调用观察者的更新方法
发布订阅好处:可以解耦合
const EventEmitter = require("events"); // 使用自己实现的 events 类 // const EventEmitter = require("./39/events.js"); const util = require("util"); function Man() {} // 继承 util.inherits(Man, EventEmitter); // 创建实例 let man = new Man(); // 发布订阅 const sleep = (name) => console.log(name, "睡"); const eat = (name) => console.log(name, "吃");
newListener 固定写法,每次绑定都会触发,可以用于判断监听了哪些事件:https://nodejs.org/dist/latest-v18.x/docs/api/events.html#event-newlistener
man.on("newListener", (type) => { console.log("newListener---->", type); if (type === "唱跳rap篮球") { // 在当前同步代码执行完毕后触发事件 process.nextTick(() => { man.emit(type, "小坤"); }); } }); // newListener 测试 man.on("唱跳rap篮球", sleep); man.once("唱跳rap篮球", eat);
on 方法:
man.on("唱跳rap篮球", sleep); man.on("唱跳rap篮球", eat); man.emit("唱跳rap篮球", "小坤");
off 方法:
man.on("唱跳rap篮球", sleep); man.off("唱跳rap篮球", sleep); man.emit("唱跳rap篮球", "小坤");
once 方法:
man.once("唱跳rap篮球", eat); man.emit("唱跳rap篮球", "小坤"); man.emit("唱跳rap篮球", "小坤");
自己实现一个简单版本的 EventEmitter
下面实现一下上面提的五种方法:newListener、on、emit、off、once
function EventEmitter() { this._events = {}; // 默认给 EventEmitter 准备的 } // 订阅 EventEmitter.prototype.on = function (eventName, callback) { if (!this._events) this._events = {}; // 如果不是 newListener 那就需要触发 newListener 的回调 if (eventName !== "newListener") { this.emit("newListener", eventName); } if (!this._events[eventName]) this._events[eventName] = []; this._events[eventName].push(callback); }; // 发布 EventEmitter.prototype.emit = function (eventName, ...args) { if (this._events && this._events[eventName]) { this._events[eventName].forEach((event) => event(...args)); } }; // 注销 EventEmitter.prototype.off = function (eventName, callback) { if (this._events && this._events[eventName]) { // 这里需要对 once 里的进行处理:删除时获取 once 里的 l 属性和 callback 比较,如果相等也需要删除 this._events[eventName] = this._events[eventName].filter((cb) => cb != callback && cb.l != callback); } }; // 订阅只执行一次 EventEmitter.prototype.once = function (eventName, callback) { // 使用切片 const once = (...args) => { callback(...args); this.off(eventName, once); }; // 给 once 添加 l 属性用于表示 callback once.l = callback; // 先绑定一个一次性事件,稍后触发时在将事件清空 this.on(eventName, once); }; module.exports = EventEmitter;
events 源码快览
先配置 lanch.json
{ // 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。 // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "启动程序", // 这里我们不跳过 node 的内部源码,因为等下需要调试 require,需注释掉 "skipFiles": [ // "<node_internals>/**" ], "program": "${workspaceFolder}\\doc\\39 # events 模块的实现原理.js" } ] }
然后调用方法处打上断点,进入单步调试
on 方法:别名 addListener
底层调用的是 _addListener
源码这里面写了 Object.create(null)
,它跟 {}
有区别,它没有原型链,而 {}
是有原型链的。
newListener 方法:我们可以看到在 _addListener
里面有对 newListener
做处理,会先触发 newListener。
emit 方法:
这里需要提到一个 primordials :(primordials 变量是nodejs内部对原生 js 众多类型构造器、方法的一个外观,防止当原生 js 构造器、方法被覆盖时导致出错)这里的 apply 就是来自 primordials 里的 Reflect.apply
off 方法:别名 removeListener
也是类似的判断方法:list[i] === listener || list[i].listener === listener
once 方法:底层调用了 _onceWrap
,将 listener 挂在了 wrapped.listener
上,我们自己实现的是挂在 once.l