本文最后更新于 2024-03-22T23:32:34+00:00
前言
TS 是 JS 的超集:它是完全包含的 JS,并扩展了很多功能
比较重要的扩展功能:
特点:
- 编写项目维度:
- 代码编译维度:支持自主检测
- 运行流程维度:编译、检测、产出
- 复杂特性:支持模块化、泛型、接口……
基础
TS 的里面写的类型,仅仅用于编译时进行检测,编译完成的产物里面不会存在
1 2 3 4 5
| const inEable: boolean = true
const inEable = true
|
基础类型
《基础类型》官方文档:https://typescript.bootcss.com/basic-types.html
与 JS 一样的
boolean、number、string、undefined、null
1 2 3 4 5
| const a: boolean = true; const b: string = "1"; const c: number = 2; const d: undefined = undefined; const e: null = null;
|
与 JS 不一样的
Array - 数组
语法
1 2 3 4 5 6 7
| const Array1: number[] = [1, 2, 3]
const Array2: Array<number> = [1, 2, 3]
const Array3: Array<any> = [1, 2, 3, "32", true];
|
只读数组
1 2 3 4 5 6 7 8 9 10 11
| let Array4: ReadonlyArray<number> = [1, 2, 3, 4] Array4[0] = 7 Array4.push(4); Array4.length = 40; Array4.splice(0, 1);
let Array5 = []; Array5 = Array4;
Array5 = Array4 as [];
|
编译结果
没啥特殊的,就是去掉了类型定义
1 2 3 4 5 6 7
| const Array1 = [1, 2, 3]
const Array2 = [1, 2, 3]
const Array3 = [1, 2, 3, "32", true];
|
Tuple - 元组
一种特殊的数组类型,它允许你指定一个固定长度的数组,且数组中每个位置的元素可以有不同的类型。
语法
1 2
| let Tuple: [string, number, boolean]; Tuple = ["Hello", 123, true];
|
特点
- 固定长度: 元组有固定的长度,不能在创建后添加或删除元素。
- 类型限制: 元组中的每个位置可以有不同的数据类型,但是一旦定义后,类型不能改变。
场景
函数返回多个值时或者处理一些固定长度的数据结构
编译结果
没啥特殊的,就是去掉了类型定义
1 2
| let Tuple; Tuple = ["Hello", 123, true];
|
Enum - 枚举
用于定义一组有名字的常量。
语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| enum Color1 { Red, Green, Blue, }
let myColor1: Color1 = Color1.Red; console.log(myColor1); let myColor1_1: Color1 = Color1['Green']; console.log(myColor1_1);
enum Color2 { Red = 3, Green = 6, Blue = 9, } let myColor2: Color2 = Color2.Red; console.log(myColor2);
enum Color3 { Red = "Red", Green = "Green", Blue = "Blue", } let myColor3: Color3 = Color3.Red; console.log(myColor3);
enum Color4 { Red, Green = "Green", Black, Blue = 9, Yellow, } let myColor4: Color4 = Color4.Red; console.log(myColor4); let myColor4_2: Color4 = Color4.Green; console.log(myColor4_2); let myColor4_3: Color4 = Color4.Blue; console.log(myColor4_3); let myColor4_4: Color4 = Color4.Yellow; console.log(myColor4_4);
|
反向映射
通过枚举的值,反向拿 key,写法类似于数组下标取值,即 Color[x],x 为枚举定义时某个 key ‘=’ 后面的值
限制:仅支持值为数值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| enum Color5 { Red, Green = "Green", Blue = 9, Yellow, Black = "black", }
let myColor5 = Color5[0]; console.log(myColor5); let myColor5_2 = Color5["Green"]; console.log(myColor5_2); let myColor5_3 = Color5[9]; console.log(myColor5_3); let myColor5_4 = Color5[10]; console.log(myColor5_4); let myColor5_5 = Color5["black"]; console.log(myColor5_5);
|
编译结果
枚举经过 TS 的编译后,本质就是对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| var Color5; (function (Color5) { Color5[Color5["Red"] = 0] = "Red"; Color5["Green"] = "Green"; Color5[Color5["Blue"] = 9] = "Blue"; Color5[Color5["Yellow"] = 10] = "Yellow"; })(Color5 || (Color5 = {}));
let myColor5 = Color5[0]; console.log(myColor5); let myColor5_2 = Color5["Green"]; console.log(myColor5_2); let myColor5_3 = Color5[9]; console.log(myColor5_3); let myColor5_4 = Color5[10]; console.log(myColor5_4);
Color5 = { '0': 'Red', '9': 'Blue', '10': 'Yellow', Red: 0, Green: 'Green', Blue: 9, Yellow: 10, Black: 'black' }
Color5[Color5["Red"] = 0] = "Red";
|
面试题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| enum Color6 { Red, Green = "Green", Blue = 9, Yellow, Black = "black", }
let Enum; (function (Enum) { Enum["Red"] = 0; Enum["0"] = "Red";
Enum["Green"] = "Green";
Enum["Blue"] = 9; Enum["9"] = "Blue";
Enum["Yellow"] = 10; Enum["10"] = "Yellow";
Enum["Black"] = "black"; })(Enum || (Enum = {}));
|
Any
表示任意类型,可以赋值给任何变量,该变量后续可直接使用,并且该变量可以赋值给其他类型变量,any 变量以后将跳过类型检测
1 2 3 4 5 6 7 8 9 10
| let anyValue: any = 1;
anyValue = "xxsw";
console.log(anyValue.slice(1));
let numberValue: number = anyValue;
|
使用场景
- JS 迁移到 TS:使用 any 可以快速推进,不用陷入无穷的类型之中,但仅仅是过渡
- 不关心传参类型的函数:比如有个自定义打印函数,里面就一段
console.log('自定义打印:', params)
,这时候任何值传进来都直接打印,所以可以给 params 设为 any,因为不关心它具体类型
Unknown
表示未知类型(是 any 的安全版本),可以赋值给任何变量,该变量后续不可直接使用,需要进行类型检测与类型断言后才能使用,并且该变量不可以赋值给其他非unknown或any
类型变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| let unknownnValue: unknown = 1;
unknownnValue = "xxsw";
if (typeof unknownnValue === "string") { console.log(unknownnValue.slice(1)); }
console.log((unknownnValue as string).slice(1));
let stringValue: string = unknownnValue;
let newAnyValue: any = unknownnValue;
let newUnknownnValue: unknown = unknownnValue;
|
使用场景
任何使用 any 的地方,都应该优先使用 unknown,这样使用时更安全(因为要先类型推断)
Void
表示没有类型
- 用于函数:表明没有返回值或者返回 undefined
- 用于变量:表明只能赋值为 undefined 和 null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function voidFunction(): void { console.log("This is a console.log"); }
function noReturnFunction() { console.log("This is a console.log"); }
function onlyReturnFunction() { console.log("This is a console.log"); return; }
let voidValue1: void = 1; let voidValue2: void; let voidValue3: void = undefined;
|
为什么无返回值的函数,其返回值类型为 void 而不是 undefined?
因为更符合直觉,void 表示函数无返回值。
虽然无返回值的函数默认返回的是 undefined,但 undefined 本身具有一些歧义:代表该值未定义、属性不存在,若将无返回值的函数类型定为 undefined,则将直面歧义。
Never
永远不存在的值的类型,是所有类型的子类型
它可以赋值给任何类型,但其他类型(除了 Never)不能赋值给它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| enum errorLevel { A, Z, }
function throwError(level: errorLevel) { if (level === errorLevel.A) { throw new Error("这个错误是可控抛出的"); } else if (level === errorLevel.Z) { throw new Error("这个错误是不可控抛出的"); } else { let a = level;
a = "b";
const c: number = a; } }
|
使用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| enum errorLevel { A, Z, }
function throwError(level: errorLevel) { if (level === errorLevel.A) { throw new Error("这个错误是可控抛出的"); } else if (level === errorLevel.Z) { throw new Error("这个错误是不可控抛出的"); } else { let a = level; } }
enum errorLevel { A, B, Z, }
function throwError(level: errorLevel) { if (level === errorLevel.A) { throw new Error("这个错误是可控抛出的"); } else if (level === errorLevel.Z) { throw new Error("这个错误是不可控抛出的"); } else { let a: never = level; } }
|
1 2 3 4
| function crashFunc(): never { throw new Error('this function will crash') }
|
Objetc - 对象
TS 里面有三个对象:object | Object | {}
object:JS 中没有;在 TS 中表示非原始类型,本质是一种类型
1
| function fn(o: object): void {}
|
Object:JS/TS 中都表示 Object 构造函数,本身具有很多属性与方法
{}:JS 中表示基于 Object 的实例;在 TS 中特指没有成员的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const emptyObject = {};
emptyObject.name = "1"; emptyObject.getName = () => {};
const emptyObject2 = {} as { name: string; getName: Function };
emptyObject2.name = "1"; emptyObject2.getName = () => {};
const emptyObject2: { name: string; getName: Function } = {};
|
类型断言
当你清楚的知道某个值的类型时,可以进行断言,告知 TS:“相信我,我知道自己在干什么”。
断言只是在编译阶段起作用。
写法 1 - 尖括号
1 2
| let str: any = 'xxx dsad dsad' const len: number = (<string>str).length
|
写法 2 - as 语法
1 2
| let str: any = 'xxx dsad dsad' const len: number = (str as string).length
|
两种写法是等价的,但在 TS 使用 JSX 时,as 才被允许
! - 用于除去 null 和 undefined 的
identifier!
从identifier
的类型里去除了null
和undefined
1 2 3 4 5 6 7
| let A: string | undefined; A!.slice(1);
let B: string = A!;
|
进阶类型
接口 - interface
《接口》官方文档:https://typescript.bootcss.com/interfaces.html
定义:对行为的抽象
基础
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const person: person = { name: 'xx', age: 20, sayHello(){ console.log('你好') } }
interface person { name: string; age: number; sayHello(): void; }
|
进阶
只读 - readonly
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| interface person { readonly name: string; age: number; sayHello(): void; }
const person: person = { name: 'xx', age: 20, sayHello(){ console.log('你好') } }
person.name = "lisi";
|
可选 - ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| interface person { readonly name: string; age?: number; sayHello(): void; }
const person: person = { name: 'xx', sayHello(){ console.log('你好') } }
person.age = 20; person.age = '20';
|
可索引类型 - [key:T]: X
表示当使用 T 类型去索引时会返回 X 类型的值
支持字符串、数字、symbol
三种索引
当字符串、数字
共存时,则要求数字
索引值的类型必须为字符串
索引值的子类型
因为使用数字
索引,JS 底层还是会转为字符串
索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| interface person { readonly name: string; age?: number; sayHello(): void; [key: string]: any; [key: number]: number; [key: symbol]: symbol; }
person.address = "四川省";
person[1] = 1;
person[Symbol()] = Symbol("我是唯一的");
person[1] = "四川省";
person[Symbol()] = 123;
|
当使用了字符串
索引时,其他属性的定义都要跟字符串
索引保持一致
1 2 3 4 5
| interface person { [key: sting]: string; name: string; age: number; }
|
索引可以设置为只读
1 2 3 4 5
| interface person { readonly [key: sting]: string; } const person: person = { name: 'lisi' } person.name = 'zhangsan'
|
继承
接口继承接口
接口可以互相继承
语法:接口名 extends 接口名1,[接口名2, 接口名3, ...]
一个接口可以继承多个接口,创建出多个接口的合成接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| interface Animal { name: string; age: number; }
interface Cat extends Animal { eatFish(): void; }
interface JuCat extends Cat, Animal { isFat(): boolean; }
let XiaoJuCat = {} as JuCat; XiaoJuCat.name = "XiaoJu"; XiaoJuCat.age = 3; XiaoJuCat.eatFish = () => { console.log("[ 每天四顿,每顿 2 条鱼 ] >"); }; XiaoJuCat.isFat = () => { console.log("[ XiaoJu 非常非常的胖 ] >"); return true; };
|
接口继承类
没错,接口是可以继承类
的,但只会继承类的成员,但不会继承具体实现,并且该接口只能被这个类或其子类所实现(implement)。
语法:接口名 extends 类名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Control { private state: any; }
interface SelectableControl extends Control { select(): void; }
class Button extends Control implements SelectableControl { select() {} }
class TextBox extends Control {}
class Image implements SelectableControl { select() {} }
class Location {}
|
在上面的例子里,SelectableControl
包含了Control
的所有成员,包括私有成员state
。 因为state
是私有成员,所以只能够是Control
的子类们才能实现SelectableControl
接口。 因为只有Control
的子类才能够拥有一个声明于Control
的私有成员state
,这对私有成员的兼容性是必需的。
在Control
类内部,是允许通过SelectableControl
的实例来访问私有成员state
的。 实际上,SelectableControl
就像Control
一样,并拥有一个select
方法。Button
和TextBox
类是SelectableControl
的子类(因为它们都继承自Control
并有select
方法),但Image
和Location
类并不是这样的。
定义函数类型
interface 可以将定义类型作用于函数
定义函数类型时,也可以混合其他类型哦
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| interface MyselfDesc { (name: string, age: number): string;
name: string;
sayName(): string }
const myselfDesc: MyselfDesc = (n: string, a: number): string => { return `你好,我是${n},今年${a}岁,很高兴认识你`; }; myselfDesc.name = 'myselfDesc' myselfDesc.sayName = function() { return this.name }
const myselfDesc: MyselfDesc = (n, a) => { return `你好,我是${n},今年${a}岁,很高兴认识你`; };
const myselfDesc2: MyselfDesc = (n, a) => { return 123; };
|
定义类类型
interface 可以将定义类型作用于类
语法:class 类名 implements 接口名
接口描述了类的公共部分,而不是公共和私有两部分。它不会帮你检查类是否具有某些私有成员。
并且接口的类型最终会落到实例上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| interface PersonInterface { name: string; age: number; say(msg: string): void; }
class Person implements PersonInterface { name = ""; age = 0; say(msg: string) { console.log("[ msg ] >", msg); } constructor(name: string, age: number) {} }
const p1 = new Person('lisi', 30)
|
编译结果
经过 TS 编译后,interface
啥都没有,不会在结果代码里面留东西
泛型
《泛型》官方文档:https://typescript.bootcss.com/generics.html
考虑重用性而提出的,适用于多种类型
基础
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
function onlyReturn(arg) { return arg }
function onlyReturn<T>(arg: T): T { return arg }
|
约定成俗:
T、U、K:键值
V:纯值
E:节点
进阶
泛型接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| interface OnlyReturn1 { <T>(arg: T): T }
const onlyReturn: OnlyReturn1 = <T>(arg: T): T => { return arg }
interface OnlyReturn2<T> { (arg: T): T }
const onlyReturn: OnlyReturn2<number> = <T>(arg: T): T => { return arg }
const onlyReturn: OnlyReturn2<string> = <T>(arg: T): T => { return arg }
|
泛型类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class GenericNumber<T> { zeroValue: T; add: (x: T, y: T): => T }
let myGenericNumber = new GenericNumber<number>() myGenericNumber.zeroValue = 0 myGenericNumber.add = (x, y) => x + y
let myGenericNumber = new GenericNumber<string>() myGenericNumber.zeroValue = 'name' myGenericNumber.add = (x, y) => x + y
|
泛型约束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const onlyReturn = <T>(arg: T): T => { console.log(arg.length); return arg }
interface Length { length: number }
const onlyReturn = <T extends Length>(arg: T): T => { console.log(arg.length); return arg }
|
在泛型约束中使用类型参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const getObjValue = <T, K>(obj: T, key: K) { return obj[key] }
const getObjValue = <T, K extends keyof T>(obj: T, key: K) { return obj[key] }
let x = { a: 1, b: 2, c: 3, d: 4 };
getObjValue(x, "a"); getObjValue(x, "m");
|
高级类型
《高级类型》官方文档:https://typescript.bootcss.com/advanced-types.html
交叉类型
定义:将多个类型合并为一个类型,该类型必须满足所有多类型
白话:多个类型取且
语法:类型1 & 类型2 & ...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| interface P { name: string; } interface E { age: number; }
const OO: P & E = { name: "liusan", age: 20, };
interface P { name: string; } interface E { name: number; } type PE = P & E
|
联合类型
定义:将多个类型合并为一个类型,该类型只需满足多类型之一
白话:多个类型取或
语法:类型1 | 类型2 | ...
1 2 3 4 5 6 7 8 9 10 11
| interface P { name: string; } interface E { age: number; }
const OO: P | E = { name: "liusan", };
|
类型保护
当使用联合类型
时,只能确定该类型包含多类型的交集成员,如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| interface Bird { fly(): void; layEggs(): void; }
interface Fish { swim(): void; layEggs(): void; }
function getSmallPet(): Fish | Bird { const result = {} as Fish | Bird;
return result; }
let pet = getSmallPet(); pet.layEggs();
pet.swim();
pet.fly();
if (pet.swim) { pet.swim(); } else if (pet.fly) { pet.fly(); }
if ((<Fish>pet).swim) { (<Fish>pet).swim(); } else { (<Bird>pet).fly(); }
|
类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。
要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个类型谓词
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const isFish = (pet: Fish | Bird): pet is Fish => { return (<Fish>pet).swim !== undefined; }
if (isFish(pet)) { pet.swim(); } else { pet.fly(); }
|
编译结果
TS 编译后,会留下对应的代码
1 2 3 4 5 6 7 8 9 10 11 12 13
| function isFish(pet) { return pet.swim !== undefined; }
if (isFish(pet)) { pet.swim(); } else { pet.fly(); }
|
类型别名 - type
作用:给一个类型起个新名字
类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| type Name = string; type GetName = () => string; type NameOrGetName = Name | GetName;
const getName = (n: NameOrGetName): Name => { if (typeof n === "string") { return n; } else { return n(); } };
type Tree<T> = { value: T; left: Tree<T>; right: Tree<T>; };
|
映射类型
一种从旧类型中创建新类型的一种方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| interface Person { name: string; age: number }
type Readonly<T> = { readonly [K in keyof T]: T[K]; }
type Partial<T> = { [K in keyof T]?: T[K]; }
type PersonReadonly = Readonly<Person>
type PersonPartial = Readonly<Person>
|
复杂的映射类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| type Proxy<T> = { get(): T; set(value: T): void; };
type Proxify<T> = { [P in keyof T]: Proxy<T[P]>; };
function proxify<T>(o: T): Proxify<T> { const result = {} as Proxify<T>;
for (const key in o) { if (Object.prototype.hasOwnProperty.call(o, key)) { result[key] = { get() { return o[key]; }, set(value) { o[key] = value; }, }; } }
return result; } let proxyProps = proxify({ name: "123", age: 1 });
|
编译结果
经过 TS 编译后,interface
啥都没有,不会在结果代码里面留东西
装饰器
《装饰器》官方文档:https://typescript.bootcss.com/decorators.html
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上。
基础语法:@expression,expression 求值后必须为函数,将在运行时被调用
使用的前提是:tsconfig.json 开启配置
1 2 3 4 5
| { "compilerOptions" : { "experimentalDecorators": true } }
|
装饰器工厂
定制装饰器用的,本质是一个返回函数的函数
1 2 3 4 5 6
| function Get(path: string) { return function(target, propertyKey: string, decriptor: PropertyDescriptor){ } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
function Get(path: string) { console.log('Get()') return function(target, propertyKey: string, decriptor: PropertyDescriptor){ console.log('Get() do something') } }
function Post(path: string) { console.log('Post()') return function(target, propertyKey: string, decriptor: PropertyDescriptor){ console.log('Post() do something') } }
class UserService { userName = "lisi";
@Get('/userinfo') @Post('/userinfo') userinfo() { } }
|
编译结果
上述代码经过 TS 编译后,将变成以下 JS 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; };
function Get(path) { console.log("Get()"); return function (target, propertyKey, decriptor) { console.log("Get() do something"); }; }
function Post(path) { console.log("Post()"); return function (target, propertyKey, decriptor) { console.log("Post() do something"); }; } class UserService { constructor() { this.userName = "lisi"; } userinfo() { } } __decorate( [Get("/userinfo"), Post("/userinfo")], UserService.prototype, "userinfo", null );
|
Reflect.decorate:ECMAScript 2016 标准引入的一个方法,主要用于处理装饰器
装饰器分类
类装饰器
作用于类上的装饰器,可以用来监视,修改或替换类定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
function Sealed(constructor: Function) { Object.seal(constructor) Object.seal(constructor.prototype) }
@Sealed class UserService2 { userName = "lisi";
userinfo() { } }
|
编译结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; };
function Sealed(constructor) { Object.seal(constructor); Object.seal(constructor.prototype); } let UserService2 = class UserService2 { constructor() { this.userName = "lisi"; } userinfo() { } }; UserService2 = __decorate([Sealed], UserService2);
|
实际场景
- 重载构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| function classDecorator<T extends { new (...args: any[]): {} }>( constructor: T ) { return class extends constructor { newProperty = "new property"; hello = "override"; }; }
@classDecorator class Greeter { property = "property";
hello: string;
constructor(m: string) { this.hello = m; } }
console.log(new Greeter("world"));
var __decorate = (this && this.__decorate) || ...
function classDecorator(constructor) { return class extends constructor { constructor() { super(...arguments); this.newProperty = "new property"; this.hello = "override"; } }; }
let Greeter = class Greeter { constructor(m) { this.property = "property"; this.hello = m; } };
Greeter = __decorate([classDecorator], Greeter); console.log(new Greeter("world"));
|
- 针对老业务进行扩展时,很有用
假如有个场景:之前有个 class,有点复杂,也满足之前的需求。但现在需要加几个属性与方法来支持新业务,则这种情况该怎么处理?
- 侵入式:直接改这个 class,强行添加新的属性与方法
- 继承式:通过 extends 继承这个类,然后再添加新的属性与方法
以上两个方法都能解决问题,但都存在对原有类的冲击,容易产生不可预测的影响
- 可以通过
类装饰器
进行可插拔的更改,并且不会对原来的类产生影响
代码举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Course { constructor() {} }
function Version_2_1(constructor: Function) { constructor.prototype.startClass = function () { console.log("[ let‘s startClass ] >", this.teacher); }; }
@Version_2_1 class Course { teacher = "lisi"; constructor() {} }
const course = new Course(); course.startClass();
|
方法装饰器
作用于方法的装饰器,可以用来监视,修改或替换方法定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
function Post(path: string) { console.log("Post()");
return function ( target: any, propertyKey: string, decriptor: PropertyDescriptor ) { console.log("Post() do something");
decriptor.enumerable = true;
console.log("[ target ] >", JSON.stringify(target)); console.log("[ propertyKey ] >", propertyKey); console.log("[ decriptor ] >", decriptor); }; }
class UserService { userName = "lisi";
@Post("/userinfo") userinfo() { } }
|
访问器装饰器
作用于访问器(geter/seter)的装饰器,可以用来监视,修改或替换访问器定义。
不允许同时装饰一个成员的 get 和 set 访问器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
function configurable(value: boolean) { return function ( target: any, propertyKey: string, descriptor: PropertyDescriptor ) { descriptor.configurable = value; }; }
class Point { _x: number; _y: number;
constructor(x: number, y: number) { this._x = x; this._y = y; }
@configurable(false) get x(): number { return this._x + 1; }
@configurable(false) get y(): number { return this._y + 1; } }
const point = new Point(0, 0); console.log("[ point ] >", point); console.log("[ point.x ] >", point.x); console.log("[ point.y ] >", point.y);
|
编译结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| var __decorate = (this && this.__decorate) || ...
function configurable(value) { return function (target, propertyKey, descriptor) { descriptor.configurable = value; }; } class Point { constructor(x, y) { this._x = x; this._y = y; } get x() { return this._x + 1; } get y() { return this._y + 1; } } __decorate([ configurable(false) ], Point.prototype, "x", null); __decorate([ configurable(false) ], Point.prototype, "y", null); const point = new Point(0, 0); console.log("[ point ] >", point); console.log("[ point.x ] >", point.x); console.log("[ point.y ] >", point.y);
|
属性装饰器
作用于属性的装饰器,可以用来监视,修改或替换属性定义。
一般用于处理这个属性的元数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import "reflect-metadata";
const formatMetadataKey = Symbol("format");
function format(formatString: string) { return function (target: any, porpertyKey: string) { return Reflect.metadata(formatMetadataKey, formatString); }; }
function getFormat(target: any, porpertyKey: string) { return Reflect.getMetadata(formatMetadataKey, target, porpertyKey); }
class Greeter2 { @format("Hello, %s") greeting: string;
constructor(message: string) { this.greeting = message; } greet() { let formatString = getFormat(this, "greeting"); return formatString.replace("%s", this.greeting); } }
|
实际场景
属性拦截
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function greetingWraper(target: any, porpertyKey: string) { Object.defineProperty(target, porpertyKey, { }); }
class Greeter2 { @greetingWraper greeting: string = "111";
constructor(message: string) { this.greeting = message; } }
|
参数装饰器
作用于参数的装饰器,可以用来监视参数是否传入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| 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: PropertyDescriptor ) { 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 GreeterX { greeting: string;
constructor(message: string) { this.greeting = message; }
@validate greet(@required name: string) { return "Hello " + name + ", " + this.greeting; } }
|
元数据
官方文档:元编程 - JavaScript | MDN
总结
了解完装饰器后,如果是经常写 nodejs 的人就会发现已经不知不觉用了装饰器
1 2 3 4 5 6 7
| @ApiOperation({ summary: '用户登录' }) @ApiCreatedResponse({ type: UserEntity }) @Post('login') async login(@Body() loginDto: LoginDto) { }
|
其中的@ApiOperation、@ApiCreatedResponse、@Post、@Body
等,就是很多 nodejs 框架所提供的装饰器
所以装饰器的实际使用场景在nodejs
比较广泛,客户端领域可能用的比较少。
TS 原理解析
1 2 3 4 5 6 7 8 9
| let a: number = 1
|
扩展知识
Object.seal()
官方文档:Object.seal() - JavaScript | MDN
作用:密封一个对象。
密封后:该对象将【不能添加新属性】、【不能删除现有属性或更改其可枚举性和可配置性】、【不能重新分配其原型】、【现有属性的值是可以更改的】(这是与 freeze 的区别)
1 2 3 4 5 6 7 8
| const obj = { name: 'zhangsan' } const sealObj = Object.seal(obj) sealObj === obj
sealObj.age = 12 delete sealObj.name sealObj.name = 'lisi' obj.__proto__ = {}
|
Object.freeze()
官方文档:Object.freeze() - JavaScript | MDN
作用:冻结一个对象
冻结后:该对象将【不能添加新的属性】、【不能移除现有的属性】、【不能更改它们的可枚举性、可配置性、可写性或值】、【对象的原型也不能被重新指定】
1 2 3 4 5 6 7 8
| const obj = { name: 'zhangsan' } const sealObj = Object.freeze(obj) sealObj === obj
sealObj.age = 12 delete sealObj.name sealObj.name = 'lisi' obj.__proto__ = {}
|
面试题
interface VS type
|
interface |
type |
extends |
可以 |
不可以 |
implements |
可以 |
不可以 |
联合|交叉 |
不可以 |
可以 |
使用情况:如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。