Mixin 模式
- 在
JavaScript
中,我们只能继承单个对象。每个对象只能有一个[[Prototype]]
。并且每个类只可以扩展另外一个类。 mixin
是一个包含可被其他类使用而无需继承的方法的类。
一个 Mixin 实例
- 最简单的构造mixin方式:构造一个拥有实用方法的对象
// 创建一个对象来构造minin
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
}
};
// 用法:
class User {
constructor(name) {
this.name = name;
}
}
// 拷贝方法
Object.assign(User.prototype, sayHiMixin);
// 现在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!
- 上面的代码只包含简单方法的拷贝。所以User还可以继承另一个类。mixin和继承可以共存。
class User extends Person {
// ...
}
Object.assign(User.prototype, sayHiMixin);
- Minin 可以在自己的内部使用继承
let sayMixin = {
say(phrase) {
alert(phrase);
}
};
let sayHiMixin = {
__proto__: sayMixin, // (或者,我们可以在这儿使用 Object.create 来设置原型)
sayHi() {
// 调用父类方法
super.say(`Hello ${this.name}`); // (*)
},
sayBye() {
super.say(`Bye ${this.name}`); // (*)
}
};
class User {
constructor(name) {
this.name = name;
}
}
// 拷贝方法
Object.assign(User.prototype, sayHiMixin);
// 现在 User 可以打招呼了
new User("Dude").sayHi(); // Hello Dude!
注意:
- 当把
sayHiMixin
中的方法复制到User.prototype
中去的时候,[[Homeobject]]
内部属性仍旧引用的是sayHiMixin
。也就是表面上是new User("Dude").sayHi();
实际上是sayHiMixin.sayHi()
。所以,当super
在[[HomeObject]].[[Prototype]]
中寻找父方法时,意味着它搜索的是sayHiMixin.[[Prototype]]
,而不是User.[[Prototype]]。
EventMixin
- 构造一个
mixin
,将与事件相关的函数添加到任意class/object
中
Mixin提供的方法
.trigger(name, [...data])
:在发生重要事情的时候生成一个事件.on(name, handler)
:为具有给定名称的事件添加了handler
函数作为监听器,当具有给定name
的事件触发时将调用该方法,并从.trigger
调用中获取参数。.off(name, handler)
:删除 handler 监听器
let eventMixin = {
/**
* 订阅事件,用法:
* menu.on('select', function(item) { ... }
* _eventHandlers属性:储存每个事件名称对应的处理程序(handler)
*/
on(eventName, handler) {
//如果还没有_eventHandlers属性,就创建一个
if (!this._eventHandlers) this._eventHandlers = {};
//如果这个名称的事件不存在,就创建一个
if (!this._eventHandlers[eventName]) {
this._eventHandlers[eventName] = [];
}
//在_eventHandlers属性添加这个名称的事件对应的处理程序
this._eventHandlers[eventName].push(handler);
},
/**
* 取消订阅,用法:
* menu.off('select', handler)
* 从处理程序列表中删除指定的函数
*/
off(eventName, handler) {
//this._eventHandlers如果存在,获取它的[eventName]
let handlers = this._eventHandlers?.[eventName];
if (!handlers) return;
for (let i = 0; i < handlers.length; i++) {
if (handlers[i] === handler) {
//第一个参数是删除的位置,第二个参数是删除的数量
handlers.splice(i--, 1);
}
}
},
/**
* 生成具有给定名称和数据的事件
* this.trigger('select', data1, data2);
*/
trigger(eventName, ...args) {
if (!this._eventHandlers?.[eventName]) {
return; // 该事件名称没有对应的事件处理程序(handler)
}
// 调用事件处理程序(handler)
this._eventHandlers[eventName].forEach(handler => handler.apply(this, args));
}
};
用法:
// 创建一个 class
class Menu {
choose(value) {
this.trigger("select", value);
}
}
// 添加带有事件相关方法的 mixin
Object.assign(Menu.prototype, eventMixin);
let menu = new Menu();
// 添加一个事件处理程序(handler),在被选择时被调用:
menu.on("select", value => alert(`Value selected: ${value}`));
// 触发事件 => 运行上述的事件处理程序(handler)并显示:
// 被选中的值:123
menu.choose("123");