Mixins

除了传统的 OO 层次结构,另一种从可重用组件构建类的风行办法是通过组合更简略的局部类来构建它们。 您可能相熟 Scala 等语言的 mixin 或特色的想法,并且该模式在 JavaScript 社区中也很风行。

模式依赖于应用具备类继承的泛型来扩大基类。 TypeScript 最好的 mixin 反对是通过类表达式模式实现的。

看一个例子:

class Sprite {  name = "";  x = 0;  y = 0;  constructor(name: string) {    this.name = name;  }}type Constructor = new (...args: any[]) => {};// This mixin adds a scale property, with getters and setters// for changing it with an encapsulated private property:function Scale<TBase extends Constructor>(Base: TBase) {  return class Scaling extends Base {    // Mixins may not declare private/protected properties    // however, you can use ES2020 private fields    _scale = 1;    setScale(scale: number) {      this._scale = scale;    }    get scale(): number {      return this._scale;    }  };}const EightBitSprite = Scale(Sprite);const flappySprite = new EightBitSprite("Bird");flappySprite.setScale(0.8);console.log('Ethan:' ,flappySprite.scale);

本例子和我之前的文章TypeScript 类装璜器的一个例子和应用单步调试搞清楚其运行原理其实很相似,只是没有应用装璜器语法罢了。

应用Scale 对 Sprite 进行拆卸,传入的是 Class Sprite 的定义:

返回的是一个新的函数,但只有不应用该函数去实例化新的类实例,函数体就不会执行。

当初筹备应用 Scale 装璜过后的 Sprite 的扩大类去进行实例化操作了:

行将进入 mixin 外部:

首先执行基类的字段初始化逻辑:

而后才是子类字段的初始化逻辑:

Constrained Mixins

咱们能够对上述 Mixins 做一些革新和加强。

在下面的模式中,mixin 没有类的基础知识,这会使创立你想要的设计变得艰难。

比方,应用下面的 mixin,咱们能够给任意的 Class 增加 _scale 属性。

如果咱们想对此做进一步限度,比方限度 Scale 只能装璜某些特定类型的 Class.

为了对此建模,咱们批改原始构造函数类型以承受泛型参数。

// This was our previous constructor:type Constructor = new (...args: any[]) => {};// Now we use a generic version which can apply a constraint on// the class which this mixin is applied totype GConstructor<T = {}> = new (...args: any[]) => T;

当初,应用这个类型结构器,必须传入一个基类的类型作为类型参数:

type Spritable = GConstructor<Sprite>;

当初,Scale 装璜器只能润饰 Sprite 及其子类了:

当初,如果传入一个并非 Sprite 及其子类的办法进入 Scale 装璜器,会引起语法错误:

另一种通过 Object.defineProperty 实现的 Mixin

// Each mixin is a traditional ES classclass Jumpable {  jump() {}}class Duckable {  duck() {}}// Including the baseclass Sprite {  x = 0;  y = 0;}// Then you create an interface which merges// the expected mixins with the same name as your baseinterface Sprite extends Jumpable, Duckable {}// Apply the mixins into the base class via// the JS at runtimeapplyMixins(Sprite, [Jumpable, Duckable]);let player = new Sprite();player.jump();console.log(player.x, player.y);// This can live anywhere in your codebase:function applyMixins(derivedCtor: any, constructors: any[]) {  constructors.forEach((baseCtor) => {    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {      Object.defineProperty(        derivedCtor.prototype,        name,        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||          Object.create(null)      );    });  });}

把 Jumpable 和 Duckable 的属性通过 Object.defineProperty 赋给 Sprite:

最初的运行时成果:

interface Sprite,能够应用 Duckable 和 Jumpable 类里定义的办法了。

更多Jerry的原创文章,尽在:"汪子熙":