封装时更好的写法
现在有这样一个类,提供了事件对象的实现:
ts
export class EventCenter {
public on(eventName: Event, handler) {
// ...
}
public once(eventName: Event, handler) {
handler.once = 1 // 标志只触发一次
this.on(eventName, handler)
}
public emit(eventName: Event, ...options) {
// ...
handler.call(this, ...options) // 触发handler函数
// 如果是一次性函数 -> 卸载
if (handler?.once === 1) {
this.off(eventName, handler)
}
// ...
}
public off(eventName?, handler?) {
// ...
}
}
其中 once
函数提供了在调用一次后即刻销毁的事件挂载方式。
接下来我们看看如何更好的实现,优化后的代码:
ts
export class EventCenter {
public on(eventName: Event, handler) {
// ...
}
public once(eventName: Event, handler) {
const wrappedHandler = (...options) => {
handler.call(this, ...options)
this.off(eventName, wrappedHandler)
}
wrappedHandler.once = true
this.on(eventName, wrappedHandler)
}
public emit(eventName: Event, ...options) {
// ...
handler.call(this, ...options)
// ...
}
public off(eventName?, handler?) {
// ...
}
}
提示
之所以这样修改,在给函数对象挂载属性时,一般来说,最好 避免修改函数自身的属性 。这是因为 函数应该专注于执行特定的功能,而不应该被用于存储状态或附加其他行为。
在修改前的代码中给 handler
函数添加了一个 once
属性来表示执行一次后自动销毁。尽管这样的做法可以实现一次性的事件订阅,但会破坏函数的单一职责原则并增加复杂性。
如何理解这里的函数的单一职责原则?
在修改前的代码中,我们为了实现 once
这一功能,需要 once
及 emit
函数共同发挥作用。使 emit
函数不再纯粹(掺杂了 once
所需要的 off
调用),这显然不是一个好的设计。
在修改后的代码里, once
功能完全由 once
函数完成🎉。
推荐的更好写法是使用闭包来管理状态,以实现一次性的事件订阅。可以通过创建一个包装函数,在内部维护一个标志位来控制回调函数的执行次数。
在修改后的代码中,once
方法会创建一个新的包装函数 wrappedHandler
来处理事件回调。这个包装函数会在执行后自动解绑事件。通过使用闭包和额外的标志位,我们避免了直接修改原始函数的属性,使代码更清晰、易于理解,并且保持了函数的单一职责。
总结起来,使用闭包来管理状态和附加行为会比直接在函数上挂载属性更好,因为它遵循了良好的设计原则,并提供了更可控和可维护的代码。