1.重温typescript 语法(上)

162 阅读20分钟

虽然使用了typescript很多年,还没有为它记录一个笔记,这里再重新梳理typescript的使用api,重温typescript的使用。

1.常见类型使用汇总

类型描述基础使用
string, number, boolean, symbol, bigInt基本类型同JS类型一致
数组可以是任何类型的数组string[], number[], boolean[]; Person[]
any特殊类型不希望被检查的类型可设置为any
类型声明方式多中场景使用- 定义变量: let obj: boolean = {a: 1};
- 函数参数:(param: string)=>{}
函数返回值:let a = ():boolean=> { return true}
对象类型类型推断与可选属性b必须传入数字或不传入:()=>const method = ({a, b}: {a: string, b?: number})=> {console.log(a, b);}method({b: 10, a: '30'})
联合类型匹配其中任意类型即可let method=(param: number | string | boolean)=>{}
type类型别名定义type A = {x: 1}; const method=(parm: A)=> {}
interface接口定义,命名对象类型的另一种方式interface A{x: 1}; const method=(parm: A)=> {}
<>, as类型断言<>: const el = document.getElementById("main_canvas");
as: const x = "hello" as number;// 报错
as: const x = "hello" as any as number
文字类型除string ,number,还可用特定字符串与数字作为类型type A = 0 | 1; let x: A = 0; x = 10; // 重新赋值报错,只能0 或者1
!.非空断言符const a:any = {x: undefined}; a.x?.hello();// no error
enum枚举enum ServiceType{ SAY=1, SING=2}; const type: ServiceType=ServiceType.SAY;console.log(type)

注意点

  • 1.type与interface可以自由选择,interface功能都能覆盖type, interface 可进行继承拓展,type只能通过&拓展类型。
  • as const: 所有属性都被分配文字类型。method类型为GET, url类型为https://example.com
const req = { url: "https://example.com", method: "GET" } as const;

2. 函数

描述基础使用
function关键字type FuncType=(name: string) =>void; function test(fn: FuncType) {fn('hello')}
call signatures, 给函数增加额外属性- type FuncType = { desc: string; (name: string): string};
- function test(fn: FuncType) {console.log(fn.desc);}
- const fn:FuncType = (name: string) =>{return name}; fn.desc="i'm func"
- 调用: test(fn);
构造函数签名- class A { constructor(name: string) {console.log(name)}};
- type SomeConstructor = {new (s: string): A;};
-function fn(ctor: SomeConstructor) {return new ctor("hello");}
fn(A);
泛型函数(Generic Functions)function findByKey<Type extends {length: number}, Vector>(key: Type, data: Vector[]) { return key.length ? data[0] : key;}
(回调)函数可选参数function func(arr: number[], callback: (index?: number)=> void, other?: number) {}
函数重载两个重载:一个接受一个参数,另一个接受三个参数。前两个签名称为重载签名。第三个为实现签名,但这个签名不能直接调用。尽管我们编写了一个在必需参数之后有两个可选参数的函数,但它不能用两个参数来调用
image-2.png
其他- void:表示不返回值的函数的返回值(无return或者return没有显示返回任何数)。
- object: 非原始值(string,number,bigint,boolean,symbol,null,undefined)
- unknown: 表示任何值,但是比any安全,因为对值执行任何操作都是不合法的unknown。function f(a: unknown){a.b();} // 报错,a' is of type 'unknown'
- never: 函数从不返回值。function f(x: number) {if(typeof x==='number'){} else { console.log(x, 'x为never')}}
- Function: function ff(f:Function) {f(1)}
剩余参数和实参- function multiply(n: number, ...m: number[]) {} - const arr1=[1,2];const arr2= [12];arr2.push(...arr1);console.log(arr2)
参数解构function sum({ a, b, c }: { a: number; b: number; c: number }) {}

3.对象类型

描述基础使用
定义对象类型interface Person{name: string; } function greet(p: Person){}
对象类型可选属性interface Person{name?: string; } function greet(p: Person){}
对象类型readonly, 地址不能被改,属性可以被修改image-3.png
Index Signatures(索引签名):不确定属性名称,但知道值的类型interface Person{[key: string]: string, a: 1} // a不是string类型,所以报错了
interface Person{[key: string]: string
number, a: 1 // 联合类型,a可以是number}
Excess Property Checks(多余属性检查)interface Person{name: string}; let p:Person ={age: 1} // 报错,Person没有age属性}
处理多余属性:interface Person{name?: string, [propName: string]: any}; let p:Person ={age: 1}
interface extends(接口继承)interface Person{name: string}; interface Woman extends Person{sex: string}
接口继承可多个: interface Woman extends A, B {}
Intersection Types(交叉类型)interface Person{name: string}; interface Basic {age: number}
type Womain = Person & Basic;
Array Type用起来和ES6一样:function p(number[]){}
ReadonlyArray,不应更改的数组const arr: ReadonlyArray = ["red", "green", {a: 1}];
只能读,不能改数组(slice等方法可以,因为不改变原数组),push,pop等不能,数组内部的对象{a:1}可改,因为不影响原数组。
Tuple Types:元组类型,特殊的数组类型。type A =[number, string]; function f(p: A) {} // 只能是 [数字,字符串]的组合类型,每个元素的含义都是“显而易见的”。
元祖其他方式写法:type A=[string, number?];typeB =[string, ...boolean[]];type c=[...string[], boolean];
readonly 元祖类型:创建后就不再修改function doSomething(pair: readonly [string, number]){}

注意点

  • interfere 的extends和交叉类型看起来一致,但是交叉类型如果属性相同,类型不匹配时,类型将作为never类型返回。而extends直接在继承的地方就报错了,Named property 'name' of types 'Person' and 'Basic' are not identical
    image-4.png
  • 当不确定类型值,最好不要使用any,赋值为unknown,在具体使用的时候通过类型判断或as转换类型。也可使用泛型解决interface A<T>{prop: T}
    image-5.png

4 类型操作之泛型

  • 当已知泛型对象为数组,并需使用其固定属性或方法,可明确指出: function a <T>(p: T[]) {console.log(p.length)}
  • 泛型类型参数可使用不同的名称,只要数量与变量方式使用一致。
// 四种方式都一个意思
function a <T>(p: T[]) {return p}
const b: <Type>(p: Type[]) => Type[] = a; // 名字不一样
const c: {<Type2>(p: Type2[]): Type2[]} = a; // 对象字面量的调用签名
interface D {<TypeD>(p: TypeD[]): TypeD[]}
const d: D = a; 
  • 泛型类使用
class Person<Type>{
  calcResult: Type;
  print(a: Type, b: Type) {
    this.calcResult = a + b;
  }
}
let a = new Person<number>();
let b = new Person<string>();
  • 约束使用参数
function validate<T extends {length: number}>(p: T) {console.log(p.length);}
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {}
  • 泛型创建工程函数
function create<T>(c: {new(): T}) : T{ return new c()}
class A {}
console.log(create(A))

5.类型操作 之其他类型

描述基础使用
Keyof 类型运算符:采用对象类型并生成其键的字符串或数字文字联合。type P={x: string, y: string}; type K = keyof P; // 等价于 type K = 'x' /'y';
type P={[key: number]: string}; type K = keyof P; // 等价于 type K = number;
Typeof 类型运算符let s = {x: 1,y:2}; let n: typeof s; // n的类型也变成{x: number, y: number}
ReturnType预定义类型,它接受一个函数类型并生成其返回类型值和类型不是一回事。要引用值的类型,我们使用 typeof
function f(){return {x: 10, y: 2}}; type P=ReturnType
索引访问类型 - 索引type Person = { age: number; name: string; alive: boolean };
type Age=Person['age'];
type AgeAndName=Person['age' | 'name'];
type AllProp=Person[keyof Person]; // 等价 string | number | boolean
索引访问类型 - 使用number获取数组元素类型type Persons = Array<{ age: number; name: string }>;
type Person=Persons[number]; // Person类型为{ age: number; name: string }
type Age = Persons[number]['age'] // number
条件类型image-6.png
type Obj<T> = T extends { message: unknown} ? T['message'] : never;
infer:在extends条件语句中待推断的类型变量
Return: 推翻的返回类型
image-7.png

分配条件类型:
image-8.png

想将其不分开,使用[]包裹起来:
image-9.png
映射类型:与别的类型相似,可通过映射类型处理。
- -?: 去掉可选性属性
-readonly: 去掉只读属性
-可通过as重新命名key的名字
image-10.png
模板字面量类型(用于类型定义的位置,用法)image-11.png
内在字符串操作类型: 可用于字符串操作的类型image-12.png

6. class

详细细节参考注释。

interface Basic {
 getSelfName(): string;
}

// implements可以同时继承多个接口,接口的方法必须全部实现
class Person implements Basic{
    public publicProp: string = 'public可以在任何地方访问成员,默认为public';
    protected protectedProps: string = 'protected only actived in child class';
    private privateProps: string = 'private props';
    name: string;
    readonly department: string = 'saler'; // 只能获取,不能修改
    static staticProps = 0; // 静态成员:通过类构造函数对象本身访问,不与类的实例关联。静态成员也可以被继承。
    private static staticProps2 = 10; // static可与private, protected, public 结合使用,但是 private不能再类的外面进行调用
    constructor(name: string) { // 构造函数
        this.name = name;
    }
    static {
        console.log('静态代码块初始化,可访问类中的任何属性,项目初始化信息可放在这里')
    }
    getSelfName() {
        return this.name; // 方法内部通过this进行访问
    }
    getPrivate() {
        return this.privateProps; // 只能在自己内部访问
    }
    getAnotherPrivate(p: Person) {
        console.log(p.privateProps); // 虽然不能跨类访问,但是同一个类的实例,可以通过参数传递发,访问到类的另一个实例的私有属性
    }
    static calc() {} // 静态方法:通过类构造函数对象本身访问,不与类的实例关联
    thisMethod(p: this) { //this表示定义方法时的上下文
        p.name = this.name;
    }
}
// 可通过括号进行访问私有属性,但是 p.privateProps不能被访问。与JavaScript不同,JavaScript编译后任然是私有属性,使用括号也不能访问
const p = new Person('ZY');  
p['privateProps'];// typescript 可访问
// Person.staticProps2; 不能在外部调用private


// extends: 继承只能继承一个类
class Woman extends Person {
    private _company='company';
    // 参数属性:通过在构造函数参数前加上可见性修饰符public、private、protected或readonly之一来创建。
    // 结果字段获得这些修饰符,并将其定义到类的属性上
    constructor(name: string, private age: number) {
        super(name); // 当继承父级,必须调用super
        this.age = age;
    }
    // 类也可以有访问器,可通过getter,setter方法进行属性设置于获取。注意getter和setter内部操作的是私有变量
    get company() {
        return this._company;
    }
    set company(company: string) {
        this._company = company;
    }
    getAge() {
        console.log('this.age:', this.age)
    }
    getSelfName() {
        return this.name + 'self'; // 覆写父类的方法
    }
    getArrowSelfName = () => {
        return this.name;
    }
    getProtectedProps() {
        return this.protectedProps; // 可在子类中访问protected
    }
    getParentPrivateProps() {
        // return this.privateProps; 不能被访问
    }
    validateProtectedWomain2(w: Person) {
        // console.log(w.protectedProps);// 属性可被继承,但是不能调用Person类的protected属性
    }
}

const w = new Woman('zyh', 10);
const ThisSelf = {
    name: 'thisself',
    getName: w.getSelfName,
    getArrowName: w.getArrowSelfName
}
console.log(ThisSelf.getName()) //  输出:thisselfself, 谁调用this指向谁
console.log(ThisSelf.getArrowName()); // 输出:zyh, 箭头函数绑定定义时候的上下文环境

// 测试this
const p1 = new Person('person');
// w.thisMethod(p1); // 调用者为w,因此thisMethod中方法参数必须是 Woman, 传入Person类不匹配


// 类可以声明索引签名;它们的工作方式与其他对象类型的索引签名相同
// 泛型类: 当使用 实例化泛型类时new,其类型参数的推断方式与函数调用相同
class Man<T>  {
    [s: string]: boolean | ((s: string) => T);
}
const man = new Man<boolean>();
man.sex = true;

// abstract: 
// 抽象方法或抽象字段是尚未提供实现的方法或字段。这些成员必须存在于抽象类中,而抽象类不能直接实例化。
// 抽象类的作用是作为子类的基类,子类会实现所有抽象成员。如果一个类没有任何抽象成员,则称其为具体类
// Base由于它是抽象的,因此我们无法实例化new

abstract class A {
    abstract getName(): string; // 抽象方法:子类必须实现
    getAge() : number{  return 10; } // 可以拥有已经实现的方法,子类不必实现
}

class Child extends A {
    getName(): string {
    throw new Error("Method not implemented.");
    }
}
class Child2 extends A {
    getName(): string {
    throw new Error("Method not implemented.");
    }
}
// 两个类可以互相代替使用,因为它们结构是相同的
const C: Child2 = new Child();

7.模块

JavaScript 规范声明,任何没有声明import、export或顶层的JavaScript 文件await都应被视为脚本,而不是模块。

描述基础使用
ES 模块语法默认导出与导入:A.js : export default xxx; B.js: import xxx from './A.js'
部分导出与导入(As 重命名): A.js :export {A: xx}; B.js: import {A as OhterName } from 'A.js';
将导出的所有对象重命名:import * as A from 'A.js'
导入另一个文件内容,可能会触发影响其他对象的副作用 :A.js: export const PI = 3.14; B.js文件: import('./A.js'); console.log(PI);// 直接使用变量
CommonJs模块导出与导入: A.js: module.exports = {a: 1}; B.js: const { a } = require('./A.js'); const A = require('./A.js')

8.常见类型转换

描述基础使用
Awaited:处理函数await中的操作async或 s.then()上的方法进行建模Promisetype C = Awaited<booleanPromise>; // C为numberboolean
Partial:将所有属性Type设置为可选type Partial = {[K in keyof T]?: T[K];}

type C = Partial<{a: number, b: boolean}> // C类型: {a?: number
undefined, b?: booleanundefined}
Required: 将所有属性Type设置为必填type Required = {[K in keyof T]-?: T[K]}

type C=Required<{a?: number}>; // {a: number}
Readonly: 将所有属性Type设置为readonlytype Required = {readonly [K in keyof T]: T[K]}

type C=Readonly<{a: number}>; const c: C = {a: 1}; c.a = 10;// 不能修改,报错
Record<Keys, Type>:其属性键为Keys,其属性值为Type。此实用程序可用于将一种类型的属性映射到另一种类型type <K extends keyof any, T> = { [P in K]: T }

type A = 'a' | 'b';
type B = {x: string, y: number };
type C = Record<A, B>; // {a: B, b: B}
Pick<Type, Keys>: 选取属性为keys的,构造一个新类型type Pick<T, K> = {[P in K]: T[P];};

type C = { a: {x: string, y: number }; b: string, c: boolean };
type D = Pick<C, 'b' | 'c'>; // {b: string, c:boolean}
Omit<Type, Keys>: 排除属性为keys的,构造一个新类型type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

type C = { a: {x: string, y: number }; b: string, c: boolean };
type D = Omit<C, 'b' | 'c'>; // {a: {x: string, y: number }}
Exclude<UnionType, ExcludedMembers>:排除联合类型UnionType中的 ExcludedMembers所有成员type Exclude<T, U> = T extends U ? never : T;

type A = 'x'|'y'|'z';
type B = 'x';
type C = Exclude<A, B>; // 'y'|'z'
Extract<Type, Union>: 取交集,保留Type类型中存在Union的数据type Extract<T, U> = T extends U ? T : never

type A = 'x'|'y'|'z';
type B = 'x';
type C = Extract<A, B>; // 'x'
NonNullable:排除null和undefined构造新的类型type T = NonNullable<stringnullundefined>; // string
Parameters: 从函数类型的参数中使用的类型构造一个元组类型Typtype Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

type A = Parameters<(a: string, b: number[]) => string>; // [a: string, b: number[]]
ConstructorParameters: 从构造函数类型的类型构造一个元组或数组类型type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

class C { constructor(a: number, b: string) {}}; type A = ConstructorParameters; // [a: number, b: string]
InstanceType: 由type中构造函数的实例类型组成的类型type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any

class A {} type T = InstanceType;
ReturnType: 构造一个由函数的返回类型组成的类型Typetype ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

declare function f1(): { a: number; b: string };
type A = ReturnType; // declare function f1(): { a: number; b: string };
NoInfer: 阻止对所含类型的推断image-13.png
ThisParameterType: 提取Type的类型作为返回type ThisParameterType = T extends (this: infer U, ...args: never) => any ? U : unknown

image-14.png
OmitThisParameter: 从Type中移除this参数type OmitThisParameter = unknown extends ThisParameterType ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T
image-15.png
ThisType: 此实用程序不返回转换后的类型。相反,它用作上下文类型的标记。注意,必须启用noImplicitThis标志才能使用此实用程序。stackoverflow.com/questions/5…
内在字符串操作类型type A = Uppercase<'hello'>; // "HELLO"
type B = Lowercase; // "hello"
type C = Capitalize; // "Hello"
type D = Uncapitalize<'HELLO'>; "hELLO"

9.装饰器

装饰器是一种特殊类型的声明,它可被附加到类声明、方法、访问符、参数或者属性上,不需要改变原类和继承的情况下,动态拓展对象。

// 支持装饰器
{
"compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
  }
}

装饰器使用:

// 类装饰器:的表达式将在运行时作为函数被调用,并且被装饰类的构造函数是其唯一参数。
function DecoratorCls(constructor: Function) {
  console.log('类装饰器:');
  constructor.prototype.decoName = 'decorator name';
  Object.seal(constructor);
  Object.seal(constructor.prototype);
  // constructor.prototype.decoNamed = 'xx'; // 不能再向constructor.prototype添加新的属性
}

//方法装饰器:(包括getter,setter,访问器装饰器),参数有三个:target:对象的原型, propertyKey:方法的名称, descriptor:方法的属性描述符
function configurable1(target: any, propertyKey: string,descriptor: PropertyDescriptor) {
    console.log('方法装饰器,访问器装饰器:', '2', target, propertyKey, descriptor)
}
//方法装饰器:
function configurable(key: keyof PropertyDescriptor, value: boolean) {
  console.log(1) 
  return function method(target: any, propertyKey: string,descriptor: PropertyDescriptor) {
   descriptor[key] = value;
   console.log('3')
  }
}

// 属性装饰器:
// target :当前对象的原型(静态成员是类的构造函数), propertyKey :参数的名称
function propertyDecotrator(target: any, propertyKey: string) {
  console.log('属性装饰器:', target, propertyKey);
}
  
// 参数装饰器: 只能用于观察方法上是否声明了参数
// target :当前对象的原型(静态成员是类的构造函数), propertyKey :参数的名称, index: 参数的索引
function paramDecorator(target: Object, propertyName: string, index: number) {
  console.log('参数装饰器:',target, propertyName, index);
}

// 装饰工厂
// 想自定义如何将装饰器应用于声明,我们可以编写一个装饰器工厂。Decorator Factory只是一个函数,它返回将在运行时由Decorator调用的表达式。
function AddMethod(sex: boolean) {
  return function(constructor: Function) {
    constructor.prototype.isWomain = () => sex;
  }
}


// 类装饰器
@DecoratorCls
// 装饰工厂
@AddMethod(true)
class Person {
  isWomain!: Function; // !加上,可以不用初始化数据(但是我没看到哪里讲了这个...)
  // 属性装饰器:
  @propertyDecotrator
  name: string;
  // 属性装饰器(静态属性):
  @propertyDecotrator
  static department: string = '';

  private _age:number =  0;
  constructor(name: string) {
    this.name = name;
  }
  // 方法装饰器:
  // 执行从上到下进行计算,结果从下到上进行调用:所以打印是 1=> 2=> 3
  @configurable('writable', false)
  @configurable1  
  getName(@paramDecorator key: string, @paramDecorator world: string) {
    console.log('this.name:', this.name);
  }

  // 访问器装饰器: 用法与方法装饰器一致
  // 注意TypeScript 不允许同时修饰单个成员的get和set访问器。
  // 相反,成员的所有修饰器必须应用于按文档顺序指定的第一个访问器。这是因为修饰器应用于属性描述符,它结合了get和set访问器,而不是分别修饰每个声明。? 这句话不太了解
  @configurable('configurable', false)
  get age() {
    return this._age;
  }
  
}

const p = new Person('zyh');
// p.age = 20; // writable 设置为false 不能赋值
console.log('age:', p.age)
console.log('p is woman', p.isWomain())

10 声明合并

描述基础使用
接口合并image-16.png
命名空间合并命名空间合并:
image-17.png
image-18.png

name 没有被 export 导出,无法在另一个同名的namespace中获取。
19.gif
命名空间与类合并:
image-19.png

命名空间中导出的成员,会成为同名函数的属性
image-20.png

合并命名空间和枚举,会将命名空间导出的成员作为枚举的扩充
image-21.png

11 枚举

可将其放到https://www.typescriptlang.org/play/?#code/Q,然后在右侧.js中查看编译结果。

// 异构枚举: 字符串和数字混合使用,称为异构枚举
enum Friut {
  APPLE,  // 可省略,默认是从0开始,APPLE ===0
  ORANGE=2, // 可显示指定数字
  PURPLE="purple" // 字符串枚举
}
enum Fruit2 {
  PURPLE,
  ORANGE = 3,
  APPLE // 前一个枚举成员是数字常量,当前枚举成员未赋值,则在上一个成员数值上自增
}
console.log(Fruit2.APPLE) // 4

// 枚举可作为类型
const a: Friut = Friut.ORANGE;
// 枚举可作为参数传递
const m = (p: { PURPLE: string}) => {console.log(p.PURPLE)}
m(Friut) // purple

type F = keyof typeof Friut; // "APPLE" | "ORANGE" | "PURPLE"

// 枚举进行了反向映射:枚举被编译为一个对象,该对象存储正向 ( name-> value) 和反向 ( value-> name) 映射。
// 对其他枚举成员的引用始终作为属性访问发出,并且永远不会内联
const apple = Friut.APPLE; // 应该是0
const appleValue = Friut[apple]; // 可以根据0获取到APPLE
console.log(appleValue) // 打印APPLE

const right = 1;
// const 枚举,Const 枚举只能使用常量枚举表达式,与常规枚举不同,它们在编译期间会被完全删除
// const 枚举不能有计算成员?? 这条有点奇怪,官网说不能计算,但是实际确计算成功了
const enum Direction {
  UP,
  DOWN,
  LEFT= 'le' + 'ft',
  RIGHT= right * 2
}
const d = [Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT];
console.log(d)

迭代器与生成器可以参考JavaScript中的,更详细。

12 命名空间与模块

模块:包含顶级 import 或者 export 的文件都被当成一个模块,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的。
命名空间:目标是为解决重名问题。命名空间定义标识符的可见范围,但不同的命名空间定义相同的标识符(name)是不会相互干扰的。命名空间本质上是一个对象,作用是将一系列相关的全局变量组织到一个对象的属性。通过namespace定义:

namespace MySpace {
  const name = 'zyh';
  export const id = '1';
  export const myName =() => name;
}
// 调用
console.log(MySpace.id, MySpace.myName())
// console.log(MySpace.name) // 没有导出,不能访问

命名空间与模块区别:

  • 命名空间是位于全局命名空间下的一个普通的带有名字的 JavaScript 对象,使用起来十分容易。但就像其它的全局命名空间污染一样,它很难识别组件之间的依赖关系
  • 都可以包含代码和声明,但是模块可以声明它的依赖
  • 开发过程中不建议用命名空间,但通常在通过 d.ts 文件标记 js 库类型的时候使用命名空间,主要作用是给编译器编写代码的时候参考使用

12 Symbol

符号看起来已经和ES6的Symbol用法一致了。

描述基础使用
类型Symbol表示独一无二的值,Symbol不能用于计算(字符串、number都不行),可调用String转为字符串。Symbol的参数就是它的描述var a = Symbol('a'); var b = Symbol('a'); // a 不等于b
Symbol不会出现在遍历对象的时候(for...in, for...of, Object.keys()等)它也不算私有属性,可以通过Object.getOwnPropertySymbols()获取对象的所有Symbol属性名,
Reflect.ownKeys()可以通过该方法返回对象的所有键名,包含Symbol类型
Symbol.for(): description一样,使用同一个Symbol值。将会被登记在全局环境通过key进行搜索,如果存在,直接返回,不存在new一个。const a = Symbol.for('same'); const b = Symbol.for('same'); a===b; // true
Symbol.keyFor():返回一个已登记的 Symbol 类型值的keylet s1 = Symbol.for("foo");Symbol.keyFor(s1) //foo
Symbol.hasInstance: 当对象调用instanceof运算符时foo instance Foo,实际调用的是Foo[Symbol.hasInstance](foo)class MyClass{ [Symbol.hasInstance](foo: any) {return foo instanceof Array}}; [1, 2, 3] instanceof new MyClass() // 内部实际调用的是hasInstance
Symbol.isConcatSpreadable:表示在concat数组时,是否可以展开。数组默认为true, 类数组默认为falseconst a = [1,2];const b: any=[3,4]; b[Symbol.isConcatSpreadable]=false; const c= a.concat(b, 5); console.log(c); // [1, 2, [3, 4], 5]
Symbol.species:指向构造函数,创建衍生对象(基于new的实例获取的值)时,会使用该属性image-22.png
Symbol.match:指向一个函数,当执行str.match(newObj)是调用。class MyMatcher {[Symbol.match](str: string) {return 'hello world'.indexOf(str);}} const m = new MyMatcher(); console.log('这两种方式一样:', 'e'.match(m as any), mSymbol.match) // 匹配出index为1
Symbol.replace:Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象const x = {};x[Symbol.replace] = (a,b) =>{ console.log(a,b); return a + b};var a = 'Hello'.replace(x, 'World') // 打印出 Hello World(a的值也是这个)
Symbol.search:指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值const x: any = {};x[Symbol.search] = (a: string) => console.log('worldHelloWord'.includes(a), a);var a = 'Hello'.search(x)
Symbol.split:重新定义了字符串对象的split方法的行为const x: any = {};x[Symbol.split] = (a: string) => console.log(a);var a = 'Hello'.split(x)
Symbol.iterator:指向该对象的默认遍历器方法image-23.png
Symbol.toPrimitive: 指向一个方法。该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值let ab: any = {[Symbol.toPrimitive](hint: string) {switch (hint) {case 'number': return 123; default:throw new Error();}}}; console.log(3 * ab) ; // 等于 3 * 123 = 369
Symbol.toStringTag: 用来设定一个字符串。设定的字符串会出现在toString()方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object]或[object Array]中object后面的那个大写字符串image-24.png
Symbol.unscopables指向一个对象。该对象指定了使用with关键字时,哪些属性会被with环境排除。with是已过期的语法,这里不探讨

13. 三斜线指令

/// <reference>: 可以理解为比较特殊的单行注释,会被当做编译器指令进行解析(可理解为import,告诉编译器在编译过程中可引入额外的文件)。

  • 需出现在文件顶部,否则只会被当做普通的单行注释
  • 用于声明文件之间的依赖
  • 用于引入不存在文件时会报错
  • 如果指定了--noResolve编译选项,三斜线引用会被忽略,既不会引入新文件也不会改变给定文件的顺序
/// <reference types="..." /> : 用于声明对某个包的依赖
/// <reference no-default-lib="true"/> : 用于将一个文件标记成默认库
// <reference lib="es2017.string" /> : 允许文件明确包含现有的内置库文件,编译中的一个文件相当于使用 进行编译--lib es2017.string
/// <amd-module name="NamedModule"/> : 用于给编译器传入一个可选的模块名
/// <amd-dependency path="x"/>: 告诉编译器有一个非TS模块依赖需要被注入;不过该指令已经使用import "x"语句代替了

14. using

typescript 5.2引入新的关键字:using。当离开作用域时,可使用Symbol.dispose释放掉任何内容。

Symbol.dispose

是JavaScript中的一个新的全局符号,任何带有Symbol.dispose功能的,都将被视为资源(具有特定生命周期的对象),可以关键字using一起使用。

await using

可以使用Symbol.asyncDisposeawait using来处理需要异步处理的资源。

实例

const getResource = () => {
  console.log('do whatever you want')
  return {
    [Symbol.dispose] : () => {
      console.log('Hooray!')
    }
  }
}
using a = getResource();

// ========================================
 
const getConnection = async () => {
  const connection = await getDb();
  return {
    connection,
    [Symbol.asyncDispose]: async () => {
      await connection.close();
    },
  };
};
{
  await using { connection } = getConnection(); // 连接将自动关闭
}