虽然使用了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) {} |
函数重载 | 两个重载:一个接受一个参数,另一个接受三个参数。前两个签名称为重载签名。第三个为实现签名,但这个签名不能直接调用。尽管我们编写了一个在必需参数之后有两个可选参数的函数,但它不能用两个参数来调用 |
其他 | - 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, 地址不能被改,属性可以被修改 | ||
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
- 当不确定类型值,最好不要使用
any
,赋值为unknown
,在具体使用的时候通过类型判断或as
转换类型。也可使用泛型解决interface A<T>{prop: T}
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 |
条件类型 | type Obj<T> = T extends { message: unknown} ? T['message'] : never; |
infer:在extends 条件语句中待推断的类型变量Return: 推翻的返回类型 | 分配条件类型: 想将其不分开,使用 [] 包裹起来: |
映射类型:与别的类型相似,可通过映射类型处理。 - -? : 去掉可选性属性-readonly : 去掉只读属性 -可通过 as 重新命名key的名字 | |
模板字面量类型(用于类型定义的位置,用法) | |
内在字符串操作类型: 可用于字符串操作的类型 |
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()上的方法进行建模Promise | type C = Awaited<boolean | Promise>; // C为number | boolean |
Partial:将所有属性Type设置为可选 | type Partial = {[K in keyof T]?: T[K];} type C = Partial<{a: number, b: boolean}> // C类型: {a?: number | undefined, b?: boolean | undefined} |
Required: 将所有属性Type设置为必填 | type Required = {[K in keyof T]-?: T[K]} type C=Required<{a?: number}>; // {a: number} | ||
Readonly: 将所有属性Type设置为readonly | type 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<string | null | undefined>; // string |
Parameters: 从函数类型的参数中使用的类型 构造一个元组类型Typ | type 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: 构造一个由函数的返回类型组成的类型Type | type 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: 阻止对所含类型的推断 | |||
ThisParameterType: 提取Type的类型作为返回 | type ThisParameterType = T extends (this: infer U, ...args: never) => any ? U : unknown | ||
OmitThisParameter: 从Type中移除this参数 | type OmitThisParameter = unknown extends ThisParameterType ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T | ||
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 声明合并
描述 | 基础使用 |
---|---|
接口合并 | |
命名空间合并 | 命名空间合并: name 没有被 export 导出,无法在另一个同名的namespace中获取。 命名空间与类合并: 命名空间中导出的成员,会成为同名函数的属性 合并命名空间和枚举,会将命名空间导出的成员作为枚举的扩充 |
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 类型值的key | let 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, 类数组默认为false | const 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的实例获取的值)时,会使用该属性 | |
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:指向该对象的默认遍历器方法 | |
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后面的那个大写字符串 | |
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.asyncDispose
和await 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(); // 连接将自动关闭
}