一文弄懂TypeScript中的混合(Mixin)

1.前言

由于TypeScrip中的类不支持多继承,所以引入了混合(Mixin)的特性,可以间接实现多继承的效果。

2.正文

复制代码
// 声明一个汽车类Vehicle,它有drive方法
class Vehicle {
  drive(): void {
    console.log('驾驶')
  }
}

// 声明轿车类Car,它有work方法
class Car extends Vehicle{
  work(): void {
    console.log('上班代步')
  }
}

// 声明越野车类Suv,它有cross方法
class Suv extends Vehicle{
  cross(): void {
    console.log('山地越野')
  }
}

// 声明跨界车类Crossover, 继承Car和Suv,既可以上班代步,也可以山地越野
class Crossover extends Car, Suv{ // 这里ts报错:Classes can only extend a single class. 子类只能继承一个父类

}

以上示例为了实现让Crossover类同时拥有work和cross方法,我们尝试一个类同时继承多个类,ts报错了,下面利用Mixin的特性,来实现这一功能:

复制代码
class Vehicle {
    drive(): void {
      console.log('驾驶')
    }
  }
  
  // 声明轿车类Car,它有work方法
  class Car extends Vehicle{
    work(): void {
      console.log('上班代步')
    }
    getSpace(): void {
        console.log('small');
    }
  }
  
  // 声明越野车类Suv,它有cross方法
  class Suv extends Vehicle{
    cross(): void {
      console.log('山地越野')
    }
    getSpace(): void {
        console.log('big');
    }
  }
  
  // 声明跨界车类Crossover, 继承Car和Suv,既可以上班代步,也可以山地越野
  class Crossover extends Vehicle {
  
  }

  // 这里需要创建需要合并类(Crossover)的同名接口,并继承多个类Car、Suv
  interface Crossover extends Car, Suv {}
  
  applyMixins(Crossover, [Car, Suv])
  
  let crossover = new Crossover()
  
  // 该混合函数摘自TypeScript官方手册,如果derivedCtor和constructors类中存在同名方法,则后者覆盖前者
  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)
        );
      });
    });
  }

  crossover.drive() // 驾驶
  crossover.work() // 上班代步
  crossover.cross() // 山地越野
  crossover.getSpace() // big,同名方法getSpace是Suv中的,applyMixins函数传入的第二个参数数组最后一个类是Suv

以上Crossover类通过Mixin实现继承Car和Suv中的方法,并且也继承了第二级类Vehicle中的方法,需要注意的是,对于同名的方法,会存在方法覆盖的问题。

3.拓展

利用Mixin的思想还能做一些有趣的事儿!

复制代码
interface AnimalConfig {
    type: string
    name: string
    voice: string
}

class AnimalSpeak {
    speak(config: AnimalConfig): void {
        const {type, name, voice} = config
        console.log(`${type + name}的叫声是${voice}~`);
    }
    dogSpeak(name: string): void {
        console.log(`小狗${name}的叫声是汪汪~`);
    }
    catSpeak(name: string): void {
        console.log(`小猫${name}的叫声是喵喵~`);
    }
}

function extend<T, U>(to: T, from: U) {
    for (const key in from) {
        ;(to as T & U)[key] = from[key] as any
    }
    return to as T & U
}
const context = new AnimalSpeak()
const instance = AnimalSpeak.prototype.speak.bind(context)

extend(instance, context)

instance({type: '小猫', name: '咪咪', voice: '喵喵'}) // 猫咪咪的叫声是喵喵~

instance.dogSpeak('哮天犬') // 小狗哮天犬的叫声是汪汪~

以上示例中context是AnimalSpeak类的实例对象,instance是一个方法,利用extend方法,将context对象中的属性方法赋值给instance,这样instance既可以当做方法使用,也可以当做对象调用其中的方法,非常灵活!

脚踏实地行,海阔天空飞~

相关推荐
@@@@@@@@545 小时前
JavaScript 的缺点与 TypeScript 的弥补
开发语言·javascript·typescript
用户0934077735146 小时前
HarmonyOS WPS Open SDK 实践:最小打开闭环与 enableEdit 分支处理
typescript·harmonyos
柯克七七1 天前
我把 bug 修得太干净了,测试组以为这个模块本来就没问题
前端·javascript·typescript
退休倒计时1 天前
【每日一题】LeetCode 39. 组合总和 TypeScript
算法·leetcode·typescript
yyt3630458411 天前
KlineChartQuant Tooltip 高频交互 200FPS 性能优化完整复盘
前端·经验分享·性能优化·typescript·vue·交互·chrome devtools
用户0934077735141 天前
HarmonyOS WPS Open SDK 实践:办公审批附件「打开—编辑—回传」代码实现
typescript·harmonyos
Flynt1 天前
TypeScript 5.8 让我少踩了一个大坑,顺手再聊聊 --erasableSyntaxOnly
前端·javascript·typescript
退休倒计时2 天前
【每日一题】LeetCode 17. 电话号码的字母组合 TypeScript
算法·leetcode·typescript
无情的西瓜皮3 天前
MCP 协议实战:从零搭建一个 MCP 服务器(Node.js 版)
vue.js·人工智能·typescript·node.js
退休倒计时3 天前
【每日一题】LeetCode 78. 子集 TypeScript
算法·leetcode·typescript