在JavaScript/TypeScript中用闭包手动模拟类继承的潜在弊端
我有一个基于ES6类实现的SongDisplayer(继承自EventEmitter):
export class SongDisplayer extends EventEmitter{ mediaPath: string; currentlyPlaying: string; constructor(hlsMediaPath: string){ super(); this.mediaPath = hlsMediaPath; this.currentlyPlaying = ''; this.on('currentlyPlaying', (songInfo) => { this.currentlyPlaying = songInfo; }); }; async queueDisplaySong( songInfo: string ): Promise<undefined>{ const segments = await this._getM3u8Segments(this.mediaPath); if (!segments){ this.emit('currentlyPlaying', songInfo); return; }; ...... }; async _getM3u8Segments( mediaPath: string ): Promise<string[]>{ ........ }; ...... };
我尝试用闭包工厂函数模拟类似的类行为:
export function MakeSongDisplayer( mediaPath: string ){ const eventEmitter = new EventEmitter(); let currentlyPlaying = ''; eventEmitter.on('currentlyPlaying', (songInfo) => { currentlyPlaying = songInfo; }); return { queueDisplaySong: (songInfo: string) => { queueDisplaySong( songInfo, mediaPath, eventEmitter ); }, on: (event: string, cb: (...args: any) => any) => { eventEmitter.on(event, cb); }, getCurrentlyPlaying: () => currentlyPlaying }; }; async function queueDisplaySong( songInfo: string, mediaPath: string, eventEmitter: EventEmitter ): Promise<undefined>{ const segments = await _getM3u8Segments(mediaPath); if (!segments){ eventEmitter.emit('currentlyPlaying', songInfo); return; }; ...... };
我直觉认为大量创建实例时闭包方式内存开销更高,因为返回对象的函数会重复创建而非共享原型引用,但不确定这个理解是否正确,想知道闭包模拟类的方式还有哪些潜在弊端?
内存开销更高(你的直觉是对的):ES6类的方法挂载在原型链上,所有实例共享同一份函数引用;而闭包模式下,每次调用
MakeSongDisplayer都会创建全新的queueDisplaySong、on、getCurrentlyPlaying函数实例,每个实例都占用独立内存。当创建成百上千个实例时,这种重复创建的函数会累积出明显的内存差异。无法利用原型链实现继承与扩展:类的原型机制天然支持继承(比如原代码的
extends EventEmitter),子类可以轻松重写父类方法、扩展原型方法;但闭包模式下,所有逻辑都封装在工厂函数内部,要实现类似继承的逻辑只能手动复制属性和方法,代码冗余度极高,也不符合JavaScript的原型式设计范式。调试与可维护性下降:类的实例有明确的构造函数标识,调试时能清晰看到实例的原型链、所属类;而闭包返回的是普通对象,没有统一的构造函数标记,调试时很难追踪对象的来源和结构。另外,类的私有方法(比如
_getM3u8Segments)可以通过原型统一管理,闭包模式下拆分逻辑只能依赖外部函数,代码分散,维护成本更高。事件系统扩展性差:原类中可以通过
this直接访问EventEmitter的所有方法(比如emit、off);但闭包模式下你只暴露了on方法,如果后续需要使用off、once等方法,必须手动在返回对象中新增对应封装,扩展性远不如类。TypeScript类型支持较弱:类的类型定义天然清晰,实例的属性、方法类型可以通过类本身自动推导;而闭包工厂函数返回的对象,必须手动定义完整接口才能获得准确的类型提示,否则TypeScript只能推断出对象表面结构,无法关联工厂函数的内部逻辑,类型维护成本更高。
内容的提问来源于stack exchange,提问作者hankthetank27

