在JavaScript中,我们只能继承单个对象。每个对象只能有一个[[Prototype]]。而且每个类只能扩展另一个类。但有时这种设置(译注:单一继承)会让人觉得有限制。例如,我有一个StreetSweeper类和一个Bicycle类,现在想要它们的混合:StreetSweepingBicycle类。或者,我们有一个User类和一个EventEmitter类用于事件生成,我们想将EventEmitter的功能添加到User中,以便我们的用户可以发出事件。有一个概念可以帮助我们,叫做“mixins”。根据维基百科的定义,mixin是一个类,它包含可以被其他类使用而无需继承的方法。换句话说,mixin提供了实现特定行为的方法,但我们不是单独使用它,而是使用它来将这些行为添加到其他类中。1.Mixin实例在JavaScript中构造mixin最简单的方法是构造一个带有实用方法的对象,这样我们就可以轻松地将这些实用方法合并到任何类的原型中。例如,这个名为sayHiMixin的mixin用于为User添加一些“语言特性”://mixinletsayHiMixin={sayHi(){alert(`Hello${this.name}`);},sayBye(){alert(`Bye${this.name}`);}};//用法:classUser{constructor(name){this.name=name;}}//复制方法Object.assign(User.prototype,sayHiMixin);//现在用户可以打招呼newUser("Dude").sayHi();//HelloDude!这里没有继承,只有简单的方法拷贝。所以User可以从另一个类继承,也可以包含mixins以“混合”其他方法,像这样:classUserextendsPerson{//...}Object.assign(User.prototype,sayHiMixin);mixins可以在自己内部使用继承。比如这里sayHiMixin继承自sayMixin:letsayMixin={say(phrase){alert(phrase);}};letsayHiMixin={__proto__:sayMixin,//(或者,我们这里可以用Object.create来设置原型)sayHi(){//调用父类方法super.say(`Hello${this.name}`);//(*)},sayBye(){super.say(`Bye${this.name}`);//(*)}};classUser{constructor(name){this.name=name;}}//复制方法Object.assign(User.prototype,sayHiMixin);//现在用户可以打招呼了newUser("Dude")。打招呼();//哥们,你好!请注意,在sayHiMixin内部,对父类的super.say()方法的调用(在标有(*)的行上)会在mixin的原型中查找该方法,而不是在搜索的类中查找。这是原理图(见右图):这是因为方法sayHi和sayBye最初是在sayHiMixin中创建的。因此即使它们被复制,它们的[[HomeObject]]内部属性仍然引用sayHiMixin,如上图所示。当super在[[HomeObject]].[[Prototype]]中寻找父方法时,意味着它会搜索sayHiMixin.[[Prototype]],而不是User.[[Prototype]]。2.EventMixin现在让我们构建一个实际使用的mixin。例如,许多浏览器对象的一个??重要特性是它们可以生成事件。事件是向任何需要它的人“广播消息”的好方法。因此,让我们构建一个mixin,它允许我们轻松地将与事件相关的函数添加到任何类/对象。Mixins将提供.trigger(name,[...data])方法来在重要事件发生时“生成事件”。name参数(arguments)是事件的名称,[...data]是带有事件数据的可选附加参数(arguments)。此外,还有.on(name,handler)方法,该方法将处理函数添加为具有给定名称的事件的侦听器。当具有给定名称的事件触发时将调用此方法,并从.trigger调用中获取参数。...和??.off(name,handler)方法,它删除处理程序侦听器。添加mixin后,对象用户将能够在访客登录时生成事件“登录”。另一个对象(例如日历)可能希望监听此类事件,以便为已登录的人加载日历。Alternatively,whenamenuitemisselected,menucangeneratea"select"event,andotherobjectscanassignhandlerstoreacttothatevent.等等。下面是代码:leteventMixin={/***订阅事件,用法:*menu.on('select',function(item){...}*/on(eventName,handler){if(!this._eventHandlers)this._eventHandlers={};if(!this._eventHandlers[eventName]){this._eventHandlers[eventName]=[];}this._eventHandlers[eventName].push(handler);},/***取消订阅,用法:*menu.off('select',handler)*/off(eventName,handler){lethandlers=this._eventHandlers?.[eventName];if(!handlers)return;for(leti=0;i
