如何为ES/JS对象实现事件分发系统?原型继承方案是否可行?
自定义对象继承EventDispatcher实现事件分发的方案可行性
结论
这个方案完全可行,是JavaScript中实现复用事件分发逻辑的经典思路,能有效避免重复代码,非常适合你需要创建大量带事件能力对象的场景。
现有代码的问题与优化建议
你的基础实现方向没问题,但存在几个细节缺陷,优化后能让代码更健壮:
1. 修复原型继承的共享问题
你当前用WR_PlaylistManager.prototype = new WR_EventDispatcher();的方式,会导致所有子类实例共享同一个events对象(父类构造函数中创建的events会被挂载到子类原型上,所有子类实例都会引用它)。正确的做法是在子类构造函数中调用父类构造函数,确保每个实例拥有独立的事件存储:
var WR_PlaylistManager = function(data) { // 调用父类构造函数,初始化当前实例的events属性 WR_EventDispatcher.call(this); var self = this; function test() { var k = 6; self.fireEvent('NEXT_READY', [{ 'a': 2, 'b': 11 }]); } } // 正确建立原型链继承,保留父类原型方法 WR_PlaylistManager.prototype = Object.create(WR_EventDispatcher.prototype); // 修正构造函数指向,避免 instanceof 判定异常 WR_PlaylistManager.prototype.constructor = WR_PlaylistManager;
2. 绑定事件处理函数的上下文
当前evs[i].apply(null, args)会把处理函数的上下文设为window(浏览器环境),通常我们希望处理函数中的this指向触发事件的对象实例,修改为:
evs[i].apply(this, args);
3. 完善边界情况处理
- 在
removeEventListener中,当移除最后一个事件处理函数后,删除对应的事件键,避免空数组占用内存 - 添加
hasEventListener方法,方便检查事件是否已绑定处理函数 - 触发事件时复制事件数组,避免遍历过程中数组被修改导致异常
优化后的完整代码
// EventDispatcher 模块 (function() { var WR_EventDispatcher = function() { // 每个实例拥有独立的事件存储对象 this.events = {}; }; WR_EventDispatcher.prototype.addEventListener = function(name, handler) { if (!this.events.hasOwnProperty(name)) { this.events[name] = []; } // 避免重复添加同一个处理函数 if (this.events[name].indexOf(handler) === -1) { this.events[name].push(handler); } }; WR_EventDispatcher.prototype.removeEventListener = function(name, handler) { if (!this.events.hasOwnProperty(name)) return; var index = this.events[name].indexOf(handler); if (index !== -1) { this.events[name].splice(index, 1); // 事件数组为空时删除对应键 if (this.events[name].length === 0) { delete this.events[name]; } } }; WR_EventDispatcher.prototype.fireEvent = function(name, args) { if (!this.events.hasOwnProperty(name)) return; args = args || []; // 复制数组,避免触发过程中数组被修改导致遍历异常 var evs = this.events[name].slice(); for (var i = 0; i < evs.length; i++) { // 绑定当前实例为处理函数的上下文 evs[i].apply(this, args); } }; WR_EventDispatcher.prototype.hasEventListener = function(name, handler) { if (!this.events.hasOwnProperty(name)) return false; return handler ? this.events[name].indexOf(handler) !== -1 : this.events[name].length > 0; }; window.WR_EventDispatcher = WR_EventDispatcher; }(window)); // 自定义对象 PlaylistManager (function(window) { "use strict" var WR_PlaylistManager = function(data) { // 调用父类构造函数初始化实例属性 WR_EventDispatcher.call(this); var self = this; // 暴露test方法供外部调用 this.test = function() { var k = 6; self.fireEvent('NEXT_READY', [{ 'a': 2, 'b': 11 }]); } } // 正确设置原型继承关系 WR_PlaylistManager.prototype = Object.create(WR_EventDispatcher.prototype); WR_PlaylistManager.prototype.constructor = WR_PlaylistManager; window.WR_PlaylistManager = WR_PlaylistManager; }(window)); // 使用示例 var playlistManager = new WR_PlaylistManager(); playlistManager.addEventListener('NEXT_READY', function(data) { console.log('事件触发,数据:', data, '当前实例:', this); }); playlistManager.test();
总结
这种基于原型继承的事件分发复用方案完全适配你的场景,优化后的代码保证了每个实例的独立性,同时提升了代码的健壮性和可维护性。
内容的提问来源于stack exchange,提问作者Toniq
相关产品推荐
相关产品推荐

