mixin 多重继承
为什么 JavaScript 需要 mixin?
- 限制: 一个对象只能有一个
[[Prototype]], 一个类只能extends一个父类, 没有多继承; - mixin: 只提供方法、不单独使用的对象, 把方法复制进别的类;
- 与继承的差别: 不占用唯一的父类名额, 也不改原型链, 只是在
prototype上做方法拷贝;
const sayHiMixin = {
sayHi() {
return `Hello ${this.name}`;
},
sayBye() {
return `Bye ${this.name}`;
},
};
class User {
constructor(name) {
this.name = name;
}
}
Object.assign(User.prototype, sayHiMixin);
new User("Dude").sayHi(); // "Hello Dude"
- 与继承并用:
class User extends Person {}之后照样能Object.assign(User.prototype, mixin); - 多继承: 依次
Object.assign多个 mixin 即叠加多份行为; 风险是同类或 mixin 间同名覆盖, 命名要够独特;
mixin 内部的 super 指向哪里?
- 机制:
super依赖方法创建时的[[HomeObject]], 即它当初写在哪个对象里; - 复制不改
[[HomeObject]]: 方法被拷进新类后,super仍在原 mixin 的原型上找, 不在宿主类上找;
const sayMixin = {
say(p) {
return p;
},
};
const sayHiMixin = {
__proto__: sayMixin, // mixin 之间也能继承
sayHi() {
return super.say(`Hello ${this.name}`);
},
};
Object.assign(User.prototype, sayHiMixin); // User 同上节
new User("Dude").sayHi(); // "Hello Dude"
如何用 mixin 给任意类加上事件能力?
- 目标: 让任何类/对象获得
.on(name, handler)、.off(name, handler)、.trigger(name, ...data); - 存储: 在
this._eventHandlers上按事件名存处理函数数组;
const eventMixin = {
on(name, handler) {
this._eventHandlers ??= {};
(this._eventHandlers[name] ??= []).push(handler);
},
off(name, handler) {
const list = this._eventHandlers?.[name];
if (!list) return;
const i = list.indexOf(handler);
if (i >= 0) list.splice(i, 1);
},
trigger(name, ...args) {
this._eventHandlers?.[name]?.forEach((h) => h.apply(this, args));
},
};
class Menu {
choose(value) {
this.trigger("select", value);
}
}
Object.assign(Menu.prototype, eventMixin);
new Menu().on("select", (v) => console.log(`selected: ${v}`));
- 关键:
trigger用h.apply(this, args)转发上下文与数据, 见 装饰器与调用转发; - 价值: 同一 mixin 可挂到任意多个类上, 互不干扰, 也不打断各自的继承链;