这是我参与「第五届青训营 」伴学笔记创造活动的第 10 天

TypeScript 介绍

  1. TypeScript 是 JavaScript 的超集,供给了 JavaScript 的一切功用,并供给了可选的静态类型、Mixin、类、接口和泛型等特性。
  2. TypeScript 的方针是经过其类型体系协助及早发现过错并提高 JavaScript 开发功率。
  3. 经过 TypeScript 编译器或 Babel 转码器转译为 JavaScript 代码,可运行在任何浏览器,任何操作体系。
  4. 任何现有的 JavaScript 程序都能够运行在 TypeScript 环境中,并只对其间的 TypeScript 代码进行编译。
  5. 在完整保留 JavaScript 运行时行为的基础上,经过引进静态类型界说来提高代码的可保护性,削减可能出现的 bug。
  6. 永远不会改变 JavaScript 代码的运行时行为,例如数字除以零等于 Infinity。这意味着,假如将代码从 JavaScript 迁移到 TypeScript ,即便 TypeScript 认为代码有类型过错,也能够保证以相同的办法运行。
  7. 对 JavaScript 类型进行了扩展,增加了例如 anyunknownnevervoid
  8. 一旦 TypeScript 的编译器完结了检查代码的作业,它就会 擦除 类型以生成最终的“已编译”代码。这意味着一旦代码被编译,生成的普通 JS 代码便没有类型信息。这也意味着 TypeScript 绝不会依据它揣度的类型更改程序的 行为。最重要的是,虽然可能会在编译过程中看到类型过错,但类型体系本身与程序怎么运行无关。
  9. 在较大型的项目中,能够在单独的文件 tsconfig.json 中声明 TypeScript 编译器的配置,并细化地调整其作业办法、严格程度、以及将编译后的文件存储在何处。

东西类型

TypeScript 供给了多种东西类型(实用类型)来协助常见的类型转化,这些东西类型在全局范围内可用。

Awaited

Awaited<Type> 模拟异步函数中的 awaitPromises 上的 .then() 办法回来值类型。

type A = Awaited<Promise<string>>;
// type A = string
type B = Awaited<Promise<Promise<number>>>;
// type B = number
type C = Awaited<boolean | Promise<number>>;
// type C = number | boolean

ConstructorParameters

ConstructorParameters<Type> 从结构函数的类型生成一个包括一切参数类型的元组类型(假如 Type 不是函数,则生成 never 类型)。

type T0 = ConstructorParameters<ErrorConstructor>;
// type T0 = [message?: string]
type T1 = ConstructorParameters<FunctionConstructor>;
// type T1 = string[]
type T2 = ConstructorParameters<RegExpConstructor>;
// type T2 = [pattern: string | RegExp, flags?: string]
type T3 = ConstructorParameters<any>;
// type T3 = unknown[]
type T4 = ConstructorParameters<Function>;
// Type 'Function' does not satisfy the constraint 'abstract new (...args: any) => any'.
  // Type 'Function' provides no match for the signature 'new (...args: any): any'.
// type T4 = never

Exclude

Exclude<UnionType, ExcludedMembers>从联合类型中删去指定类型。

type T0 = Exclude<"a" | "b" | "c", "a">;
// type T0 = "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
// type T1 = "c"
type T2 = Exclude<string | number | (() => void), Function>;
// type T2 = string | number

Extract

Extract<Type, Union> 经过从 Type 中提取可分配给 Union 的一切联合成员来结构一个类型。

type T0 = Extract<"a" | "b" | "c", "a" | "f">;
// type T0 = "a"
type T1 = Extract<string | number | (() => void), Function>;
// type T1 = () => void

InstanceType

InstanceType<Type> 结构一个由 Type 中结构函数的实例类型组成的类型。

class C {
  x = 0;
  y = 0;
}
type T0 = InstanceType<typeof C>;
// type T0 = C
type T1 = InstanceType<any>;
// type T1 = any
type T2 = InstanceType<never>;
// type T2 = never
type T3 = InstanceType<string>;
// Type 'string' does not satisfy the constraint 'abstract new (...args: any) => any'.
// type T3 = any
type T4 = InstanceType<Function>;
// Type 'Function' does not satisfy the constraint 'abstract new (...args: any) => any'.
  // Type 'Function' provides no match for the signature 'new (...args: any): any'.
// type T4 = any

NonNullable

NonNullable<Type> 经过从 Type 中排除 nullundefined 来结构一个类型。

type T0 = NonNullable<string | number | undefined>;
// type T0 = string | number
type T1 = NonNullable<string[] | null | undefined>;
// type T1 = string[]

Omit

Omit<Type, Keys>从方针类型中删去 keys(字符串文字或字符串文字的并集)来结构一个类型。

interface Person {
  name: string;
  age: number;
  location?: string;
}
const bob: Omit<Person, 'age' | 'location'> = {
  name: 'Bob'
  // `Omit` 现已从类型中删去了年纪和位置,它们不能在这儿界说
};
console.log(bob); // { name: 'Bob' }

OmitThisParameter

OmitThisParameter<Type> 从类型中删去 this 参数,创建一个没有 this 参数的新函数类型,泛型会被擦除,只有最终一个重载签名能够被传播到新的函数类型中。假如 Type 没有显式声明 this 参数,则成果仅仅 Type

function toHex(this: Number) {
  return this.toString(16);
}
const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(16);
console.log(fiveToHex()); // 10

Parameters

Parameters<Type> 依据函数类型 Type 的参数中运用的类型结构元组类型。

declare function f1(arg: { a: number; b: string }): void;
type T0 = Parameters<() => string>;
// type T0 = []
type T1 = Parameters<(s: string) => void>;
// type T1 = [s: string]
type T2 = Parameters<<T>(arg: T) => T>;
// type T2 = [arg: unknown]
type T3 = Parameters<typeof f1>;
// type T3 = [arg: {
    // a: number;
    // b: string;
// }]
type T4 = Parameters<any>;
// type T4 = unknown[]
type T5 = Parameters<never>;
// type T5 = never
type T6 = Parameters<string>;
// Type 'string' does not satisfy the constraint '(...args: any) => any'.
// type T6 = never
type T7 = Parameters<Function>;
// Type 'Function' does not satisfy the constraint '(...args: any) => any'.
  // Type 'Function' provides no match for the signature '(...args: any): any'.
// type T7 = never

Partial

Partial<Type> 更改方针中的一切特点为可选。

interface Point {
  x: number;
  y: number;
}
let pointPart: Partial<Point> = {}; // `Partial` 使得 x 与 y 都变成可选
pointPart.x = 10;
console.log(pointPart); // { x: 10 }

Pick

Pick<Type, Keys>从方针类型中挑选指定 keys(字符串文字或字符串文字的并集)来结构一个类型。

interface Person {
  name: string;
  age: number;
  location?: string;
}
const bob: Pick<Person, 'name' | 'location'> = {
  name: 'Bob',
  // location: 'Bob',
  // `Pick` 只保留了名字和位置(可选),年纪已从类型中删去,无法在此处界说
};
console.log(bob); // { name: 'Bob' }

Readonly

Readonly<Type> 结构一个一切特点都设置为 readonly 的类型。

interface Todo {
  title: string;
  description: string;
}
const todo: Readonly<Todo> = {
  title: "Delete inactive users",
  description: "clear clutter",
};
// Readonly将Todo一切特点都设置为只读,它们都无法修改
todo.title = "Hello"; // Cannot assign to 'title' because it is a read-only property.
todo.description = "Hello"; // Cannot assign to 'description' because it is a read-only property.

Record

Record是界说具有特定键类型和值类型的方针类型的简写办法,用于将一种类型的特点映射到另一种类型。Record<Keys, Type> 将结构一个新的方针类型,其特点键为 Keys,特点值为 Type

const nameAgeMap: Record<string, number> = {
  'Alice': 21,
  'Bob': 25
};

这儿的 Record<string, number>相当于{ [key: string]: number }

Required

Required<Type> 更改方针中的一切特点为有必要的。

interface Car {
  make: string;
  model: string;
  mileage?: number;
}
let myCar: Required<Car> = {
  make: 'Ford',
  model: 'Focus',
  mileage: 12000 // `Required` 强制 mileage 有必要界说
};
console.log(myCar); // { make: 'Ford', model: 'Focus', mileage: 12000 }

ReturnType

ReturnType<Type> 提取函数类型的回来类型。

declare function f1(): { a: number; b: string };
type T0 = ReturnType<() => string>;
// type T0 = string
type T1 = ReturnType<(s: string) => void>;
// type T1 = void
type T2 = ReturnType<<T>() => T>;
// type T2 = unknown
type T3 = ReturnType<<T extends U, U extends number[]>() => T>;
// type T3 = number[]
type T4 = ReturnType<typeof f1>;
// type T4 = {
    // a: number;
    // b: string;
// }
type T5 = ReturnType<any>;
// type T5 = any
type T6 = ReturnType<never>;
// type T6 = never
type T7 = ReturnType<string>;
// Type 'string' does not satisfy the constraint '(...args: any) => any'.
// type T7 = any
type T8 = ReturnType<Function>;
// Type 'Function' does not satisfy the constraint '(...args: any) => any'.
  // Type 'Function' provides no match for the signature '(...args: any): any'.
// type T8 = any

ThisParameterType

ThisParameterType<Type> 提取函数类型的 this 参数的类型,假如函数类型没有 this 参数,则为 unknown

function toHex(this: Number) {
  return this.toString(16);
}
function numberToString(n: ThisParameterType<typeof toHex>) {
  return toHex.apply(n);
}
console.log(numberToString(16)); // 10

ThisType

ThisType<Type> 用作上下文 this 类型的符号,但不回来转化后的类型,有必要启用 noImplicitThis 标志才能运用。该接口仅仅一个在 lib.d.ts 中声明的空接口,除了在方针字面量的上下文类型中被识别之外,该接口的行为类似于任何空接口。

type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>; // methods办法中'this'类型是D & M
};
function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  let data: object = desc.data || {};
  let methods: object = desc.methods || {};
  return { ...data, ...methods } as D & M;
}
let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      // this类型为{ x: number, y: number } & { moveBy(dx: number, dy: number): number }
      this.x += dx;
      this.y += dy;
    },
  },
});
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);
console.log(obj); // { x: 15, y: 25, moveBy: [Function: moveBy] }

内部字符串操作类型

为了协助进行字符串操作,TypeScript 包括一组可用于字符串操作的类型。这些类型内置于编译器中以提高功用,而且无法在 TypeScript 顺便的 .d.ts 文件中找到。

Uppercase

Uppercase<StringType> 将字符串中的每个字符转化为大写版本。

type Greeting = "Hello, world"
type ShoutyGreeting = Uppercase<Greeting>
// type ShoutyGreeting = "HELLO, WORLD"
type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<"my_app">
// type MainID = "ID-MY_APP"

Lowercase

Lowercase<StringType> 将字符串中的每个字符转化为等效的小写字母。

type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>
// type QuietGreeting = "hello, world"
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP">
// type MainID = "id-my_app"

Capitalize

Capitalize<StringType> 将字符串中的榜首个字符转化为等效的大写字母。

type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;
// type Greeting = "Hello, world"

Uncapitalize

Uncapitalize<StringType> 将字符串中的榜首个字符转化为等效的小写字母。

type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
// type UncomfortableGreeting = "hELLO WORLD"

装修器

  1. 自从 ES2015 引进 class,当咱们需求在多个不同的类之间共享或许扩展一些办法或行为的时候,代码会变得错综复杂。
  2. 装修器是一种特殊类型的声明,它能够附加到类声明、办法、拜访符、特点、类办法的参数上,以到达扩展类的行为。
  3. 装修器运用 @expression 方式,expression表达式求值后有必要为一个函数,它会在运行时被调用,它接纳三个参数targetnamedescriptor,然后可选性的回来被装修之后的descriptor方针。

装修器工厂

装修器工厂仅仅一个函数,它回来装修器将在运行时调用的表达式。经过装修器工厂办法,能够额外传参,普通装修器无法传参。

function log(param: string) {
  return function(target: any, name: string, descriptor: PropertyDescriptor) {
    console.log('target:', target);
    console.log('name:', name);
    console.log('descriptor:', descriptor);
    console.log('param:', param);
  }
}
class Employee {
  @log('with param')
  routine() {
    console.log('Daily routine');
  }
}
const e = new Employee();
e.routine();

装修器组合

多个装修器能够一起运用到一个声明上,它们能够书写在同一行上:

@f @g x

也能够书写在多行上:

@f
@g
x

当多个装修器运用在一个声明上时会进行如下过程的操作:

  1. 由上至下顺次对装修器表达式求值。
  2. 求值的成果会被当作函数,由下至上顺次调用。
function first() {
  console.log("first(): factory evaluated");
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("first(): called");
  };
}
function second() {
  console.log("second(): factory evaluated");
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("second(): called");
  };
}
class ExampleClass {
  @first()
  @second()
  method() { }
}

这会将以下输出打印到控制台:

first(): factory evaluated
second(): factory evaluated
second(): called
first(): called

类装修器

经过类装修器扩展类的特点和办法,类装修器表达式会在运行时当作函数被调用,装修类的结构函数作为其仅有的参数。

假如类装修器回来一个值,它将用供给的结构函数替换类声明。假如你挑选回来一个新的结构函数,则有必要留意保护原始 prototype,在运行时运用装修器的逻辑不会为你做这件事。

下面一个示例,阐明了怎么覆盖结构函数以设置新的默认值。

function reportableClassDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    reportingURL = "http://www...";
    // 函数重载
    meeting() {
      console.log('重载:Daily meeting!')
    }
  };
}
@reportableClassDecorator
class BugReport {
  type = "report";
  title: string;
  constructor(t: string) {
    this.title = t;
  }
  meeting() {
    console.log('Every Monday!')
  }
}
const bug = new BugReport("Needs dark mode");
console.log(bug.title);
console.log(bug.type);
// 装修器不会更改TypeScript类型,因而类型体系不知道新特点reportingURL
// bug.reportingURL; // Property 'reportingURL' does not exist on type 'BugReport'.
console.log(bug);
bug.meeting(); // 重载:Daily meeting!

办法装修器

办法装修器的表达式会在运行时当作函数被调用,不能用于声明文件、重载或任何其他环境上下文(例如在 declare 类中)。具有以下三个参数:

  1. target: 关于静态成员来说是类的结构函数,关于实例成员是类的原型方针。
  2. name: 成员的名字。
  3. descriptor: 成员的特点描述符。

假如你了解Object.defineProperty,你会立刻发现这正是Object.defineProperty的三个参数。比方经过装修器完结一个办法只读功用,其实便是修改数据描述符中的writable的值 :

function readonly(value: boolean = true) {
  return function(target: any, name: string, descriptor: PropertyDescriptor) {
    descriptor.writable = !value;
  };
}
class Employee {
  @readonly()
  salary() {
    console.log('这是个隐秘');
  }
}
const e = new Employee();
e.salary = () => { // 不可写
  console.log('change');
};
e.salary(); // 这是个隐秘

拜访器装修器

拜访器装修器运用于拜访器的特点描述符,可用于调查、修改或替换拜访器的界说。拜访器装修器不能在声明文件或任何其他环境上下文(例如在 declare 类中)中运用,不允许一起装修单个成员的 getset 拜访器。拜访器装修器的表达式将在运行时作为函数调用,参数与办法装修器相同。

function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
  };
}
class Point {
  private _x: number;
  private _y: number;
  constructor(x: number, y: number) {
    this._x = x;
    this._y = y;
  }
  @configurable(false)
  get x() {
    return this._x;
  }
  @configurable(false)
  get y() {
    return this._y;
  }
}

特点装修器

特点装修器的表达式将在运行时作为函数调用,参数与办法装修器相同,但它的第三个参数为 undefined,由于此时的特点还没有初始化,所以没有配置项。特点装修器不能在声明文件或任何其他环境上下文(例如在 declare 类中)中运用。

例如咱们能够运用特点装修器来记载有关特点的元数据:

import "reflect-metadata";
const formatMetadataKey = Symbol("format");
function format(formatString: string) {
  return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
  return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
class Greeter {
  @format("Hello, %s")
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    let formatString = getFormat(this, "greeting");
    return formatString.replace("%s", this.greeting);
  }
}
console.log(new Greeter('abc').greet()); // Hello, abc

咱们运用函数声明界说了 @format 装修器和 getFormat 函数,这儿的 @format("Hello, %s") 装修器是一个装修器工厂。当调用 @format("Hello, %s") 时,它运用 reflect-metadata 库中的 Reflect.metadata 函数为特点添加元数据条目。当调用 getFormat 时,它会读取元数据值。

参数装修器

参数装修器运用于类结构函数或办法声明的函数,只能用于调查参数是否已在办法上声明。参数装修器不能在声明文件、重载或任何其他环境上下文(例如在 declare 类中)中运用。

参数装修器的表达式将在运行时作为函数调用,具有以下三个参数:

  1. 静态成员的类的结构函数,或实例成员的类的原型。
  2. 成员的称号。
  3. 参数在函数参数列表中的序号索引。

下面示例,@required 装修器添加一个元数据条目,将参数符号为必需。然后 @validate 装修器将现有的 greet 办法包装在一个函数中,该函数在调用原始办法之前验证参数。

import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
  let method = descriptor.value!;
  descriptor.value = function() {
    let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
    if (requiredParameters) {
      for (let parameterIndex of requiredParameters) {
        if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
          throw new Error("Missing required argument.");
        }
      }
    }
    return method.apply(this, arguments);
  };
}
class BugReport {
  type = "report";
  title: string;
  constructor(t: string) {
    this.title = t;
  }
  @validate
  print(@required verbose?: boolean) {
    if (verbose) {
      return `type: ${this.type}\ntitle: ${this.title}`;
    } else {
      return this.title;
    }
  }
}
console.log(new BugReport('abc').print(false)); // abc

元数据

TypeScript 包括对为具有装修器的声明宣布某些类型的元数据的实验性支撑。要启用此实验性支撑,您有必要在命令行或 tsconfig.json 中设置 emitDecoratorMetadata 编译器选项:

tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata
{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

emitDecoratorMetadata 标志启用对与模块 reflect-metadata 一起运用的装修器的发射类型元数据的实验性支撑。启用后,只需导入了 reflect-metadata 库,就会在运行时揭露额外的设计时类型信息。

// @emitDecoratorMetadata
// @experimentalDecorators
// @strictPropertyInitialization: false
import "reflect-metadata";
class Point {
  constructor(public x: number, public y: number) { }
}
class Line {
  private _start: Point;
  private _end: Point;
  @validate
  set start(value: Point) {
    this._start = value;
  }
  get start() {
    return this._start;
  }
  @validate
  set end(value: Point) {
    this._end = value;
  }
  get end() {
    return this._end;
  }
}
function validate<T>(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) {
  let set = descriptor.set!;
  descriptor.set = function(value: T) {
    let type = Reflect.getMetadata("design:type", target, propertyKey);
    if (!(value instanceof type)) {
      throw new TypeError(`Invalid type, got ${typeof value} not ${type.name}.`);
    }
    set.call(this, value);
  };
}
const line = new Line();
line.start = new Point(0, 0);
// @ts-ignore
line.end = {}; // Invalid type, got object not Point.

装修器履行次序

关于怎么运用运用于类内部各种声明的装修器,有一个明确界说的次序:

  1. 对每个实例成员运用特点装修器、拜访器、参数装修器、办法。
  2. 对每个静态成员运用特点装修器、拜访器、参数装修器、办法。
  3. 结构办法参数装修器。
  4. 类装修器。
function extension(params: string) {
  return function(target: any) {
    console.log(params);
  }
}
function method(params: string) {
  return function(target: any, name: string, descriptor: PropertyDescriptor) {
    console.log(params);
  }
}
function attribute(params: string) {
  return function(target: any, name: string) {
    console.log(params);
  }
}
function argument(params: string) {
  return function(target: any, name: string, index: number) {
    console.log(params, index);
  }
}
@extension('类装修器')
class Employee {
  constructor(@argument('结构办法参数装修器') n: string) {
    this.name = n;
  }
  @attribute('静态特点装修器')
  static id: number;
  @attribute('特点装修器')
  name!: string;
  @method('set 办法装修器')
  set age(@argument('set 办法参数装修器') n: number){}
  @method('静态办法装修器')
  static work(@argument('静态办法参数装修器') name: string, @argument('静态办法参数装修器') department: string) {}
  @method('办法装修器')
  salary(@argument('参数装修器') name: string, @argument('参数装修器') department: string) {}
}

打印次序为:

特点装修器
set 办法参数装修器 0
set 办法装修器
参数装修器 1
参数装修器 0
办法装修器
静态特点装修器
静态办法参数装修器 1
静态办法参数装修器 0
静态办法装修器
结构办法参数装修器 0
类装修器

混入(Mixins)

  1. 在 TypeScript 中,implements 只会承继特点的类型,而不会承继实际的逻辑,所以需求依据不同的功用界说多个可复用的类,将它们作为mixins
  2. 由于extends只支撑承继一个父类,咱们能够经过implements来连接多个mixins,而且运用原型链将父类的办法完成复制到子类。
  3. 这就像组件拼合相同,由一堆细粒度的mixins快速搭建起一个功用强大的类。
// @strict: false
// Each mixin is a traditional ES class
class Jumpable {
  jump(this) {
    this.x++;
  }
}
class Duckable {
  duck(this) {
    this.y--;
  }
}
// 在基类上完成希望的 mixins
// class Sprite implements Jumpable, Duckable {
//   x = 0;
//   y = 0;
//   jump: () => void;
//   duck: () => void;
// }
// 或创建一个与基类同名的接口,合并希望的 mixins
class Sprite {
  x = 0;
  y = 0;
}
interface Sprite extends Jumpable, Duckable {}
// 榜首个参数是要混合的主体,第二个参数是要混入的结构函数数组,主要逻辑便是把原型链上面的办法拷贝到要混合的主体上面
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)
      );
    });
  });
}
// 在运行时经过 JS 将 mixins 运用到基类中
applyMixins(Sprite, [Jumpable, Duckable]);
let player = new Sprite();
player.jump();
player.duck();
console.log(player.x, player.y); // 1, -1
  1. 没有运用 extends 而是运用了 implements,把类当成了接口,仅运用它们的类型而非其完成。咱们也没有在类里边完成接口,由于这是咱们在用 mixin 时想防止的。
  2. 为即将 mixin 进来的特点办法创建出占位特点,这告知编译器这些成员在运行时是可用的,这样就能运用 mixin 带来的便当,虽说需求提前界说一些占位特点。
  3. 最终,创建了一个协助函数,帮咱们做混入操作。它会遍历 mixins 上的一切特点,并复制到方针上去,把之前的占位特点替换成真实的完成代码。