使用发布订阅模式实现 Event 类
在腾讯面试中遇到的这个问题,参考文章。
用 JavaScript 实现一个类,包含绑定事件on
、解绑事件off
、只监听一次事件once
和派发事件 emit
。
on(eventName, func):
监听eventName
事件,事件触发的时候调用func
函数emit(eventName, arg1, arg2, arg3,arg4...)
: 触发eventName
事件, 并且把参数 arg1, arg2, arg3, arg4...传给事件处理函数off(eventName, func)
: 停止监听某个事件once(eventName, func)
: 监听eventName
事件,事件触发的时候调用func
函数,随后立即删除该绑定
在组件化开发的页面中,也就是同一个页面由两三个人来开发的情况下,为了保证组件的独立性,往往使用订阅发布模式,即组件间通信使用事件监听和派发的方式,而不是直接相互调用组件,这就是题目中要求实现的类。
代码一览:
js
class Event {
constructor() {
// 存储事件的数据对象
// 为了迅速查找,使用字典
this.events = {};
}
// 绑定事件
on(eventName, callBack) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callBack);
}
// 派发事件:遍历事件列表,对每个事件执行相应的回调函数
emit(eventName, ...args) {
if (this.events[eventName]) {
this.events[eventName].forEach((callBack) => callBack(...args));
}
}
// 取消订阅事件,从事件列表中删除指定的回调函数
off(eventName, callBack) {
if (this.events[eventName]) {
if (callBack) {
// 如果指定了回调函数,就删除该函数
this.events[eventName] = this.events[eventName].filter(
(cb) => cb != callBack
);
} else {
// 如果没指定回调函数,就删除所有绑定的函数
this.events[eventName] = [];
}
}
}
// 为事件绑定一个只执行一次的回调函数
once(eventName, callBack) {
// 将回调函数与取消订阅事件封装起来,成为一个新的函数
const func = () => {
callBack();
this.off(eventName, func);// 注意这里传入的参数是新定义的 func 而不是 callBack
};
this.on(eventName, func);
}
}
function test() {
console.log("in test");
}
const test2 = function () {
console.log("in test2");
};
myEvent = new Event();
myEvent.on("test", test);
myEvent.once("test2", test2);
myEvent.emit("test2");
LazyMan 实现
先来了解一下什么是 LazyMan
, LazyMan
方法可以按照以下方式调用:
js
LazyMan('Hank');
// 输出:
// Hi! This is Hank!
LazyMan('Hank').sleep(3).eat('dinner')
// 输出:
// Hi! This is Hank!
// //等待3秒..
// Wake up after 3
// Eat dinner~
LazyMan('Hank').eat('dinner').eat('supper')
// 输出:
// Hi This is Hank!
// Eat dinner~
// Eat supper~
LazyMan('Hank').sleepFirst(2).eat('dinner').sleep(3).eat('supper')
// 输出:
// //等待2秒..
// Wake up after 2
// Hi This is Hank!
// Eat dinner~
// //等待3秒..
// Wake up after 2
// Eat supper~
// 以此类推
分析一下本题的核心:链式调用、异步问题。
方法一:任务队列
这个方法的核心是 next()
方法以及 return this
。
js
class LazyMan {
taskQueue = [];
constructor(name) {
this.name = name;
setTimeout(() => {
// 创造一个异步
this.next();
});
}
sayName() {
const fn = ()=>{
console.log(`我叫 ${this.name}`);
this.next();
}
this.taskQueue.push(fn);
return this;
}
next() {
const fn = this.taskQueue.shift();
if (fn) {
fn(); // 如果 fn 存在,则执行
}
}
holdon(sec) {
return () => {
setTimeout(() => {
console.log(`${sec} 秒后醒来啦`);
this.next();
}, sec * 1000);
};
}
sleep(sec) {
this.taskQueue.push(this.holdon(sec));
return this;
}
eat(food){
const fn = ()=>{
console.log(`吃了 ${food}`);
this.next();
}
this.taskQueue.push(fn);
return this;
}
sleepFirst(sec){
this.taskQueue.unshift(this.holdon(sec));
return this;
}
}
const zrz = new LazyMan('zrz');
zrz.sayName().eat('黄焖鸡').sleepFirst(2).eat('烤肉').sleep('3').eat('黄焖鸡again!');
结果:
bash
(base) jiangjiaqi@MacBook-Air-3 lazyman % node lazyman.js
2 秒后醒来啦
我叫 zrz
吃了 黄焖鸡
吃了 烤肉
3 秒后醒来啦
吃了 黄焖鸡again!
任务队列 + Promise
实现
TIP
Promise 是一个对象,表示异步操作的结果。