纯函数式的复合
继承
class Todo { constructor (name) { this.name = name || 'Untitled'; this.done = false; } do () { this.done = true; return this; } undo () { this.done = false; return this; } }
现在我们假设 Todo 类在整个应用程序中用途很多且多处使用。在某部分代码中,我们希望 Todo 项是有颜色的。正如前面章节看到的,可以使用简单 mixin 实现如下:
const Coloured = { setColourRGB ({r, g, b}) { this.colourCode = {r, g, b}; return this; }, getColourRGB () { return this.colourCode; } }; Object.assign(Todo.prototype, Coloured);
class ColouredTodo extends Todo { setColourRGB ({r, g, b}) { this.colourCode = {r, g, b}; return this; } getColourRGB () { return this.colourCode; } }
共享是暖心之举
class ColouredTodo extends Todo {} const Coloured = { setColourRGB ({r, g, b}) { this.colourCode = {r, g, b}; return this; }, getColourRGB () { return this.colourCode; } }; Object.assign(ColouredTodo.prototype, Coloured);
function ComposeWithClass(clazz, ...mixins) { const subclazz = class extends clazz {}; for (let mixin of mixins) { Object.assign(subclazz.prototype, mixin); } return subclazz; } const ColouredTodo = ComposeWithClass(Todo, Coloured);
功能增强
const shared = Symbol("shared"); function ComposeWithClass(clazz, ...mixins) { const subclazz = class extends clazz {}; for (let mixin of mixins) { const instanceKeys = Reflect .ownKeys(mixin) .filter(key => key !== shared && key !== Symbol.hasInstance); const sharedBehaviour = mixin[shared] || {}; const sharedKeys = Reflect.ownKeys(sharedBehaviour); for (let property of instanceKeys) Object.defineProperty(subclazz.prototype, property, { value: mixin[property] }); for (let property of sharedKeys) Object.defineProperty(subclazz, property, { value: sharedBehaviour[property], enumerable: sharedBehaviour.propertyIsEnumerable(property) }); } return subclazz; } ComposeWithClass.shared = shared;
按照以上实现,每个 mixin 自己可以决定使用何属性提供对 instanceof 的支持。
const isaColoured = Symbol(); const Coloured = { setColourRGB ({r, g, b}) { this.colourCode = {r, g, b}; return this; }, getColourRGB () { return this.colourCode; }, [isaColoured]: true, [Symbol.hasInstance] (instance) { return instance[isaColoured]; } };
也可以对此进行封装:
function HasInstances (behaviour) { const typeTag = Symbol(); return Object.assign({}, behaviour, { [typeTag]: true, [Symbol.hasInstance] (instance) { return instance[typeTag]; } }) }
class Todo { constructor (name) { this.name = name || 'Untitled'; this.done = false; } do () { this.done = true; return this; } undo () { this.done = false; return this; } } const Coloured = HasInstances({ setColourRGB ({r, g, b}) { this.colourCode = {r, g, b}; return this; }, getColourRGB () { return this.colourCode; } }); const ColouredTodo = ComposeWithClass(Todo, Coloured);