📖TypeScript 速通教程 @尚硅谷
TS - 概述
- TypeScript 是 JavaScript 的超集。
- 它对 JS 进行了扩展,向 JS 中引入了类型的概念,并添加了许多新的特性。
- TS 代码需要通过编译器编译为 JS,然后再交由 JS 解析器执行。
- TS 完全兼容 JS,换言之,任何的 JS 代码都可以直接当成 TS 使用。
静态类型检查
- 静态类型检查,即在代码运行前进行检查,发现代码的错误或不合理之处,减少运行时异常的出现几率。
- TypeScript 的核心就是静态类型检查,即将运行时的错误前置。
TS - 编译
浏览器无法直接运行 .ts 文件,因此需要将 .ts 文件编译为 .js 文件。
编译 - 命令行
Step1. npm install -g typescript
Step2. tsc <.js 文件路径>
tsc是全局安装 typescript 后暴露的命令,是 typescript compiler 的简写,用于将 .ts 文件编译为 .js 文件
编译 - 自动化
Step1. npm install -g typescript
Step2. tsc --init
创建了
tsconfig.json配置文件,规定了将 .ts 文件编译为 .js 文件的方式。可以在配置文件中设置"noEmitOnError": true,使得编译出错时不生成 .js 文件。
Step3. tsc --watch 
监视当前目录下的所有 .ts 文件的变化,并根据
tsconfig.json将其自动编译为 .js 文件。--watch后也可以加具体的文件路径,表示只监视特定的文件。
TS - 类型声明
- 
解释:类型声明给变量设置了类型,使得变量只能存储某种类型的值。 
- 
语法 1 
 2
 3
 4
 5
 6
 7let 变量: 类型; 
 let 变量: 类型 = 值;
 function fn(参数: 类型, 参数: 类型): 类型{
 ...
 }
TS - 类型推断
解释:当对变量的声明和赋值是同时进行的,TS 编译器会自动判断变量的类型
JS|TS - 类型总览
- JavaScript 中的数据类型:①string ②number ③boolean ④null ⑤undefined ⑥bigint ⑦symbol ⑧object
- TypeScript 中的数据类型
- JS 所有类型
- TS 新增类型:①any ②unknown ③never ④void ⑤tuple ⑥enum ⑦字面量
- TS 用于自定义类型的关键字:①type ②interface
 
- 注意事项:String 和 string 都是 TypeScript 中所接受的类型,不过前者是包装对象类型,一般不建议使用;后者是原始类型,推荐使用。
- 原始类型:内存占用空间小,处理速度快。
- 包装对象:内存占用空间多,但可以调用方法或访问属性。
 
TS - 常用类型
any
- 解释:任意类型,即一旦将变量类型限制为 any,那么就意味着放弃对该变量的类型检查。1 
 2
 3
 4
 5
 6
 7
 8// 显示 any 
 let a: any
 // 隐式 any
 let b
 // 可以给 any 类型的变量赋任何值,不会警告
 b = 100
 b = 'hello'
 b = false
- 注意:any类型的变量,可以赋值给任意类型的变量,从而造成污染。1 
 2
 3
 4
 5let a: any 
 a = 100
 let b: string
 b = a // 无警告
unknown
- 解释:未知类型,可以理解为类型安全的 any。该类型会强制开发者在使用之前对变量进行类型检查(if 判断或类型断言),否则会进行警告。1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16let a: unknown 
 a = 100
 a = 'hello'
 a = false
 let x: string
 x = a // 警告:不能将类型 unknown 分配给类型 string
 // 方式一:if 判断,让 ts 知道 unknown 变量的具体类型
 if(typeof a === 'string'){
 x = a
 }
 // 方式二:类型断言,用于指定 unknown 变量的具体类型
 x = a as string // 类型断言写法 1
 x = <string>a // 类型断言写法 2
- 注意:读取 any类型变量的任何属性都不会报错,而unknown正相反。
never
解释:不是任何类型,即 never 类型的变量不能有值。
- 不用 never去限制变量,无意义。
- TypeScript 会根据代码执行情况,推断出某些永远无法执行的变量类型为 never。1 
 2
 3
 4
 5
 6
 7
 8let a: string 
 a = 'hello'
 if (typeof a === 'string'){
 console.log(a.toUpperCase())
 } else {
 console.log(a) // 这里的 a 会被推断为 never 类型,因为没有值符合这里的逻辑
 }
- never也可以用于限制函数的返回值类型,当 ①程序无法正常执行(报错)②程序永远无法停止(死循环)时,其返回值类型就可以是- never。- 1 
 2
 3- function throwError(message: string): never { 
 throw new Error(message)
 }
void
- 解释:空类型,常用于函数返回值的类型声明,表示 ①函数不返回任何值,②调用者也不应该依赖其返回值进行任何操作。其中,undefined是void唯一接受的一种返回值。1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19// 隐式返回 undefined - 1(无警告) 
 function print(msg: string): void{
 console.log(msg)
 }
 // 隐式返回 undefined - 2(无警告)
 function print(msg: string): void{
 console.log(msg)
 return
 }
 // 显示返回 undefined - 3(无警告)
 function print(msg: string): void{
 console.log(msg)
 return undefined
 }
 let res = print("hello world")
 if(res) console.log("可以获取函数的 void 返回值吗?") // 警告
- 注意:如果函数的返回值被声明为 void,那么- 语法上,函数可以返回 undefined,显式还是隐式返回无所谓。
- 语义上,函数调用者不应该关心函数返回的值,也不应依赖返回值进行任何操作。
 
- 语法上,函数可以返回 
object
- object:所有非原始类型,包括对象、函数、数组等。
- Object:所有可以调用 Object 方法的类型(除了 undefined 和 null 的任何值)。
- 注意:object 和 Object 在实际开发中使用频率极低。
声明对象类型
| 1 | // 声明(一般)对象类型的必选属性 | 
声明函数类型
| 1 | // 声明函数类型,包括参数及返回值 | 
声明数组类型
| 1 | // 声明数组类型 - 方式 1(类型[]) | 
tuple
解释:特殊的数组类型,可以存储固定数量的元素,并且每个元素的类型是已知的,且可以不同。元组用于精确描述一组值的类型,? 表示可选元素。
| 1 | // 两个元素,第一个必须是 string 类型,第二个必须是 number 类型 | 
enum
- 
解释: 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
 27enum Sex { 
 Male,
 Female
 }
 /*
 Sex 被编译为,
 var Sex;
 (function (Sex) {
 Sex[Sex["Male"] = 0] = "Male";
 Sex[Sex["Female"] = 1] = "Female";
 })(Sex || (Sex = {}));
 */
 function recruitPolicy(sex: Sex): void {
 if (sex === Sex.Male) {
 console.log('height >= 185cm and weight <= 80kg');
 } else if (sex === Sex.Female) {
 console.log('height >= 165cm and weight <= 70kg');
 } else {
 console.log('invalid params');
 return;
 }
 console.log('eyesight >= 3.8');
 console.log('salary = 3000');
 }
 recruitPolicy(Sex.Female);
- 
字符串枚举:枚举成员的值是字符串,不存在反向映射。 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13enum Sex { 
 Male = 'male',
 Female = 'female'
 }
 /*
 Sex 被编译为,
 var Sex;
 (function (Sex) {
 Sex["Male"] = "male";
 Sex["Female"] = "female";
 })(Sex || (Sex = {}));
 */
- 
常量枚举:特殊的枚举类型,使用 const enum关键字定义,在编译时会被内联,避免生成额外的代码。编译时内联:即 TypeScript 在编译时,会将枚举成员引用替换为它们的实际值,这样就不会生成额外的枚举对象,从而减少代码量,并提高运行时性能。 1 
 2
 3
 4
 5
 6const enum Sex { 
 Male = 'male',
 Female = 'female'
 }
 console.log(Sex.Male)被编译为, 1 
 2; 
 console.log("male" /* Sex.Male */);
type
- 解释:type关键字用于为任意类型创建别名。
- 基本使用:type 别名 = 类型1 
 2
 3
 4
 5// digit 类型是 number 类型的别名 
 type digit = number
 let price: digit
 price = 100
- 联合类型:type 别名 = 类型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
 23type Status = number | string 
 type State = "Success" | "Error" | "Unkown"
 function printHttpStatus(status: Status) {
 let state: State;
 if (status.toString().startsWith('2')) {
 console.info(status);
 state = 'Success';
 }
 else if (status.toString().startsWith('4')) {
 console.error(status);
 state = "Error";
 }
 else {
 console.log("unknown status");
 state = "Unkown"
 }
 return state;
 }
 const state404 = printHttpStatus("404");
 const state200 = printHttpStatus("200");
 console.log(`404 is ${state404}, 200 is ${state200}`)
- 交叉类型:type 别名 = 类型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
 26type Address = { 
 country: string;
 province: string;
 city: string;
 district: string;
 code: string
 }
 type BasicInfo = {
 name: string;
 age: number;
 school: string;
 }
 type Person = Address & BasicInfo;
 const lee: Person = {
 name: "lee",
 age: 22,
 school: "NU",
 country: "CN",
 province: "SX",
 city: "XX",
 district: "JJ",
 code: "000000"
 }
type + void 存在的问题
- 问题描述:当使用类型声明限制函数返回值为 void 时,TypeScript 不会严格要求函数返回空(即允许函数返回 undefined 之外的值)。 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12type NoReturnFunc = (...args: String[]) => void; 
 const printf: NoReturnFunc = (msg) => {
 console.log(msg);
 return 0;
 }
 const funcCode = printf("Hello World");
 console.log(funcCode); // 0
 if (funcCode == 0) // 警告:This comparison appears to be unintentional because the types 'void' and 'number' have no overlap.
 console.log("success!")
- 原因解释:TS 这样设计是为了确保像如下的代码成立,避免类型限制影响箭头函数的简写语法。 1 
 2
 3
 4
 5
 6
 7
 8const src = [1, 2, 3]; 
 const dst = [0];
 src.forEach(el => dst.push(el));
 // forEach 的回调函数返回值类型是 void,表示不关心返回值。
 // 这里使用的箭头函数虽然返回了 dst.push(el) 的结果(即 dst 的最新长度),但 TypeScript 将其处理为 void 类型。
 // 这是为了避免因为不必要的返回值引发错误。
 // 同时,void 类型的函数返回值不能被用于比较或赋值,否则会报错。
TS - 类
定义 | 重写
TS 中的类与 JS 相比,有以下注意事项,
- 属性必须预先声明
- 重写父类方法时推荐使用 override关键字,可以增加语法提示,避免出错
| 1 | class Person { | 
属性修饰符

| 1 | class Person { | 
属性的简写
语法:constructor(修饰符 属性名: 类型, 修饰符 属性名: 类型, ...)
属性简写必须包含修饰符,但是当属性是继承于父类/抽象类时,不能包含修饰符。
| 1 | class Person1 { | 
TS - 抽象类
- 解释:抽象类是一种无法被实例化的类,专门用于定义类的结构和行为,类中可以写抽象方法,也可以写具体实现。
- 功能:抽象类主要用于为其派生类提供一个基础结构,要求其派生类必须实现其中的抽象方法。
- 语法:使用 abstract关键字修饰类,则为抽象类;修饰方法,则为抽象方法。
- 适用场景:①定义通用接口 ②提供基础实现 ③确保关键实现 ④共享代码和逻辑
| 1 | // 包裹 - 抽象类 | 
TS - 接口
- 解释:接口(interface)是一种定义结构的方式,用于为类、对象、函数等规定一种契约,从而确保代码的一致性和类型安全。但是注意:interface 只能定义结构,不能包含任何实现。
- 命名规范:如 IPerson或PersonInterface。
- 适用场景:①定义对象的格式 ②类的契约 ③自动合并
定义 | 使用
类结构
类使用 implements 关键字实现已有的接口,规范类的实现。
| 1 | interface IPerson { | 
对象结构
接口可以看作一个类型,可以使用 :接口名 的方式,限制对象的类型。
| 1 | interface IUser { | 
函数结构
接口可以看作一个类型,也可以使用 :接口名 的方式,限制函数的类型。
| 1 | interface IAdd { | 
继承
接口可以通过 extends 关键字实现继承。
| 1 | interface IPerson { | 
自动合并
同名接口的重复定义并不会导致接口覆盖,而是自动合并。
| 1 | interface IPerson { | 
与 type 和抽象类的区别

TS - 泛型
- 
解释:泛型允许在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时才被指定为具体的类型。泛型可以让同一段代码适用于多种类型,同时仍保持类型的安全性。 
- 
使用 - 
泛型函数 1 
 2
 3
 4
 5
 6
 7function log<T>(msg: T): T { 
 console.log(msg);
 return msg;
 }
 log<number>(100);
 log<string>('Hello World');
- 
多个泛型 1 
 2
 3
 4
 5
 6
 7function log<T, U>(msg1: T, msg2: U): T | U { 
 console.log(msg1, msg2)
 return Date.now() % 2 ? msg1 : msg2
 }
 log<number, string>(100, 'big number');
 log<string, boolean>('happy now', false);
- 
泛型接口 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11interface IPerson<T> { 
 name: string
 age: number
 extraInfo: T
 }
 let p: IPerson<string> = {
 name: "Jack",
 age: 19,
 extraInfo: "Male"
 }
- 
泛型约束 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10interface IPerson { 
 name: string
 age: number
 }
 function logPerson<T extends IPerson>(info: T): void {
 console.log(`name=${info.name}, age=${info.age}`);
 }
 logPerson({ name: "Jack", age: 19 })
- 
泛型类 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14class Person<T> { 
 constructor(
 public name: string,
 public age: number,
 public extraInfo: T
 ) { }
 }
 interface IJobInfo {
 title: string,
 company: string
 }
 const p = new Person<IJobInfo>("Jack", 19, { title: "web", company: "ccd" })
 
- 
TS - 类型声明文件
- 
解释:类型声明文件是 TypeScript 中的一种特殊文件,通常以 .d.ts作为扩展名。其主要用于为现有的 JavaScript 代码提供类型信息,使得 TypeScript 能够在使用 JavaScript 库或模块时进行类型检查和提示。
- 
语法:使用 declare关键字,为现有代码提供类型信息。1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21// demo.js 
 export function add(a, b) {
 return a + b;
 }
 export function mul(a, b) {
 return a * b;
 }
 // demo.d.ts,为现有的同名 .js 文件中的代码添加类型信息
 declare function add(a: number, b: number): number;
 declare function mul(a: number, b: number): number;
 export {add, mul};
 // index.ts
 import { add, mul } from "./demo.js"
 const x = add(2, 4); // 会有类型提示
 const y = mul(4,5); // 会有类型提示
 console.log(x, y);
TS - 装饰器
概述
- 
装饰器本质是一种特殊的函数,它可以对类、属性、方法、参数进行扩展,同时能让代码更简洁。 
- 
装饰器自 2015 年在 ECMAScript-6 中被提出到现在,已将近 10 年。 
- 
截止目前,装饰器依然是实验性特性,需要开发者手动调整配置来开启装饰器支持。 
- 
装饰器有五种:类装饰器、属性装饰器、方法装饰器、访问器装饰器、参数装饰器。 
- 
虽然 TypeScript 5.0 中可以直接使用类装饰,但为了确保其他装饰器可用,现阶段使用时,仍建议使用 experimentalDecorators配置来开启装饰器支持。  
类装饰器
- 
解释:类装饰器是一个应用在类声明上的函数,可以为类添加额外的功能或逻辑。 
- 
语法:类装饰器的定义及使用 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15/* 类装饰器 - 定义 :一个可接受目标类为参数的函数 */ 
 /* 注意-1:target 参数就是要被装饰的类 */
 /* 注意-2:类装饰器可以返回一个新的类,替换掉被装饰的类;否则,被装饰的类不会被替换 */
 function ClassDecoratorDemo(target: Function) {
 console.log(`类装饰器被调用,收到的参数为 ${target}`);
 }
 /* 类装饰器 - 使用:通过 @装饰器名 的方式将类装饰器应用于类 */
 /* 注意:类装饰器的调用时机为类定义阶段,类实例化之前 */
 // @ClassDecoratorDemo <==> ClassDecoratorDemo(Person)
 class Person {
 constructor(public name: string, public age: number) {
 console.log(`类构造函数被调用`);
 }
 }
- 
构造类型声明:在 TypeScript 中, Function类型可以表示诸如普通函数、箭头函数、方法等等,但是Function类型的函数并非都可以使用new关键字实例化(如箭头函数),因此我们需要定义一个新的类型用于限制类装饰器中的target参数类型,该类型称之为构造类型,共有以下两种方式声明构造类型。- 
声明构造类型 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12/* 仅声明构造类型 */ 
 /*
 - new 表示 该类型可以使用 new 关键字进行实例化,即该类型是构造类型
 - ...args 表示 该类型可以接受任意数量的参数
 - any[] 表示 该类型可以接受任意类型的参数
 - {} 表示 该类型的返回类型是对象(非 null、非 undefined)
 */
 type Constructor = new (...args: any[]) => {};
 function ClassDecoratorDemo(target: Constructor) {}
 class Person {}
- 
声明构造类型 + 指定静态属性 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11/* 声明构造类型 + 指定静态属性 */ 
 type Constructor = {
 new (...args: any[]): {}; // 构造类型签名
 layer: string; // 静态属性 layer(即指定的构造类型必须要有 layer 静态属性)
 };
 function ClassDecoratorDemo(target: Constructor) {}
 class Person {
 static layer = "elite";
 }
 
- 
- 
应用举例-1:定义一个装饰器,实现 Person实例调用toString时返回JSON.stringify的执行结果。1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21/* 自定义构造类型 */ 
 type Constructor = new (...args: any[]) => {};
 /* 自定义类装饰器,用于新增属性和方法 */
 function ToStringDecorator(target: Constructor) {
 // 修改 Person 的 toString 方法
 target.prototype.toString = function () {
 return JSON.stringify(this); // this 指向调用 toString 方法的 Person 实例
 };
 // “密封” Person 的原型对象,此时 ①阻止添加新属性 ②阻止删除现有属性 ③保持现有属性的可配置性
 Object.seal(target.prototype);
 }
 /* 使用类装饰器 */
 class Person {
 constructor(public name: string, public age: number) {}
 }
 const tom = new Person("Tom", 18);
 console.log(tom.toString());
- 
应用举例-2:设计一个 LogTime装饰器,可以给实例添加一个属性,用于记录实例对象的创建时间,再添加一个方法,用于读取创建时间。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/* 自定义构造类型 */ 
 type Constructor = new (...args: any[]) => {};
 /* User 类型约束 */
 interface User {
 createdAt: Date;
 getCreatedAt: () => string; // 等价于 getCreatedAt(): string
 }
 /* 自定义类装饰器,用于新增属性和方法 */
 function LogTime<T extends Constructor>(target: T) {
 return class extends target {
 public createdAt: Date; // 实例创建时间
 constructor(...args: any[]) {
 super(...args); // 调用父类的构造函数
 this.createdAt = new Date();
 }
 getCreatedAt() {
 return `该对象创建于 ${this.createdAt}`;
 }
 };
 }
 /* 使用类装饰器 */
 class User {
 constructor(public name: string, public age: number) {}
 }
 const tom = new User("Tom", 18);
 console.log(JSON.stringify(tom));
 console.log(tom.getCreatedAt());
 console.log(tom.createdAt);
装饰器工厂
- 
解释:装饰器工厂是一个返回装饰器的函数,可以为装饰器添加参数,从而更灵活地控制装饰器的行为。 
- 
应用举例:定义一个 LogInfo类装饰器工厂,实现Person实例可以调用introduce方法,且introduce调用内容输出的次数由LogInfo接收的参数决定。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/* 构造类型 */ 
 type Constructor = new (...args: any[]) => {};
 /* Person 类型约束 */
 interface Person {
 introduce: () => void; // 等价于 introduce(): void
 }
 /* 类装饰器工厂 */
 function DecoratorFactory(repetition: number) {
 return function (target: Constructor) {
 // 向实例的原型上添加方法 introduce
 target.prototype.introduce = function () {
 for (let i = 0; i < repetition; i++) {
 console.log(`My name is ${this.name}, and I'm ${this.age} years old.`);
 }
 };
 };
 }
 /* 使用类装饰器 */
 (5)
 class Person {
 constructor(public name: string, public age: number) {}
 }
 const tom = new Person("tom", 20);
 tom.introduce();
装饰器组合
解释:一个类可以组合使用多个装饰器或装饰器工厂,其执行顺序为 ① 由上到下执行所有的装饰器工厂,依次获取到对应的装饰器 ② 由下到上执行所有的装饰器。
| 1 | /* 自定义构造类型 */ | 
上述代码执行输出结果为
| 1 | test2工厂 | 
属性装饰器
- 
语法 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19/** 
 * 属性装饰器
 * @param target 对于静态属性,该参数为类;对于实例属性,该参数为类的原型对象
 * @param propertyKey 属性名
 */
 function FieldDecoratorDemo(target: object, propertyKey: string) {
 console.log(target, propertyKey);
 }
 class Person {
 /* 属性装饰器的使用 */
 public name: string; // 等价于 FieldDecoratorDemo(prototype, name)
 public age: number; // 等价于 FieldDecoratorDemo(prototype, age)
 public static layer = "elite"; // 等价于 FieldDecoratorDemo(Person, layer)
 constructor(name: string, age: number) {
 this.name = name;
 this.age = age;
 }
 }
- 
属性遮蔽:以下代码,如果片段 (2)在片段(3)上边,那么实例化Person时,this.name = name创建实例属性name,而this.age = age则是调用原型对象上的setter,用于修改全局变量__age,即属性遮蔽。1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24// (1) 
 class Person {
 public name: string;
 public age: number;
 constructor(name: string, age: number) {
 this.name = name;
 this.age = age;
 }
 }
 // (2)
 let __age = 130;
 Object.defineProperty(Person.prototype, "age", {
 get() {
 return __age;
 },
 set(newAge) {
 __age = newAge;
 },
 });
 // (3)
 const p = new Person("Tom", 19);
 console.log(p.age);
- 
应用举例:定义一个 State属性装饰器,用于监视属性的修改。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
 63
 64
 65
 66
 67
 68
 69
 70/** 
 * 用于监听实例属性修改的属性装饰器
 * @param target 原型对象(类的 prototype)
 * @param propertyKey 被装饰的属性名
 */
 function State(target: object, propertyKey: string) {
 // 定义一个私有属性名,用于存储实际的值
 const key = `__${propertyKey}`;
 // 在原型对象上定义属性的 getter 和 setter
 Object.defineProperty(target, propertyKey, {
 get() {
 // getter 用于获取存储在实例上的私有属性值
 return this[key]; // `this` 指向实例对象
 },
 set(v) {
 // setter 用于监控属性值的修改并更新存储的值
 console.log(`实例属性 ${propertyKey} 被修改了: ${this[key]} --> ${v}`);
 this[key] = v; // 更新存储的私有属性值
 },
 enumerable: true, // 属性可枚举
 configurable: true, // 属性描述符可配置
 });
 }
 class Person {
 public name: string; // 定义实例属性 name
 age: number; // 为 age 属性添加装饰器,使其成为响应式属性
 constructor(name: string, age: number) {
 this.name = name; // 初始化实例属性 name
 this.age = age; // 通过 setter 初始化 age 属性,本质上是设置私有属性 __age 的值
 }
 }
 // 创建实例并验证功能
 const p1 = new Person("Tom", 10); // 输出: 实例属性 age 被修改了: undefined --> 10
 const p2 = new Person("Jack", 19); // 输出: 实例属性 age 被修改了: undefined --> 19
 console.log(p1, p2); // 输出: Person { name: 'Tom', __age: 10 } Person { name: 'Jack', __age: 19 }
 /* ------------------------------------------------------------------------------- */
 /* 上述代码可以完全等价为下述代码 */
 class Animal {
 public name: string;
 public age: number;
 /* 响应式实现-1,等同上述的属性装饰器 */
 private __age: number = 0;
 constructor(name: string, age: number) {
 /* 响应式实现-2,监视 age 属性的修改,其中 age 属性存放在类的原型对象上,被代理的数据存储在实例对象上 */
 Object.defineProperty(Animal.prototype, "age", {
 get() {
 return this.__age;
 },
 set(v) {
 console.log(`实例属性 age 被修改了:${this.__age} --> ${v}`);
 this.__age = v;
 },
 enumerable: true,
 configurable: true,
 });
 this.name = name;
 this.age = age;
 }
 }
 const a1 = new Animal("Happy", 20);
 const a2 = new Animal("Lucky", 12);
 console.log(a1, a2);补充 Object.defineProperty 的使用- 
功能: Object.defineProperty()在给定的对象上定义一个新属性或修改一个现有属性,同时返回修改后的对象。
- 
语法: Object.defineProperty(obj, prop, descriptor)- obj:给定的对象
- prop:要定义或修改的属性名,可以是 string 或- Symbol
- descriptor:属性描述符,用于控制要定义或修改的属性的行为
 
- 
返回值:定义或修改了指定属性后的给定对象。 
- 
使用说明 - 属性描述符(property descriptor)可以分为数据描述符(data descriptor)和访问器描述符(accessor descriptor),对应的属性可以称之为数据属性和访问器属性。
- 属性描述符是一个对象,其中数据描述符所特有的配置项为 value和writable,访问器描述符所特有的配置项为get和set,二者所共有的配置项为enumerable和configurable。
- 一个属性描述符只能是数据描述符或访问器描述符二者其一。
- 如果一个属性描述符不包含 value、writable、get、set属性,则该属性描述符被看作数据描述符。
- 如果一个属性描述符同时包含 【value或writable】和【get或set】,则会报错。
 
- 属性描述符是一个对象,其中数据描述符所特有的配置项为 
- 默认情况下,Object.defineProperty()定义的属性是不可写(not writable)、不可枚举(not enumerable)、不可配置(not configurable)的。而通过object.propertyName=propertyValue这样的方式定义的属性是可写、可枚举、可配置的。
- Object.defineProperty()本质上是通过 JavaScript 的内在方法(internal method)- [[DefineOwnProperty]]实现的,与另一个 JavaScript 的内在方法- [[Set]]无关,因此,通过该方法修改一个现有属性不会导致该属性的- setter被调用。
 
- 属性描述符(property descriptor)可以分为数据描述符(data descriptor)和访问器描述符(accessor descriptor),对应的属性可以称之为数据属性和访问器属性。
- 
descriptor配置(都是可选的!)- 
共有的配置项(shared keys) - 
configurable:属性的类型是否可以切换(数据属性 ⇔ 访问器属性);属性是否可以被删除;该属性的属性描述符的其他配置项是否可以被修改。默认值为false。特殊情况:对于数据描述符而言,如果配置了 writable: true,那么无论如何,该属性的值都可以被修改,同时writable配置项可以被修改为false。
- 
enumerable:属性是否可枚举(如for...in、Object.keys)。默认值为false。
 
- 
- 
数据描述符的配置项 - value:属性对应的值,可以是任意合法的 JavaScript 值。默认值为- undefined。
- writable:属性是否可以通过赋值操作符(assignment operator)被修改。默认值为- false。
 
- 
访问器描述符的配置项 - get:属性的- getter,访问该属性时调用,不接受参数,同时将其返回值视作属性值。默认值为- undefined。
- set:属性的- setter,对该属性赋值时调用,接受一个参数(即赋的新值)。默认值为- undefined。
 注意事项:在 get或set方法被调用时,函数中的this指向的是访问或修改该属性的对象。需要注意的是,由于 JavaScript 的继承机制,this指向的不一定是属性所定义的对象。
 
- 
 
- 
方法装饰器
- 
语法 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/** 
 * 方法装饰器
 * @param target 对于静态方法,该参数是类;对于实例方法,该参数是类的原型对象
 * @param propertyKey 方法名
 * @param descriptor 方法的描述对象,其 value 属性是被装饰的方法
 */
 function MethodDecoratorDemo(
 target: object,
 propertyKey: string,
 descriptor: PropertyDescriptor
 ) {
 console.log(target, propertyKey, descriptor);
 }
 class Person {
 constructor(public name: string, public age: number) {}
 speak() {
 // 等价于 MethodDecoratorDemo(prototype, speak, descriptor)
 console.log(`Hi, my name is ${this.name}, and my age is ${this.age}`);
 }
 static isAdult(age: number) {
 // 等价于 MethodDecoratorDemo(Person, isAdult, descriptor)
 return age >= 18;
 }
 }
- 
应用举例:定义一个 Logger方法装饰器,用于在方法执行前和执行后,追加一些额外的逻辑;一个Validate方法装饰器,用于验证数据。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
 63
 64
 65
 66/** 
 * 用于在实例方法执行前后追加一些额外逻辑的方法装饰器
 * @param target 类的原型对象
 * @param propertyKey 方法名
 * @param descriptor 方法描述对象
 */
 function Logger(
 target: object,
 propertyKey: string,
 descriptor: PropertyDescriptor
 ) {
 /* 1. 缓存原始方法 */
 const original = descriptor.value;
 /* 2. 替换原始方法 */
 descriptor.value = function (...args: any[]) {
 console.log(`正在准备执行${propertyKey}方法······`);
 const result = original.call(this, ...args);
 console.log(`${propertyKey}方法已执行完毕!`);
 return result;
 };
 }
 /**
 * 用于验证静态方法参数合法性的方法装饰器工厂
 * @param maxValue 最大参数值
 * @returns 方法装饰器
 */
 function Validate(maxValue: number) {
 return function (
 target: object,
 propertyKey: string,
 descriptor: PropertyDescriptor
 ) {
 /* 1. 缓存原始方法 */
 const original = descriptor.value;
 /* 2. 替换原始方法 */
 descriptor.value = function (...args: any[]) {
 if (args[0] > maxValue)
 throw new Error(
 `非法的参数,所传入参数值 ${args[0]} 大于最大允许传入值为 ${maxValue}`
 );
 return original.call(this, ...args);
 };
 };
 }
 class Person {
 constructor(public name: string, public age: number) {}
 
 speak(repetition: number) {
 for (let i = 0; i < repetition; i++) {
 console.log(`My name is ${this.name}, and my age is ${this.age}`);
 }
 }
 (150)
 static isAdult(age: number) {
 return age >= 18;
 }
 }
 const p = new Person("Tom", 23);
 p.speak(5);
 console.log(Person.isAdult(149));
 console.log(Person.isAdult(200));补充 Function.prototype.apply/call/bind 的使用- Function.prototype.call():以给定的- this值和逐个提供的参数调用该函数。- 语法:call(thisArg[, arg1, arg2, /* …, */ argN])
- 参数
- thisArg:调用- func时要使用的- this值。非严格模式下,- null和- undefined被替换为全局对象,原始值被转换为对象。
- arg1, arg2, /* …, */ argN:可选的若干函数参数。
 
- 返回值:使用指定的 this值和若干可选参数调用函数后的结果。
 
- 语法:
- Function.prototype.apply():以给定的- this值和以数组(或类数组对象)提供的参数调用该函数。- 语法:apply(thisArg, argsArray)
- 参数
- thisArg:调用- func时要使用的- this值。非严格模式下,- null和- undefined被替换为全局对象,原始值被转换为对象。
- argsArray:可选的类数组对象,用于指定调用- func时的参数。
 
- 返回值:使用指定的 this值和可选的类数组对象提供的参数调用函数后的结果。
 
- 语法:
- Function.prototype.bind():根据原始函数创建一个新函数,同时指定该新函数的- this值和初始参数。- 语法:bind(thisArg, arg1, arg2, /* …, */ argN)
- 参数
- thisArg:调用新函数时要使用的- this值。非严格模式下,- null和- undefined被替换为全局对象,原始值被转换为对象。如果新函数被当作构造函数使用,则该参数会被忽略。
- arg1, arg2, /* …, */ argN:调用新函数时,插入到传入新函数中的参数前的可选的初始参数。
 
- 返回值:使用指定的 this值和可选的初始参数创建的新函数。
 
- 语法:
 
访问器装饰器
- 
语法 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/** 
 * 访问器装饰器
 * @param target 对于静态访问器,该参数是类;对于实例访问器,该参数是类的原型对象
 * @param propertyKey 访问器名称
 * @param descriptor 访问器的描述对象,对应 get 和 set 属性是被装饰的访问器方法
 */
 function AccessorDecoratorDemo(
 target: object,
 propertyKey: string,
 descriptor: PropertyDescriptor
 ) {
 console.log(target, propertyKey, descriptor);
 }
 class Person {
 constructor(public name: string, public age: number) {}
 get school() {
 return "XDU";
 }
 static get address() {
 return "Xi'an";
 }
 }
- 
应用举例:对 Weather类的temp属性的set访问器进行限制,允许设置的取值范围为[-50, 50]。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/** 
 * 生成限制数值范围的访问器装饰器工厂
 * @param min 最低取值
 * @param max 最高取值
 * @returns 限制数值范围的访问器装饰器
 */
 function RangeValidate(min: number, max: number) {
 return function (
 target: object,
 propertyKey: string,
 descriptor: PropertyDescriptor
 ) {
 /* 1. 缓存原始的 setter */
 const original = descriptor.set;
 /* 2. 替换 setter,加入范围验证逻辑 */
 descriptor.set = function (value: number) {
 if (value < min || value > max)
 throw new Error(
 `${propertyKey} 的取值范围应该在 ${min} 和 ${max} 之间`
 );
 if (original) original.call(this, value);
 };
 };
 }
 class Weather {
 private _temp: number;
 constructor(temp: number) {
 this._temp = temp;
 }
 (-50, 50) set temp(value: number) {
 this._temp = value;
 }
 get temp() {
 return this._temp;
 }
 }
 const w = new Weather(20);
参数装饰器
- 
语法 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21/** 
 * 参数装饰器
 * @param target 修饰静态方法的参数时,该参数是类;修饰实例方法的参数时,该参数是类的原型对象
 * @param propertyKey 参数所在方法的名称
 * @param parameterIndex 参所在方法的参数列表中的索引(0-based)
 */
 function ArgumentDecoratorDemo(
 target: object,
 propertyKey: string,
 parameterIndex: number
 ) {
 console.log(target, propertyKey, parameterIndex);
 }
 class Person {
 constructor(public name: string) {}
 speak( message: string) {
 console.log(`${this.name} say: ${message}`);
 }
 }
- 
应用举例:定义方法装饰器 Validate,同时搭配参数装饰器NotNumber,来对指定方法的参数类型进行限制。NotNumber限制指定参数不能是数值类型,Validate 来验证使用NotNumber装饰器的参数是否满足条件。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/** 
 * 记录类型不为 number 的参数的参数装饰器
 * @param target 类的原型对象
 * @param propertyKey 参数所在的方法名
 * @param parameterIndex 参数在方法参数列表中的索引
 */
 function NotNumber(target: any, propertyKey: string, parameterIndex: number) {
 /* 存储方法 propertyKey 中所有限制类型不为数字的参数索引 */
 const notNumberArr: number[] = target[`__notNumber_${propertyKey}`] || [];
 notNumberArr.push(parameterIndex);
 target[`__notNumber_${propertyKey}`] = notNumberArr;
 }
 /**
 * 验证是否所有使用了 NotNumber 参数装饰器的参数都符合条件
 * @param target 类的原型对象
 * @param propertyKey 方法名
 * @param descriptor 方法的描述对象
 */
 function Validate(
 target: any,
 propertyKey: string,
 descriptor: PropertyDescriptor
 ) {
 /* 缓存原始方法 */
 const originalMethod = descriptor.value;
 /* 替换原始方法 */
 descriptor.value = function (...args: any[]) {
 const notNumberArr: number[] = target[`__notNumber_${propertyKey}`] || [];
 notNumberArr.forEach((index) => {
 if (typeof args[index] === "number")
 throw new Error(`第 ${index} 个参数不能为 number`);
 });
 return originalMethod.call(this, ...args);
 };
 }
 class Person {
 constructor(public name: string) {}
 
 speak( message: any) {
 console.log(`${this.name} say: ${message}`);
 }
 }
 const p = new Person("Tom");
 p.speak("Hello");
 p.speak(1);