关于TypeScript学习笔记汇总
本文承接上面内容做后续总结
类(Class)
1. 类的定义
下面是一个最基本的类:
class Point {}
复制代码1.1 字段(Fields)
一个字段声明会创建一个公共(public)可写入(writeable)的属性。类型声明是可选的,如果没有指定则是any
class Point {
    x: number; 
    y: number;
}
const pt = new Point();
pt.x = 0;
pt.y = 0;
复制代码可以给字段设置初始值,当类实例化时,会自动执行
class Point {
    x = 0;
    y = 0;
}
const pt = new Point();
console.log(`${pt.x}, ${pt.y}`);// Prints 0, 0
// class 属性的初始化器将用于推断其类型
pt.x = "0"; // Error,Type 'string' is not assignable to type 'number'.
复制代码1.2 --strictPropertyInitialization
strictPropertyInitialization 控制是否需要在构造函数中初始化类字段
class BadGreeter {
    name: string;
    // Property 'name' has no initializer and is not definitely assigned in the constructor.
}
复制代码class GoodGreeter {
    name: string;
    constructor() {
        this.name = "hello";
    }
}
复制代码如果打算通过构造函数以外的其他方法(例如,可能有一个外部库填充了类的一部分)初始化字段,则可以使用断言操作符!
class OKGreeter {
    // Not initialized, but no error
    name!: string;
}
复制代码1.3 构造函数(Constructors)
constructor 是一种用于创建和初始化class创建的对象的特殊方法。
构造函数的背景知识:
- 在一个类中只能有一个名为 “constructor” 的特殊方法。 一个类中出现多次构造函数 (constructor)方法将会抛出一个SyntaxError错误。
- 在一个构造方法中可以使用super关键字来调用一个父类的构造方法。
- 如果没有显式指定构造方法,则会添加默认的 constructor 方法。
- 如果不指定一个构造函数(constructor)方法, 则使用一个默认的构造函数(constructor)。
TypeScript中和普通函数相似,可以添加带有类型注解、默认值和重载的参数:
class Point {
    x: number;
    y: number;
    // Normal signature with defaults
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
}
复制代码重载:
class Point {
    // Overloads
    constructor(x: number, y: string);
    constructor(s: string);
    constructor(xs: any, y?: any) {
        // TBD
    }
}
复制代码- 构造函数不能有类型参数
- 构造函数不能有返回类型注释——返回的总是类实例类型
和JavaScript种一样,如果有一个基类,那么在使用这个类之前,要在构造函数体中调用 super ()
class Base {
    k = 4;
}
class Derived extends Base {
    constructor() {
        // Prints a wrong value in ES5; throws exception in ES6
        console.log(this.k);
        // Error  'super' must be called before accessing 'this' in the constructor of a derived class.
        super();
    }
}
复制代码1.4 方法(Methods)
类上的函数属性称为方法,方法可以使用与函数和构造函数相同的所有类型注解
class Point {
    x = 10;
    y = 10;
    scale(n: number): void {
        this.x *= n;
        this.y *= n;
    }
}
复制代码2. readonly
可以用 readonly 修饰符作为前缀,设置只读属性,防止赋值到构造函数之外的字段。
class Greeter {
    readonly name: string = "world";
    constructor(otherName?: string) {
        if (otherName !== undefined) {
            this.name = otherName;
        }
    }
    err() {
        this.name = "not ok"; // Error   Cannot assign to 'name' because it is a read - only property.
    }
}
const g = new Greeter();
g.name = "also not ok"; // Error   Cannot assign to 'name' because it is a read - only property.
复制代码2.1 readonly 和 const 的区别
- 
readonly - 用于属性
- 修饰的变量只能在构造函数中初始化
- 通常在 interface、class、type类型中使用它,也可以用来定义一个函数的参数
- 在编译时检查
- 用于别名,可以修改属性,看下面示例:
 // 用于别名时,可以修改属性 interface Person { name: string; age: number; } interface ReadonlyPerson { readonly name: string; readonly age: number; } let writablePerson: Person = { name: "Person McPersonface", age: 42, }; // works let readonlyPerson: ReadonlyPerson = writablePerson; console.log(readonlyPerson.age); // prints '42' writablePerson.age++; console.log(readonlyPerson.age); // prints '43' 复制代码
- 
const - 用于变量
- const声明变量,就必须立即初始化,并且不能重新分配其值
- 在运行时检查(在支持 const 语法的 JavaScript 运行时环境中)
 
const foo = 123; // 变量
interface ReadonlyPerson {
    readonly name: string; // 属性
}
复制代码3 Getters / Setters
类里也有存取器:
class C {
    _length = 0;
    get length() {
        return this._length;
    }
    set length(value) {
        this._length = value;
    }
}
复制代码可以看一下编译后的内容:
var C = /** @class */ (function () {
    function C() {
        this._length = 0;
    }
    Object.defineProperty(C.prototype, "length", {
        get: function () {
            return this._length;
        },
        set: function (value) {
            this._length = value;
        },
        enumerable: true,
        configurable: true
    });
    return C;
}());
复制代码注意:
- 如果只设置 get,没有 set,则属性为只读
- 如果没有指定 setter 参数的类型,则从 getter 的返回类型推断出来
- Getters 和 setter 必须具有相同的 可见属性修饰符(public、protected、private)
TypeScript 4.3以来,可以使用不同类型的存取器来获取和设置
class Thing {
    _size = 0;
    get size(): number {
        return this._size;
    }
    set size(value: string | number | boolean) {
        let num = Number(value);
        // Don't allow NaN, Infinity, etc
        if (!Number.isFinite(num)) {
            this._size = 0;
            return;
        }
        this._size = num;
    }
}
复制代码4. 继承(Class Heritage)
- 子类继承父类后子类的实例就拥有了父类中的属性和方法,可以增强代码的可复用性
- 将子类公用的方法抽象出来放在父类中,自己的特殊逻辑放在子类中重写父类的逻辑
class Animal {
    move() {
        console.log("Moving along!");
    }
}
class Dog extends Animal {
    woof(times: number) {
        for (let i = 0; i < times; i++) {
            console.log("woof!");
        }
    }
}
const d = new Dog();
// Base class method
d.move();
// Derived class method
d.woof(3);
复制代码- 子类可以重写父类的方法,然后通过super可以调用父类上的方法和属性
class Father {
    greet() {
        console.log("Hello, world!");
    }
}
class Child extends Father {
    // 重写父类方法,但是这里name参数要遵循 父类的方法的约定
    greet(name?: string) {
        if (name === undefined) {
            super.greet(); // 调用父类 greet 方法
        } else {
            console.log(`Hello, ${name.toUpperCase()}`);
        }
    }
}
const d = new Child();
d.greet(); // Hello, world!
d.greet("reader"); // Hello, READER
复制代码4.1 初始化顺序(Initialization Order)
class Base {
    name = "base";
    constructor() {
        console.log("My name is " + this.name);
    }
}
class Derived extends Base {
    name = "derived";
}
const d = new Derived(); // Prints "base", not "derived"
复制代码上面的示例可以看出来类初始化的顺序:
- Base 初始化
- Base 的 构造函数执行
- Derived 初始化
- Derived 的 构造函数执行
Base的构造函数运行的时候看到了自己的name值,Derived的初始化阶段还没有运行
5.类的修饰符
5.1 public
类的成员属性默认修饰符是public,在任何地方都可以访问。
class Greeter {
    public greet() {
        console.log("hi!");
    }
}
const g = new Greeter();
g.greet();
复制代码5.2 protected
protected 表示受保护的,只能在声明它的类和子类访问,其他地方不可以
class Greeter {
    public greet() {
        console.log("Hello, " + this.getName());
    }
    protected getName() {
        return "hi";
    }
}
class SpecialGreeter extends Greeter {
    public howdy() {
        // OK to access protected member here
        console.log("Howdy, " + this.getName());
    }
}
const g = new SpecialGreeter();
g.greet(); // OK
g.getName(); // Error    `Property 'getName' is protected and only accessible within class 'Greeter' and its subclasses.Property 'getName' is protected and only accessible within class 'Greeter' and its subclasses.`
复制代码注意:
如果子类重写父类的protected的属性,如果重写的时候没有加protected,则会变成public的:
class Base {
    protected m = 10;
}
class Derived extends Base {
    // No modifier, so default is 'public'
    m = 15;
}
const d = new Derived();
console.log(d.m); // OK
复制代码5.3 private
private 私有属性,只能声明它的类中访问,子类和其他地方都不可以
class Base {
    private x = 0;
}
const b = new Base();
// Can't access from outside the class
console.log(b.x); // Property 'x' is private and only accessible within class 'Base'.
// 继承的子类
class Derived extends Base {
    showX() {
        // Can't access in subclasses
        console.log(this.x); // Property 'x' is private and only accessible within class 'Base'.
    }
  }
复制代码注意:
private 的属性允许类型检查期间通过括号的方式来访问里面属性
class MySafe {
    private secretKey = 12345;
}
const s = new MySafe();
// Not allowed during type checking
console.log(s.secretKey); //   Property 'secretKey' is private and only accessible within class 'MySafe'.
// OK
console.log(s["secretKey"]); // 12345
复制代码和ts的private不同,js中的私有字段(#)在编译后仍然私有,并不提供像括号方式进行访问:
class Dog {
    #barkAmount = 0;
    personality = "happy";
    constructor() { }
}
复制代码6.静态成员 (Static Members)
类(class)通过 static 关键字定义静态方法。不能在类的实例上调用静态方法,而应该通过类本身调用。背景阅读Static Members(MDN)
class MyClass {
    static x = 0;
    static printX() {
        console.log(MyClass.x);
    }
}
console.log(MyClass.x);
MyClass.printX();
复制代码6.1 静态成员也可以使用 public、 protected 和 private 修饰符
class MyClass {
    private static x = 0;
}
console.log(MyClass.x);// Property 'x' is private and only accessible within class 'MyClass'.
复制代码6.2 静态成员也可以继承
class Base {
    static getGreeting() {
        return "Hello world";
    }
}
class Derived extends Base {
    myGreeting = Derived.getGreeting();
}
复制代码6.3 特殊名称(Special Static Names)
类本身是函数,而覆写 Function 原型上的属性通常认为是不安全的,因此不能使用一些固定的静态名称,函数属性像 name、length、call 不能被用来定义 static 成员:
class S {
    static name = "S!";
    //   Static property 'name' conflicts with built -in property 'Function.name' of constructor function 'S'.
  }
复制代码6.4 为什么没有静态类(Why No Static Classes?)
在TypeScript(和JavaScript)中没有静态类(static class),其他语言强制所有数据和方法都在一个class中,所以需要静态类,而ts中没有这种限制。我们可以通过以下方式完成这个工作:
// Unnecessary "static" class
class MyStaticClass {
    static doSomething() { }
}
// Preferred (alternative 1)
function doSomething() { }
// Preferred (alternative 2)
const MyHelperObject = {
    dosomething() { },
};
复制代码6.5 静态块(static Blocks in Classes)
静态块允许您编写具有自己作用域的语句序列,该语句序列可以访问类中的私有字段。
class Foo {
    private static count = 0;
 
    get count() {
        return Foo.count;
    }
 
    static {
        try {
            const lastInstances = loadLastInstances();
            Foo.count += lastInstances.length;
        }
        catch {}
    }
}
复制代码7. 泛型类(Generic Classes 后面泛型也会提到)
类像接口一样可以是泛型的,当一个泛型类用new实例化时,他的类型参数推断方式和函数一样
class Box<Type> {
    contents: Type;
    constructor(value: Type) {
        this.contents = value;
    }
}
const b = new Box("hello!"); // const b: Box<string>
复制代码类可以像接口一样使用泛型约束和默认值
注意:
泛型类的静态成员永远不能引用类的类型参数
下面这个代码会有语法报错:
class Box<Type> {
    static defaultValue: Type;  // Static members cannot reference class type parameters.
}
复制代码记住类型会被完全抹除。在运行时,只有一个 Box.defaultValue 属性槽。这也意味着如果设置 Box<string>.defaultValue 是可以的话,这也会改变 Box<number>.defaultValue,这样是不好的。
8. 类运行时的 this(this at Runtime in Classes)
TypeScript 并不会更改 JavaScript 运行时的行为,并且 JavaScript 有时会出现一些奇怪的运行时行为。
就比如 JavaScript 处理 this 就很奇怪:
class MyClass {
    name = "MyClass";
    getName() {
        return this.name;
    }
}
const c = new MyClass();
const obj = {
    name: "obj",
    getName: c.getName,
};
// Prints "obj", not "MyClass"
console.log(obj.getName());
复制代码默认情况下,函数中 this 的值取决于函数是如何被调用的。在这个例子中,因为函数通过 obj 被调用,所以 this 的值是 obj 而不是类实例。
这显然不是你所希望的。TypeScript 提供了一些方式缓解或者阻止这种错误。
8.1 箭头函数(Arrow Functions)
如果你有一个函数,经常在被调用的时候丢失 this 上下文,使用一个箭头函数或许更好些。
class MyClass {
  name = "MyClass";
  getName = () => {
    return this.name;
  };
}
const c = new MyClass();
const g = c.getName;
// Prints "MyClass" instead of crashing
console.log(g());
复制代码这里有几点需要注意下:
- this的值在运行时是正确的,即使 TypeScript 不检查代码
- 这会使用更多的内存,因为每一个类实例都会拷贝一遍这个函数。
- 你不能在子类使用 super.getName,因为在原型链中并没有入口可以获取父类方法。
8.2 this 参数(this parameters)
在 TypeScript 方法或者函数的定义中,第一个参数且名字为 this 有特殊的含义。该参数会在编译的时候被抹除:
// TypeScript input with 'this' parameter
function fn(this: SomeType, x: number) {
  /* ... */
}
复制代码// JavaScript output
function fn(x) {
  /* ... */
}
复制代码TypeScript 会检查一个有 this 参数的函数在调用时是否有一个正确的上下文。不像上个例子使用箭头函数,我们可以给方法定义添加一个 this 参数,静态强制方法被正确调用:
class MyClass {
  name = "MyClass";
  getName(this: MyClass) {
    return this.name;
  }
}
const c = new MyClass();
// OK
c.getName();
 
// Error, would crash
const g = c.getName;
console.log(g());
// The 'this' context of type 'void' is not assignable to method's 'this' of type 'MyClass'.
复制代码这个方法也有一些注意点,正好跟箭头函数相反:
- JavaScript 调用者依然可能在没有意识到它的时候错误使用类方法
- 每个类一个函数,而不是每一个类实例一个函数
- 父类方法定义依然可以通过 super调用
8.3 this 类型(this Types)
在类中,有一个特殊的名为 this 的类型,会动态的引用当前类的类型,让我们看下它的用法:
class Box {
  contents: string = "";
  
  // set在这里相当于:(method) Box.set(value: string): this
  set(value: string) {
    this.contents = value;
    return this;
  }
}
复制代码这里,TypeScript 推断 set 的返回类型为 this 而不是 Box 。让我们写一个 Box 的子类:
class ClearableBox extends Box {
  clear() {
    this.contents = "";
  }
}
 
const a = new ClearableBox();
const b = a.set("hello");  // const b: ClearableBox
复制代码你也可以在参数类型注解中使用 this :
class Box {
  content: string = "";
  sameAs(other: this) {
    return other.content === this.content;
  }
}
复制代码这与写 other: Box 不同,如果你有一个派生类,它的 sameAs 方法只接受来自同一个派生类的实例。
class Box {
  content: string = "";
  sameAs(other: this) { // 这个地方用 other: Box 不报错
    return other.content === this.content;
  }
}
 
class DerivedBox extends Box {
  otherContent: string = "?";
}
 
const base = new Box();
const derived = new DerivedBox();
derived.sameAs(base);
// Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'.
  // Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.
复制代码8.4 基于 this 的类型保护(this-based type guards)
你可以在类和接口的方法返回的位置,使用 this is Type 。当搭配使用类型窄化 (比如if 语句),目标对象的类型会被收窄为更具体的 Type。
class FileSystemObject {
  isFile(): this is FileRep {
    return this instanceof FileRep;
  }
  isDirectory(): this is Directory {
    return this instanceof Directory;
  }
  isNetworked(): this is Networked & this {
    return this.networked;
  }
  constructor(public path: string, private networked: boolean) {}
}
 
class FileRep extends FileSystemObject {
  constructor(path: string, public content: string) {
    super(path, false);
  }
}
 
class Directory extends FileSystemObject {
  children: FileSystemObject[];
}
 
interface Networked {
  host: string;
}
 
const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");
 
if (fso.isFile()) {
  fso.content;  // const fso: FileRep
} else if (fso.isDirectory()) {
  fso.children;   // const fso: Directory
} else if (fso.isNetworked()) {
  fso.host;  // const fso: Networked & FileSystemObject
}
复制代码一个常见的基于 this 的类型保护的使用例子,会对一个特定的字段进行懒校验(lazy validation)。举个例子,在这个例子中,当 hasValue 被验证为 true 时,会从类型中移除 undefined:
class Box<T> {
  value?: T;
 
  hasValue(): this is { value: T } {
    return this.value !== undefined;
  }
}
 
const box = new Box();
box.value = "Gameboy";
 
box.value;  
// (property) Box<unknown>.value?: unknown
 
if (box.hasValue()) {
  box.value;
  // (property) value: unknown
}
复制代码9. 参数属性(Parameter Properties)
TypeScript 提供了特殊的语法,可以把一个构造函数参数转成一个同名同值的类属性。这些就被称为参数属性(parameter properties)。你可以通过在构造函数参数前添加一个可见性修饰符 public private protected 或者 readonly 来创建参数属性,最后这些类属性字段也会得到这些修饰符
class Params {
  constructor(
    public readonly x: number,
    protected y: number,
    private z: number
  ) {
    // No body necessary
  }
}
const a = new Params(1, 2, 3);
console.log(a.x);
// (property) Params.x: number
console.log(a.z);
// Property 'z' is private and only accessible within class 'Params'.
复制代码10. 类表达式(Class Expressions)
类表达式与声明相似,唯一区别是类表达式不需要名称,尽管我们可以通过绑定的标识符进行引用:
const someClass = class<Type> {
  content: Type;
  constructor(value: Type) {
    this.content = value;
  }
};
 
const m = new someClass("Hello, world");  
// const m: someClass<string>
复制代码11. 抽象类及成员(abstract Classes and Members)
TypeScript 中,类、方法、字段都可以是抽象的(abstract)。
- 抽象描述一种抽象的概念,无法被实例化,只能被继承
- 无法创建抽象类的实例
- 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现
abstract class Base {
  abstract getName(): string;
 
  printName() {
    console.log("Hello, " + this.getName());
  }
}
 
// 抽象类不能实例化
const b = new Base();
// Cannot create an instance of an abstract class.
复制代码子类去继承抽象类,并实现抽象成员:
class Derived extends Base {
  getName() {
    return "world";
  }
}
 
const d = new Derived();
d.printName();
复制代码如果没有实现抽象类的抽象成员会报错:
class Derived extends Base {
	// Non-abstract class 'Derived' does not implement inherited abstract member 'getName' from class 'Base'.
  // forgot to do anything
}
复制代码11.1 重写(override) 和 重载(overload)
- 重写是指子类重写继承自父类中的方法
- 重载是指为同一个函数提供多个类型定义
// 重写
class Animal{
    speak(word:string):string{
        return '动作叫:'+word;
    }
}
class Cat extends Animal{
    speak(word:string):string{
        return '猫叫:'+word;
    }
}
let cat = new Cat();
console.log(cat.speak('hello'));
//--------------------------------------------
// 重载
function double(val:number):number
function double(val:string):string
function double(val:any):any{
  if(typeof val == 'number'){
    return val *2;
  }
  return val + val;
}
let r = double(1);
console.log(r);
复制代码11.2 继承 和 多态
- 继承(Inheritance)子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism)父类定义的一个方法,继承它的不同子类有不同的实现
abstract class Animal{
    name: string;
    abstract eat():void;
}
class Cat extends Animal{
    eat():void{
        console.log('吃鱼')
    }
}
class Dog extends Animal{
    eat():void{
        console.log('吃骨头')
    }
}
// 这里 Cat 和 Dog 对eat的实现不同,这里是多态
let cat = new Cat();
cat.eat()
let dog = new Dog();
dog.eat()
复制代码12. 抽象构造签名(Abstract Construct Signatures)
有时,希望接受某个类构造函数,这个函数是从某个抽象类派生的类的实例。
你可能会写下面的代码:
function greet(ctor: typeof Base) {
  const instance = new ctor();
  // Cannot create an instance of an abstract class.
  instance.printName();
}
复制代码TypeScript 会报错,告诉你正在尝试实例化一个抽象类。毕竟根据 greet 的定义,这段代码应该是合法的:
// Bad!
greet(Base);
复制代码如果写一个函数来接受带有构造签名的东西:
function greet(ctor: new () => Base) {
  const instance = new ctor();
  instance.printName();
}
greet(Derived); // Ok
greet(Base); // Error
// Argument of type 'typeof Base' is not assignable to parameter of type 'new () => Base'.
// Cannot assign an abstract constructor type to a non-abstract constructor type.
复制代码现在 TypeScript 会正确的告诉你,哪一个类构造函数可以被调用,Derived 可以,因为它是具体的,而 Base 是不能的。
13. 类之间的关系(Relationships Between Classes)
大部分时候,TypeScript 的类跟其他类型一样,会被结构性比较。
举个例子,这两个类可以互相替换,因为它们是相同的:
class Point1 {
    x = 0;
    y = 0;
}
class Point2 {
    x = 0;
    y = 0;
}
// OK
const p: Point1 = new Point2();
复制代码类似的还有,即使没有明显的继承,类的子类型之间可以建立关系:
class Person {
  name: string;
  age: number;
}
 
class Employee {
  name: string;
  age: number;
  salary: number;
}
 
// OK
const p: Person = new Employee();
复制代码这看起来有些简单,但还有一些例子可以看出奇怪的地方。
空类没有任何成员。在一个结构化类型系统中,没有成员的类型通常是任何其他类型的父类型。所以如果你写一个空类(只是举例,你可不要这样做),任何东西都可以用来替换它:
class Empty {}
 
function fn(x: Empty) {
  // can't do anything with 'x', so I won't
}
 
// All OK!
fn(window);
fn({});
fn(fn);
复制代码14.装饰器(Decorators)
要启用对 decorator 的实验性支持,您必须在命令行或 tsconfig.json 中启用 experimentalDecorators
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}
复制代码- 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为
- 常见的装饰器有类装饰器、属性装饰器、方法装饰器和参数装饰器
- 装饰器的写法分为普通装饰器和装饰器工厂
给定一个装饰器 @sealed,我们可以将 sealed 函数写成:
function sealed(target) {
    // do something with 'target' ...
}
复制代码装饰器工厂(Decorator Factory)
Decorator Factory 只是一个函数,它返回的表达式将在运行时由 Decorator 调用。
我们可以用下面的方式写一个装饰器工厂:
function color(value: string) {
    // this is the decorator factory, it sets up
    // the returned decorator function
    return function (target) {
        // this is the decorator
        // do something with 'target' and 'value'...
    };
}
复制代码14.1 类的装饰器(Class Decorator)
类装饰器在类声明之前声明,类装饰器应用于类的构造函数,用来监视、修改或替换类定义。
类装饰器的表达式将在运行时作为函数调用,修饰累的构造函数是它唯一的参数。
// 定义一个装饰器给User添加name和eat属性
function addNameEat(constructor: Function){
    constructor.prototype.name = "xiaoming"; 
    constructor.prototype.eat = function () { 
        console.log("eat"); 
    };
}
@addNameEat
class User {
    name: string;
    eat: Function;
    constructor() { }
}
let u: User = new User();
console.log(u.name);
u.eat();
复制代码给装饰器传值:
function addNameEatFactory(name: string){
    return function addNameEat(constructor: Function){
        constructor.prototype.name = name; 
        constructor.prototype.eat = function () { 
            console.log("eat"); 
        };
    }
}
@addNameEatFactory('xiaoming')
class User {
    name: string;
    eat: Function;
    constructor() { }
}
复制代码做类的替换:
接下来我们有一个如何重写构造函数来设置新默认值的例子:
// 把旧的类替换掉,但是属性可以多不能少
function reportableClassDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        reportingURL = "http://www...";
    };
}
@reportableClassDecorator
class BugReport {
    type = "report";
    title: string;
    constructor(t: string) {
        this.title = t;
    }
}
const bug = new BugReport("Needs dark mode");
console.log(bug.title); // Prints "Needs dark mode"
console.log(bug.type); // Prints "report"
// Note that the decorator _does not_ change the TypeScript type
// and so the new property `reportingURL` is not known
// to the type system:
bug.reportingURL;//  Property 'reportingURL' does not exist on type 'BugReport'.
复制代码14.2 方法装饰器(Method Decorator)
方法装饰器用来装饰方法,方法装饰器表达式在运行时将被作为一个函数调用,具有以下三个参数:
- 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数是方法的名称
- 第三个参数是方法描述符
下面是应用于 Greeter 类的方法装饰器 (@enumerable)的示例:
class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    @enumerable(false)
    greet() { // 实例方法
        return "Hello, " + this.greeting;
    }
}
复制代码我们用下面函数定义装饰器@enumerable
function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    };
}
复制代码这里装饰器@enumerable (false)是一个装饰器工厂,当调用@enumerable (false)时,它修改属性描述符的enumerable属性。
getter/setter 装饰器
TypeScript 不允许对单个成员的 get 和 set 访问器进行装饰。相反,该成员的所有 decorator 必须应用于按文档顺序指定的第一个访问器。这是因为装饰符应用于属性描述符,该属性描述符将 get 和 set 访问器组合在一起,而不是将每个声明分开。
下面是应用于 Point 类成员的 accessor decorator (@configable)的示例:
// 定义装饰器 configurable
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;
    }
}
复制代码14.3 属性装饰器(Property Decorator)
- 属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数
- 属性装饰器用来装饰属性
- 第一个参数对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 第二个参数是属性的名称
 
//修饰实例属性,target是类的原型
function upperCase(target: any, propertyKey: string) {
    // 拿到老的value值
    let value = target[propertyKey];
    const getter = function () {
        return value;
    }
    // 用来替换的setter
    const setter = function (newVal: string) {
        value = newVal.toUpperCase()
    };
    // 替换属性,先删除原先的属性,再重新定义属性
    if (delete target[propertyKey]) {
        Object.defineProperty(target, propertyKey, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    }
}
// 静态成员来说是类的构造函数
function staticDecorator(target: any, propertyKey: string){
     console.log(target,propertyKey); // [Function: User] { age: 10 }
}
class User {
    @upperCase
    name: string = 'xiaoming' // 实例属性
    @staticDecorator
    public static age: number = 10 //静态属性
    constructor() { }
}
let u: User = new User();
console.log(u.name); // XIAOMING
复制代码14.4 参数装饰器(Parameter Decorators)
会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元数据
- 第1个参数对于静态成员是类的构造函数,对于实例成员是类的原型对象
- 第2个参数的名称
- 第3个参数在函数列表中的索引
// IOC容器,比如Nest.js 大量用到了参数装饰器
// 参数装饰器 
function addAge(target: any, methodName: string, paramsIndex: number) {
    console.log(target); // { login: [Function (anonymous)] }
    console.log(methodName); // login
    console.log(paramsIndex); // 1
    target.age = 10;
}
class User {
    age: number;
    login(username: string, @addAge password: string) {
        console.log(this.age, username, password); // 10 xiaoming 1234
    }
}
let u = new User();
u.login('xiaoming', '1234')
复制代码14.5 装饰器执行顺序
先看下面示例:
function Class1Decorator() {
    return function (target: any) {
        console.log("类1装饰器");
    }
}
function Class2Decorator() {
    return function (target: any) {
        console.log("类2装饰器");
    }
}
function MethodDecorator() {
    return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
        console.log("方法装饰器");
    }
}
function Param1Decorator() {
    return function (target: any, methodName: string, paramIndex: number) {
        console.log("参数1装饰器");
    }
}
function Param2Decorator() {
    return function (target: any, methodName: string, paramIndex: number) {
        console.log("参数2装饰器");
    }
}
function PropertyDecorator(name: string) {
    return function (target: any, propertyName: string) {
        console.log(name + "属性装饰器");
    }
}
@Class1Decorator()
@Class2Decorator()
class User {
    @PropertyDecorator('name')
    name: string = 'xiaoming';
    @PropertyDecorator('age')
    age: number = 10;
    @MethodDecorator()
    greet(@Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
}
复制代码执行结果:
name属性装饰器
age属性装饰器
参数2装饰器
参数1装饰器
方法装饰器
类2装饰器
类1装饰器
复制代码- 类装饰器总是最后执行,后写的先执行
- 方法和方法参数的装饰器,先执行参数装饰器。
- 有多个参数装饰器时:从最后一个参数依次向前执行
- 方法和属性装饰器,谁在前面谁先执行。因为参数属于方法一部分,所以参数会一直紧紧挨着方法执行
简单总结:先上后下,先内后外执行,类比React组件的componentDidMount 先上后下、先内后外























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)
