【TypeScript】 基础学习

类型

基本类型

JavaScript 语言(注意,不是 TypeScript)将值分成 8 种类型。

  • boolean
  • string
  • number
  • bigint
  • symbol
  • object
  • undefined
  • null

TypeScript 继承了 JavaScript 的类型设计,以上 8 种类型可以看作 TypeScript 的基本类型。

boolean 类型

boolean类型只包含truefalse两个布尔值。

const x: boolean = true;
const y: boolean = false;

上面示例中,变量xy就属于 boolean 类型。

string 类型

string类型包含所有字符串。

const x: string = "hello";
const y: string = `${x} world`;

上面示例中,普通字符串和模板字符串都属于 string 类型。

number 类型

number类型包含所有整数和浮点数。

const x: number = 123;
const y: number = 3.14;
const z: number = 0xffff;

上面示例中,整数、浮点数和非十进制数都属于 number 类型。

bigint 类型

bigint 类型包含所有的大整数。

const x: bigint = 123n;
const y: bigint = 0xffffn;

上面示例中,变量xy就属于 bigint 类型。

bigint 与 number 类型不兼容。

const x: bigint = 123; // 报错
const y: bigint = 3.14; // 报错

上面示例中,bigint类型赋值为整数和小数,都会报错。

注意,bigint 类型是 ES2020 标准引入的。如果使用这个类型,TypeScript 编译的目标 JavaScript 版本不能低于 ES2020(即编译参数target不低于es2020)。

symbol 类型

symbol 类型包含所有的 Symbol 值。

const x: symbol = Symbol();

上面示例中,Symbol()函数的返回值就是 symbol 类型。

object 类型

根据 JavaScript 的设计,object 类型包含了所有对象、数组和函数。

const x: object = { foo: 123 };
const y: object = [1, 2, 3];
const z: object = (n: number) => n + 1;

上面示例中,对象、数组、函数都属于 object 类型。

undefined 类型,null 类型

undefined 和 null 是两种独立类型,它们各自都只有一个值。

undefined 类型只包含一个值undefined,表示未定义(即还未给出定义,以后可能会有定义)。

let x: undefined = undefined;

上面示例中,变量x就属于 undefined 类型。两个undefined里面,第一个是类型,第二个是值。

null 类型也只包含一个值null,表示为空(即此处没有值)。

const x: null = null;

上面示例中,变量x就属于 null 类型。

注意,如果没有声明类型的变量,被赋值为undefinednull,它们的类型会被推断为any

let a = undefined; // any
const b = undefined; // any

let c = null; // any
const d = null; // any

如果希望避免这种情况,则需要打开编译选项strictNullChecks

// 打开编译设置 strictNullChecks
let a = undefined; // undefined
const b = undefined; // undefined

let c = null; // null
const d = null; // null

上面示例中,打开编译设置strictNullChecks以后,赋值为undefined的变量会被推断为undefined类型,赋值为null的变量会被推断为null类型。

包装对象类型

JavaScript 的 8 种类型之中,undefinednull其实是两个特殊值,object属于复合类型,剩下的五种属于原始类型(primitive value),代表最基本的、不可再分的值。

  • boolean
  • string
  • number
  • bigint
  • symbol

上面这五种原始类型的值,都有对应的包装对象(wrapper object)。所谓“包装对象”,指的是这些值在需要时,会自动产生的对象。

"hello".charAt(1); // 'e'

上面示例中,字符串hello执行了charAt()方法。但是,在 JavaScript 语言中,只有对象才有方法,原始类型的值本身没有方法。这行代码之所以可以运行,就是因为在调用方法时,字符串会自动转为包装对象,charAt()方法其实是定义在包装对象上。

这样的设计大大方便了字符串处理,省去了将原始类型的值手动转成对象实例的麻烦。

五种包装对象之中,symbol 类型和 bigint 类型无法直接获取它们的包装对象(即Symbol()BigInt()不能作为构造函数使用),但是剩下三种可以。

  • Boolean()
  • String()
  • Number()

以上三个构造函数,执行后可以直接获取某个原始类型值的包装对象。

const s = new String("hello");
typeof s; // 'object'
s.charAt(1); // 'e'

上面示例中,s就是字符串hello的包装对象,typeof运算符返回object,不是string,但是本质上它还是字符串,可以使用所有的字符串方法。

注意,String()只有当作构造函数使用时(即带有new命令调用),才会返回包装对象。如果当作普通函数使用(不带有new命令),返回就是一个普通字符串。其他两个构造函数Number()Boolean()也是如此。

包装对象类型与字面量类型

由于包装对象的存在,导致每一个原始类型的值都有包装对象和字面量两种情况。

"hello"; // 字面量
new String("hello"); // 包装对象

上面示例中,第一行是字面量,第二行是包装对象,它们都是字符串。

为了区分这两种情况,TypeScript 对五种原始类型分别提供了大写和小写两种类型。

  • Boolean 和 boolean
  • String 和 string
  • Number 和 number
  • BigInt 和 bigint
  • Symbol 和 symbol

其中,大写类型同时包含包装对象和字面量两种情况,小写类型只包含字面量,不包含包装对象。

const s1: String = "hello"; // 正确
const s2: String = new String("hello"); // 正确

const s3: string = "hello"; // 正确
const s4: string = new String("hello"); // 报错

上面示例中,String类型可以赋值为字符串的字面量,也可以赋值为包装对象。但是,string类型只能赋值为字面量,赋值为包装对象就会报错。

建议只使用小写类型,不使用大写类型。因为绝大部分使用原始类型的场合,都是使用字面量,不使用包装对象。而且,TypeScript 把很多内置方法的参数,定义成小写类型,使用大写类型会报错。

const n1: number = 1;
const n2: Number = 1;

Math.abs(n1); // 1
Math.abs(n2); // 报错

上面示例中,Math.abs()方法的参数类型被定义成小写的number,传入大写的Number类型就会报错。

Symbol()BigInt()这两个函数不能当作构造函数使用,所以没有办法直接获得 symbol 类型和 bigint 类型的包装对象,因此SymbolBigInt这两个类型虽然存在,但是完全没有使用的理由。

typeof 运算符

JavaScript => typeof 值 return 类型<字符串>

TypeScript => typeof 值 return 值的TypeScript类型

一段代码会出现以上两种typeof的运算

js 出现在判断类型

ts 出现在类型赋值上

let a = 1;
let b: typeof a;

if (typeof a === "number") {
  b = a;
}

编译后 =>

let a = 1;
let b;
if (typeof a === "number") {
  b = a;
}

ts在编译成js后不会保留typeof 赋值之类的,所以ts的typeof里不能出现值的运算式。

type T = typeof Date(); // 报错

typeof 参数 不能是类型type

type Age = number;
type MyAge = typeof Age; // 报错

数组

数组分两种:

  1. Array 数组

    let a: number[] = [1 ,2 ,3]

  2. tuple 元组

    let a: [number, string, boolean] = [1 ,'是‘, true]

区分:成员类型写在方括号里面的就是元组,写在外面的就是数组

Array

let arr:number[] = []
let arr:Array<number> = []

进阶

type Names = string[];
type Name = Names[0]; // string

类型Names是string字符串数组,类型Name是Names索引0的类型 string

type Names = string[];
type Name = Names[number]; // string

把Names类型数组中的所有的索引的类型为number 值的类型string 赋值给Name

数组类型推断 …

readonly不能和数组泛型一起使用

readonly Array<number> = [] —— 报错

TS提供专用泛型生成只读数组 :

ReadonlyArray<T> => ReadonlyArray<number> = []

Readonly<T[]> => Readonly<number[]> = []

const断言 => const arr = [0,1] as const;

多维数组

T[][] => multi: number[][]= [ [1,2,3], [4,5,6] ];

tuple元组

TS特有数组类型

元组需要在[]里明确配置每一个成员类型,所以成员数量是确定的。

使用扩展运算符可以便是不限成员数量元组

type Names = [string, ...number[] ];

const a: Names = ['A', 1, 2, 3];
const b: Names = ['B', 1, 2, 3, 4];

元组通过[index]读取成员类型

type Names = [string, number];
type Name = Names[0]; //string

//元组所有的成员索引都是number类型
type Tuples = [string, number, boolean];
type Tuple = Tuples[number]; //string | number | boolean

元组通过tuple.length来返回成员数量

PS: 使用扩展运算符 无法推断成员数量! Names: [...string[]] = ['a','b','c']

扩展运算符将数组转换成一个逗号分隔的序列,TS认为这个序列成员数量不确定。

Symbol类型

Symbol类型的值,每一个都不一样,是独一无二的。

let x: symbol = Symbol();
let y: symbol = Symbol();

x === y // false

unique symbol

symbol类型的子类 unique symbol

symbol类型包含了所有的Symbol()的返回值。

unique symbol类型表示某个固定的Symbol()返回值

unique symbol 必须用const声明

const x: unique symbol = Symbol();
//可以通过 类型推断 简化
const y = Symbol();

每个声明的unique symbol类型的变量,他们都是一个随机的值类型,他们的类型为值类型,类型不同,自然使用===会报错

简化理解

const a: "hello" = "hello";
const b: "world" = "world";

a === b; // 报错

因为类型不同,所以在进行赋值的时候也会报错

const a: unique symbol = Symbol();
const b: unique symbol = a; // 报错

const c: typeof a = a;// 解决方案

unique symbol是symbol的子类,所以unique symbol类型的值可以赋值给symbol类型值

const a: unique symbol = Symbol();
const b: symbol = a;

const c: unique symbol = b; //报错

unique symbol 作用之一是用作属性名、类的属性值

const x: unique symbol = Symbol();
const y: symbol = Symbol();

interface Foo{
    [x]: string; //正确
    [y]: string; //报错
}

// 必须用readonly static
class C {
    static readonly foo: unique symbol = Symbol();
}

函数

类型定义

//========================普通定义============================
let add:(x: number, y: number) => number;

//========================对象写法============================
let add:{ // 对象写法
    (x: number, y: number) : number;
    version: string; // 对象写法优势:附加属性
};

//========================接口写法===========================
interface myfn{
    (x: number, y: number) : number;
}
let add: myfn = (a, b) => a+b;

使用对象写法可以在对象中额外附加属性。

箭头函数

// (参数): 返回值 => {方法}

let add:(x: number, y: number) => number = (a,b):number => {
    return a+b;
}

参数解构

type ABC = { a: number; b: number, c: number};

function sum({a,b,c}: ABC){
    console.log(a+b+c);
}

sum({ a:1, b:2, c:3}) // 6

rest参数

入参中剩余的所有参数 ...nums: numer[]数组型, ...args: [boolean, number , string]元组型 需要写出剩余的所有入参的类型。

//数组
function joinNumbers(...nums: number[]) {
    nums.forEach(element => {
        console.log(element);
    });
}


//元组
function fs(...args: [boolean, number, string]){
    args.forEach(element => {
        console.log(element);
    })
}


joinNumbers(1,2,3,4,5,6,7,8,9,0)  // 1 2 3 4 5 6 7 8 9 0
fs(false,2,"张三") // false 2 张三

readonly只读参数

function arrSum(arr: readonly number[]){
    ...
    a[0] = 0; //报错 无法对readonly进行赋值操作
}

never类型

用于返回值的类型上,表示在执行这个方法后肯定不会返回值,而是直接结束。

如果调用,则在该函数的调用位置终止,永远不会继续执行后续的代码。(不会返回上层栈)

  1. 报错

    function Err(): never{
        throw new Error();
    }
    
  2. 无限循环

    function sing(): never{
        while(true){
            console.log('sing')
        }
    }
    

高阶函数

一个函数的返回值还是一个函数

let sumplus = (a: number, b: number) => (x: number, y: number) => a+b+x+y;
console.log(sumplus(1,2)(3,4)) // 10

函数重载

在TS 里的函数重载需要用到typeof来进行类型判断,而不可以像Java一样直接进行重载

function reverse(str: string): string;
function reverse(int: number): number;
function reverse(obj: string | number): string | number{
	if(typeof obj === 'string'){
        return obj + ' : string';
    } else {
        return obj + 1;
    }
}
console.log(reverse(4)) // 5
console.log(reverse('张三')) // 张三 : string

PS : 重载的类型描述与函数的具体实现之间,不能有其他代码,否则报错。

// 报错
function fn(x: boolean): void;
function fn(x: string): void;
function fn(x: number | string) { // x应为 boolean | string
  console.log(x);
}

并且入参类型最宽泛(any)的因放在最下面。

对象( class )重载

class strBuilder{
    add(num: number): number;
    add(str: string): string;
    add(obj: number | string): number|string{
        if(typeof obj === 'string'){
            return obj + 'string';
        } else {
            return obj + 1;
        }
    }
}
let builder = new strBuilder();
console.log(builder.add('张三')) // 张三string
console.log(builder.add(1)) // 2

使用联合类型代替重载

// 写法一
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any): number {
  return x.length;
}

// 写法二
function len(x: any[] | string): number {
  return x.length;
}

对象

type Person = {
    readonly id: number; // readonly只读
    name: string;
    age: number;
    sex?: string;
    toString(id: number, name: string, age: number, sex?: string) : void;

}

const user: Person = {
    id: 1350000,
    name: '张珊',
    age: 23,
    //sex: '女'
    toString: (id: number, name: string, age: number, sex?: string) => {
        console.log(id + ", " + name + ", " + age);
    }
}
user.toString(user.id,user.name,user.age) // 1350000, 张珊, 23
user.id = 0 //报错

属性索引

string索引

type Obj = {
    [key:string] : string,
    id:string,
    name: string,
}

const object: Obj = {
    id: '000',
    name: '李四',
    custom1: '1',
    custom2: '2',
    custom3: '3',
    custom4: '4',
    custom5: '5',
    custom6: '6',
    custom7: '7',
}

number索引

type Obj = {
    [key:number] : string,
    id:string,
    name: string,
}

const object: Obj = {
    id: '000',
    name: '李四',
    1: '1',
    2: '2',
    3: '3',
    4: '4',
    5: '5',
    6: '6',
    7: '7',
}

console.log(object[1] + ", " + object.id + ", " + object.name)
//1, 000, 张三

symbol索引

interface接口

在大多数情况下优先使用灵活性较高的interface,如需复杂的类型运算则用type

interface支持替换以上对象章节中所有的type。

继承

  1. interface 继承 interface
  2. interface 继承 type (可以相互继承)
  3. interface 继承class

接口合并

接口名相同的接口可以合并他们的成员定义。

interface Box {
  height: number;
  width: number;
}

interface Box {
  length: number;
}

上面示例中,两个Box接口会合并成一个接口,同时有heightwidthlength三个属性。

与type异同

特性typeinterface
定义对象类型type Country = { name: string; capital: string; };interface Country { name: string; capital: string; }
表示非对象类型支持不支持
添加属性的方式使用 & 运算符(交叉类型)使用继承
自动合并同名定义不支持,会报错支持,会自动合并
包含属性映射(mapping)支持不支持(会报错)
使用 this 关键字不支持(会报错)支持
扩展原始数据类型支持不支持(会报错)
表达复杂类型(交叉类型和联合类型)支持不支持

表示非对象类型

type Name = string;

添加属性

type Animal = {
  name: string;
};
type Bear = Animal & {
  honey: boolean;
};

interface Animal {
  name: string;
}
interface Bear extends Animal {
  honey: boolean;
}

type Foo = { x: number };
interface Bar extends Foo {
  y: number;
}

interface Foo { x: number }
type Bar = Foo & { y: number };

自动合并同名定义

type A = { foo: number }; // 报错
type A = { bar: number }; // 报错

interface A {
  foo: number;
}
interface A {
  bar: number;
}

const obj: A = {
  foo: 1,
  bar: 1,
};

属性映射

interface Point {
  x: number;
  y: number;
}

// 可以
type PointCopy1 = {
  [Key in keyof Point]: Point[Key];
};

interface PointCopy2 {
  [Key in keyof Point]: Point[Key];
}
// 报错

使用 this 关键字

interface Foo {
  add(num: number): this;
}

type Foo = {
  add(num: number): this;
};
// 报错

扩展原始数据类型

type MyStr = string & {
  type: "new";
};

interface MyStr extends string {
  type: "new";
}
// 报错

表达复杂类型

type A = {
  /* ... */
};
type B = {
  /* ... */
};

type AorB = A | B;
type AorBwithName = AorB & {
  name: string;
};

Class类

TS全面支持面向对象编程,封装了属性和方法。

implements interface

extend class

修饰符

  1. public:公开,外部可以自由访问
  2. private:私有,实例和子类都无法访问
  3. protected:保护,实例无法访问,子类可以访问

抽象类

abstract class A {}

抽象类被当成模板,无法实例化。

抽象类当作基类使用,在它的基础上定义子类。

泛型

//========================函数============================
function id<T>(arg: T): T {
  return arg;
}

// 写法一
let myId: <T>(arg: T) => T = id;

// 写法二
let myId: { <T>(arg: T): T } = id;

//========================接口============================
interface Box<T> {
  contents: T;
}

let box: Box<string>;

//========================Class============================
class Pair<K, V> {
  key: K;
  value: V;
}


//========================Type============================
type Tree<T> = {
  value: T;
  left: Tree<T> | null;
  right: Tree<T> | null;
};

泛型设置默认值

class Generic<T = string> {
  list: T[] = [];

  add(t: T) {
    this.list.push(t);
  }
}

泛型一旦设置默认值,就代表是可选参数,如果<>里有多个参数,可选参数必须在必选参数之后。

<T = boolean , U> //报错
<U , T = boolean> //正确

泛型的约束条件

很多类型参数并不是无限制的,对于传入的类型存在某个必要属性

function comp<Type>(a: Type, b: Type) {
  if (a.length >= b.length) {
    return a;
  }
  return b;
}

上例中,泛型Type中必须包含.lengh属性,如不包含就会报错。

解决方案:

使用<T extends { length: number}>写明必须的条件

function comp<T extends { length: number }>(a: T, b: T) {
  if (a.length >= b.length) {
    return a;
  }
  return b;
}

comp([1, 2], [1, 2, 3]); // 正确
comp("ab", "abc"); // 正确
comp(1, 2); // 报错

PS: 以上例子中,TypeScript 会从实际参数推断出T的值,所以可以不用写<>

进阶

<T, U extends T>
// 或者
<T extends U, U>


// 约束条件不能引用类型参数自身
<T extends T>               // 报错
<T extends U, U extends T>  // 报错

Enum枚举

// 编译前
enum Color {
  Red, // 0
  Green, // 1
  Blue, // 2
}

// 编译后
let Color = {
  Red: 0,
  Green: 1,
  Blue: 2,
};

类型断言

写法

// 语法一:<类型>值
<Type>value;
let bar: T = <T>foo;

// 语法二:值 as 类型
value as Type;
let bar: T = foo as T;

场景:

type T = "a" | "b" | "c";

let foo = "a";
let bar: T = foo; // 报错
let bar: T = foo as T; // 正确

模块

namespace

namespace 用来建立一个容器,内部的所有变量和函数,都必须在这个容器里面使用。

装饰器 (Java注解@)

用来在定义类、方法、属性的时候修改其的行为

装饰器结构

type Decorator = (
  value: DecoratedValue, //所装饰的对象
  context: { //上下文对象
    kind: string; //必有,字符串,表示所装饰对象的类型,可能的值为`class`,`method`,`getter`,`setter`,`field`,`accessor`
    name: string | symbol; //必有,字符串或Symbol值,所装饰对象的名字,如类名、属性名
    addInitializer?(initializer: () => void): void; //可选,函数,用来添加类的初始化逻辑
    static?: boolean; //可选,布尔值,表示所装饰对象是否为类的静态成员
    private?: boolean; //可选,布尔值,表示所装饰对象是否为类的私有成员
    access: { //可选,对象,包含某个值的get和set方法
      get?(): unknown;
      set?(value: unknown): void;
    };
  }
) => void | ReplacementValue;

类装饰器

结构

type ClassDecorator = (
  value: Function, // 值为类本身
  context: { 
    kind: "class"; //类型为class
    name: string | undefined; // 类名
    addInitializer(initializer: () => void): void; //类的初始化方法
  }
) => Function | void;

类装饰器的使用

  1. 在类实例化的时候添加一个greet()方法

    function simpleDecorator(value:Function, context:any){
        if(context.kind == 'class'){
            value.prototype.greet = () => console.log("hello simpleDecorator, class name: " + context.name)
        };
    }
    
    @simpleDecorator
    class Decorators{ 
        constructor(){}
    }
    
    let arr = new Decorators();
    arr.greet(); // hello simpleDecorator, class name: Decorators
    
  2. 返回函数来代替当前的构造方法

    function constructorDecorator(value:any, context:any){
        let count = 0; //记录class实例化次数
    
        const wrapper = (...args: any[]) => { //代替class的构造方法
            count++; //先实现实例化次数+1
            const obj = new value(...args); //通过class自带的构造方法实例化
            obj.count = count; //创建一个class类的count属性,把count赋给obj.count
            return obj; //返回class的实例
        };
    
        wrapper.prototype = value.prototype; //保证两个方法的原型对象一致,否则新的构造函数`wrapper`的原型对象与MyClass不一样,通不过`instanceof`运算
        return wrapper;
    }
    
    @constructorDecorator
    class MyClass{ }
    
    let arr = new MyClass(); //count = 1
    let arr1 = new MyClass(); //count = 2
    console.log(arr instanceof MyClass); // true
    console.log(arr.count); //1
    console.log(arr1.count); //2
    
  3. 返回新类,替代原来装饰的类

    function classDecorator(value:any, context:any){
        let count = 0; //记录class实例化次数
    
        return class extends value{ //返回集成装饰类的子类
            constructor(...args:any[]){ //构造方法
                super(...args); //先执行装饰类的构造方法
                count++; //count+1
                this.count = count; 创建一个class类的count属性,把count赋给obj.count
            }
        }
    }
    
    @classDecorator
    class MyClass{ }
    
    let arr = new MyClass(); //count = 1
    let arr1 = new MyClass(); //count = 2
    console.log(arr instanceof MyClass); //true
    console.log(arr.count); //1
    console.log(arr1.count); //2
    
  4. 通过类装饰器,禁止使用new命令新建类的实例

    function forbidNewDecorator(value:any, context:any){
        if(context.kind === 'class'){ // 判断类型是不是class
            return function(...args:any[]){ //在实例化的时候执行
                if(new.target !== undefined){ //实例化是否用到了new
                    throw new TypeError('Forbid New!') //用到new,报错
                }
                return new value(...args); //没用到,new Class
            }as unknown as typeof Person
        }
    }
    
    @forbidNewDecorator
    class Person{
        name: any;
        constructor(name: any){
            this.name = name;
        }
    }
    
    const robinNew = new Person('Lihua'); //报错
    const robin = Person('ZhangSan');
    console.log(robin.name) // ZhangSan
    

方法装饰器

结构

type ClassMethodDecorator = (
  value: Function, //方法本身
  context: { //上下文对象
    kind: "method"; // 固定字符串,表示方法
    name: string | symbol; //装饰的方法名
    static: boolean; //是否为静态方法,只读属性
    private: boolean; //是否为私有方法,只读属性
    access: { get: () => unknown }; //对象,包含方法的存取器,只有`get()`取值,没有`set()`赋值
    addInitializer(initializer: () => void): void; //为方法增加初始化函数
  }
) => Function | void;

方法装饰器的本质方法,效果相同

function trace(decoratedMethod) {
  // ...
}

class C {
  @trace
  toString() {
    return "C";
  }
}

// `@trace` 等同于
C.prototype.toString = trace(C.prototype.toString);
  1. 替换原始函数

    function replaceMethod(){ 
        return function(){ //直接返回方法,表示替换原函数
            return `How are you, ${this.name}?`
        }
    }
    
    class Teacher{
        constructor(name){
            this.name = name;
        }
    
        @replaceMethod
        hello(){
            return `Hi, ${this.name}`
        }
    }
    
    const teacher1 = new Teacher('ZhangSan')
    console.log(teacher1.hello()) //How are you, ZhangSan?
    
  2. 在原始方法的基础上新增内容

    function log(originalMethod: any , context: ClassMethodDecoratorContext){
        const methodName = context.name; //获取方法名
        function replacementMethod(this: any, ...args:any[]){ //创建新方法
            console.log(`——————前置内容: ${methodName}——————`)
            const result = originalMethod.call(this, ...args); //用.call调用原方法,并把返回值赋给result
            console.log(`——————后置内容: ${methodName}——————`)
            return result; //返回方法的返回值
        }
        return replacementMethod; //返回新方法
    }
    
    class Student{
        constructor(name){
            this.name = name;
        }
    
        @log //使用注解
        hello(){
            console.log( `Hi, ${this.name}`)
        }
    }
    
    const student1 = new Student('LiSi');
    student1.hello()
    //——————前置内容: hello——————
    //Hi, LiSi
    //——————后置内容: hello——————
    
  3. 注解实现延迟执行器

    function delay(milliseconds: number = 0){ //外层装饰器,用来接收变量
        return function(value: any, context: any){ //真正的方法装饰器函数
            if(context.kind === 'method'){ // 判断是否是装饰的方法
                return function(...args:any[]){ //返回新的方法
                    setTimeout(() => { //延迟函数
                        value.apply(this, args);
                    }, milliseconds); 
                }
            }
        }
    }
    
    class Logger{
        @delay(5000)
        log(msg: string){
            console.log(msg);
        }
    }
    
    const logger = new Logger();
    logger.log('Hello World!') //延迟5秒执行
    
  4. addInitializer为装饰的方法增加初始化函数

    function collect(value:any, context:any){
        context.addInitializer(function(){
            if(!this.collectedMethodKeys){
                this.collectedMethodKeys = new Set();
            }
            this.collectedMethodKeys.add(context.name);
        })
    }
    
    class C{
        @collect
        C_1(){}
    
        @collect
        C_2(){}
    }
    const inst = new C();
    console.log(inst.collectedMethodKeys)//Set(2) { 'C_1', 'C_2' }
    

属性装饰器

结构

type ClassFieldDecorator = (
  value: undefined, // 无用,值一定为undefined
  context: {
    kind: "field"; // 属性装饰器kind一定是field
    name: string | symbol; // 属性名
    static: boolean; // 属性是否静态
    private: boolean; // 属性是否私有
    access: { get: () => unknown; set: (value: unknown) => void }; //存取器对象
    addInitializer(initializer: () => void): void; //在值初始化之前执行
  }
) => (initialValue: unknown) => unknown | void;
  1. 对属性默认值进行操作

    function Logged(value: any, context: any){
        if(context.kind === 'field'){ //属性的类型一定是字符串类型的`field`
            return function(value: any){ // value属性的默认值
                console.log(`name: ${context.name}, OldValue: ${value}`);
                return value * 2; //返回操作后的新值
            }
        }
    }
    
    class D{
        @Logged count = 2;
    }
    
    const d = new D();
    console.log(d.count);
    //name: count, OldValue: 2
    //4
    
  2. 获取装饰值的存取器,并通过存取器对实例的属性进行改变

    let acc; //定义存取器
    function accGet(value: any, context: any){
        if(context.kind === 'field'){
            acc = context.access; //指定存取器
        }
    }
    
    class Color{
        @accGet name = "green"
    }
    
    const green = new Color();
    console.log(green.name); //green
    console.log(acc.get(green)); //green
    acc.set(green,'red');
    console.log(green.name); //red
    

getter和setter装饰器

accessor装饰器

装饰器引入了一个新的属性修饰符accessor

class C {
  accessor x = 1;
}

// 等同于
class C {
  #x = 1;

  get x() {
    return this.#x;
  }

  set x(val) {
    this.#x = val;
  }
}

结构

type ClassAutoAccessorDecorator = (
  value: { //包含get和set方法,可以直接替换原生的方法
    get: () => unknown; 
    set: (value: unknown) => void;
  },
  context: {
    kind: "accessor";
    name: string | symbol;
    access: { get(): unknown; set(value: unknown): void };
    static: boolean;
    private: boolean;
    addInitializer(initializer: () => void): void;
  }
) => {
  get?: () => unknown;
  set?: (value: unknown) => void;
  init?: (initialValue: unknown) => unknown;
} | void;

对属性的存取器加上日志输出

function loggeds(value: any, context:any){
    if(context.kind === 'accessor'){
        let { get, set } = value;
        return{
            get(){
                console.log(`getter name ${context.name}`)
                return get.call(this);
            },
            set(value:any){
                console.log(`setter name ${context.name}, value ${value}`);
                return set.call(this,value);
            },
            init(initialValue: any){
                console.log(`init name ${context.name}, value ${initialValue}`);
            },
        }
    }
}

class Accresor{
    @loggeds accessor x = 1;
}
const acce = new Accresor(); //init : init name x, value 1
acce.x; //get : getter name x
acce.x = 999; //set : setter name x, value 999

装饰器的执行顺序

装饰器执行分为两个阶段

  1. 评估:计算@后表达式的值,得到的应该是函数
  2. 应用:将评估装饰器后得到的函数,应用到所装饰的对象

举例

function dbug(str:string){
    console.log(`评估 :${str}`);
    function log(value: any, context: any){
        console.log(`应用 :${str}`);
    };
    return log;
}

function logi(str:string){
    console.log(str);
    return str;
}

@dbug("类装饰器")
class T{
    @dbug("静态属性装饰器")
    static s1 = logi("静态属性值");

    @dbug("原型方法")
    add(){}

    @dbug("实例属性")
    s2 = logi("实例属性值")
}

const Ts = new T()
// 评估 :类装饰器
// 评估 :静态属性装饰器
// 评估 :原型方法
// 评估 :实例属性
// 应用 :原型方法
// 应用 :静态属性装饰器
// 应用 :实例属性
// 应用 :类装饰器
// 静态属性值
// 实例属性值

根据例子可得执行顺序

  1. 评估:先从类开始,然后类的内部根据出现的先后顺序评估
  2. 应用:先从原型方法,然后是静态属性和方法,接下来是实例属性,最后是类

PS:实例属性值在类初始化阶段并不执行,在类初始化结束后执行

​ 如果一个方法或者属性有多个装饰器,则先执行内层在执行外层

参考

https://typescript.p6p.net/typescript-tutorial/intro.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值