TypeScript简介
TypeScript
由微软开发,是基于JavaScript
的一个扩展语言
TypeScript包含了JavaScript的所有内容
TypeScript增加了静态类型检查、接口、泛型等很多现代开发特性,更适合大型项目的开发
TypeScript需要编译为JavaScript,然后交给浏览器或其他JavaScript运行环境执行
JavaScript中的典型问题
容易混淆的数据类型
逻辑漏洞
访问不存在的属性
低级的拼写错误
静态类型检查:
在代码运行前进行检查,发现代码的错误或不合理之处,减少运行时异常的出现的机率,此种检查叫做静态类型检查
,TypeScript的核心就是这个。简而言之就是把运行时的错误前置
同样的功能,TypeScript的代码量要大于
JavaScript,但由于TypeScript的代码结构更加清晰,在后期代码的维护中TypeScript远胜于JavaScript
编译TS 命令行编译(不推荐) 要把.ts文件编译成.js文件,需要配置TypeScript的编译环境,步骤如下:
第一步:
创建一个demo.ts
1 2 3 4 5 const person = { name : '张三' , age : 18 } console .log (`我叫${person.name} ,我今年${person.age} 岁了` )
第二步:
安装全局TypeScript
第三步:
使用命令编译.ts文件
自动化编译 第一步:
创建TypeScript编译控制文件
工程中会生成一个tsconfig.json
配置文件,其中包含着很多编译时的配置
默认编译的JS版本是ES7,可以手动调整为其他版本
第二步:
监视目录中的.ts
文件变化
第三步:
小优化,当编译出错时不生成.js
文件
1 tsc --noEmitOnError --watch
Tips:在tsconfig.json
文件中也可以修改
在Vue或其他框架中,TypeScript不许要用户自己处理,框架会自动编译
类型声明 格式:
1 2 3 4 修饰符 变量名:数据类型 = 值; function 函数名(形参名:数据类型,形参名:数据类型):返回值数据类型 { ... }
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let a :string ;let b :boolean ;let c :number ;a = "Hello World" ; b = false ; c = 2024 ; function count (x:number ,y:number ):number { return x + y; } let res :number = count (1 ,2 );console .log (a,b,c);console .log (res);
特殊方法(不常用)
1 2 3 4 let a :"你好" ; a = "世界" ; a = "你好" ;
类型推断 TS会根据代码进行类型推断,如下所示:
但是不推荐省略数据类型
类型总览 JS中的数据类型:
string
number
boolean
null
undefined
bigint
symbol
object
其中,object包含:Array、Function、Date、Error等
TS中的数据类型:
上述所有的JS数据类型
六个新类型:
any
unknown
never
void
tuple
enum
两个用于自定义类型的方式:
type
interface
注意点:
在 JavaScript中的这些内置构造函数:Number、String、Boolean,它们用于创建对应的包装对象,在开发日常很少使用,在TypeScript中也是同理,所以在TypeScript中进行类型声明时,通常都是小写 的number、string、boolean
示例:
1 2 3 4 5 6 7 let str1 :string ;str1 = "Hello" ; str2 = new String ("Hello" ); let str2 :String ;str2 = "Hello" ; str2 = new String ("Hello" )
原始类型VS包装对象:
原始类型:如number、string、boolean,在JavaScript中是简单数据类型,它们在内存中占用空间少,处理速度快
包装对象:如Number对象、String对象、Boolean对象,是复杂类型,在内存中占用更多空间,实际开发中很少使用
自动装箱:JavaScript在必要时会自动将原始类型包装成对象,以便调用方法或访问属性
常用类型 点击此处查看数据类型
any 任意类型,一旦将变量类型限制为any,那就意味着放弃了对该变量的类型检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let a :any ;a = 123 ; a = "hello world" a = false ; let b;b = 100 ; b = "bye bye" ; b = false
注意:any类型的变量,可以赋值给任意类型的变量
1 2 3 4 5 let c :any ;c = 5 ; let x :string ;x = c;
unknown 未知类型,可以理解为一个类型安全的any,适用于不确定数据的具体类型
1 2 3 4 5 let c :unknown ;c = 5 ; let x :string ;x = c;
并且,如果代码为以下形式
1 2 3 4 5 let c :unknown ;c = "hello world" ; let x :string ;x = c;
解决办法:
1 2 3 4 if (typeof c === "string" ) { x = c; }
1 2 3 x = a as string x = <string >a
读取any
类型数据的任何属性都不会报错,而unknown
则相反
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let str1 :string ;str1 = 'hello' ; str1.toUpperCase (); let str2 :any ;str2 = 'hello' ; str2.toUpperCase (); let str3 :unknown ;str3 = 'hello' ; str3.toUpperCase (); (str3 as string ).toUpperCase ();
never(少见) 任何值都不是,简言之就是不能有值,undefined、null、’’、0都不行
几乎不用never去直接限制变量,因为没有意义
1 2 3 4 5 6 7 8 let a :never ;a = 1 ; a = false ; a = "hello" ; a = undefined ; a = null ;
never一般是TypeScript主动推断出来的
1 2 3 4 5 6 7 8 let a :string ;a = "hello" ; if (typeof a === "string" ) { console .log (a.toUpperCase ); } else { console .log (a); }
never也可以用于限制函数的返回值
1 2 3 function throwError (str:string ):never { throw new Error ("程序异常" + str); }
void
void通常用于函数返回值的声明(就和Java中的void一样)
1 2 3 4 5 function logMessage (str:string ):void { console .log (str); } logMessage ("你好" );
注意:即使没有写函数的返回值,但是还是会有一个隐式返回值undefined
用void限定的函数还可以是以下的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 function logMessage1 (str:string ):void { console .log (str); } function logMessage2 (str:string ):void { console .log (str); return ; } function logMessage3 (str:string ):void { console .log (str); return undefined ; }
void函数不能对其返回值进行操作(这是与undefined函数的区别所在)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function test1 (str:string ):void { console .log ("Hello" ); return undefined ; } function test2 (str:string ):undefined { return undefined ; } let res1 = test1 ();let res2 = test2 ();if (res1) { console .log (res1); } if (res2) { console .log (res2); }
object 关于object
和Object
,实际开发中用的相对较小,因为范围太大了
声明对象类型 object(小写) 所有非原始类型,可以存储:对象、函数、数组等,由于限制的范围比较宽泛,实际开发中使用的相对较小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let a :object ; a = {}; a = {name :"张三" }; a = [1 ,2 ,3 ]; a = function ( ) {}; a = new String ("Hello" ); class Person {};a = new Person {}; a = 1 ; a = true ; a = "Hello" ; a = null ; a = undefined ;
Object(大写)(少见) 官方描述:所有可以调用Object方法的类型
简而言之:除了undefined和null的任何值
1 2 3 4 5 6 7 8 9 10 11 12 13 let b:Object; // 以下均无问题 b = {}; b = {name:"张三"}; b = [1,2,3]; b = function() {}; b = new String("Hello"); class Person {}; b = new Person{}; b = 1; b = false; b = "Hello";
限制一般对象 实际开发中,限制一般对象,通常用以下形式:
1 2 3 4 let person = {name :string , age?:number };person = {name :"张三" ,age :18 };
索引签名 允许定义对象可以具有任意数量的属性(具有动态属性的对象)
格式:[键名:键数据类型]:值数据类型
1 2 3 let person = {name :string , age?:number , [key :string ]:any };person = {name :"tom" ,age :18 ,gender :"男" ,city :"上海" };
声明函数类型 格式:
1 2 3 4 5 let 函数名:(形参名:形参类型,形参名:形参类型 ) => 返回值类型let 函数名 = function (形参名,形参名 ) { return 返回值类型数据; }
示例:
1 2 3 4 5 let count :(a:number ,b:number ) => number ;let count = (x,y ) => { return x + y; }
注意:
TypeScript中的=>
在函数类型声明时表示函数类型,描述其参数类型和返回类型(分隔符)
JavaScript中的=>
时一种定义函数的语法,是具体的函数实现
函数类型声明还可以使用:接口、自定义类型等方式
声明数组类型 格式:
1 2 3 4 5 let 数组名:数据类型[];let 数组名:Array <数据类型>;
示例:
1 2 3 4 5 let arr1 :string [];let arr2 :Array <number >;arr1 = ["a" ,"b" ]; arr2 = [100 ,200 ];
tuple 元组(Tuple)是一种特殊的数组类型,可以存储固定数量
的元素,并且每个元素的类型是已知的且可以不同。元组用于描述一组值的类型,?
表示可选元素
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 let arr1 :[string ,number ];let arr2 :[number ,boolean ?];let arr3 :[boolean ,...string []];arr1 = ["Hello" ,123 ]; arr2 = [100 ,false ]; arr2 = [200 ]; arr3 = [true ,"Hello" ,"World" ]; arr3 = [false ];
enum 枚举(enum)可以定义一组命名常量
,它能增强代码的可读性,也让代码更好维护
如下代码的功能是:
根据调用walk时传入的不同参数,执行不同的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function walk (str:string ) { if (str === "up" ) { console .log ("向上走" ); } else if (str === "down" ) { console .log ("向下走" ); } else if (str === "left" ) { console .log ("向左走" ); } else if (str === "right" ) { console .log ("向右走" ); } else { console .log ("未知方向" ); } } walk ("up" );walk ("down" );walk ("left" );walk ("right" );
存在的问题是调用walk时传参时没有任何提示,编码者很容易写错字符串内容;并且用于判断逻辑的up、down、left、right是连续且相关的一组值,那此时就特别适合使用枚举(enum)
数字枚举 数字枚举是最常见的枚举类型,其成员的值会自动递增
,且数字枚举还具备反射的特点。
1 2 3 4 5 6 7 8 9 enum Direction { Up , Down , Left , Right } console .log (Direction );
根据以上特点,可以修改之前的代码
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 Direction { Up , Down , Left , Right } function walk (data:Direction ) { if (data === Direction .Up ) { console .log ("向上走" ); } else if (data === Direction .Down ) { console .log ("向下走" ); } else if (data === Direction .Left ) { console .log ("向左走" ); } else if (data === Direction .Right ) { console .log ("向右走" ); } else { console .log ("未知方向" ); } } walk (Direction .Down );walk (Direction .Up );walk (Direction .Left );walk (Direction .Right );
优化过后的代码更加的便于维护,也可以防止错误输入的发生
字符串枚举 枚举成员是字符换
1 2 3 4 5 6 7 8 9 enum Direction { Up = "up" , Down = "down" , Left = "left" , Right = "right" } console .log (Direction );
常量枚举 官方描述:常量枚举是一种特殊枚举类型,它使用const
关键字定义,在编译时会被内联,避免生成一些额外的代码
内联:TypeScript在编译时,会将枚举成员引用替换为它们的实际值,而不是生成额外的枚举对象,这可以减少生成的JS代码量,并提高运行时的性能
例如:
普通枚举下的TS:
1 2 3 4 5 6 7 8 enum Direction { Up , Down , Left , Right } console .log (Direction .Up );
普通枚举下生成的JS:
1 2 3 4 5 6 7 8 9 "use strict" ;var Direction ;(function (Direction ) { Direction [Direction ["Up" ] = 0 ] = "Up" ; Direction [Direction ["Down" ] = 1 ] = "Down" ; Direction [Direction ["Left" ] = 2 ] = "Left" ; Direction [Direction ["Right" ] = 3 ] = "Right" ; })(Direction || (Direction = {})); console .log (Direction .Up );
常量枚举下的TS:
1 2 3 4 5 6 7 8 const enum Direction { Up , Down , Left , Right } console .log (Direction .Up );
常量枚举下生成的JS:
1 2 "use strict" ;console .log (0 );
type type可以为任意类型创建别名,让代码更简洁、可读性更强,同时更方便地进行类型复用和扩展
基本用法 类型别名使用type关键字定义,type后跟类型名称,如下:
1 2 3 4 type num = number;let price:num; price = 100 ;
联合类型 联合类型是一种高级形式,它表示一个值可以是几种不同类型之一
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type Statu = number | string type Gender = "男" | "女" function printStatus (data:Status ):void { console .log (data); } function printGender (data:Gender ) { console .log (data); } printStatus (404 );printStatus ("404" );printStatus (false ); printGender ("男" );printGender ("女" );printGender ("未知" );
交叉类型 交叉类型(Intersection Types)允许将多个类型合并为一个类型。合并后的类型将拥有所有被合并类型的成员。交叉类型通常用于对象类型
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type Area = { width :number ; height :height; } type Address = { num :number ; cell :number ; room :string ; } type House = Area & Address ;const house :House = { height :100 , width :100 , num :101 , cell :2 , room :"205" }
特殊情况(void不奏效) 在函数定义时,限制函数返回值为void,那么函数的返回值就必须为空
1 2 3 4 5 function demo ( ):void { return undefined ; return null ; }
使用类型声明限制函数返回值为void,TypeScript并不会严格要求函数返回空
1 2 3 4 5 6 7 8 9 10 11 type LogFunc = () => void ;const f1 :LogFunc = () => { return 100 ; } const f2 :LogFunc = () => 200 ; const f3 :LogFunc = function ( ) { return 300 ; }
导致的原因:
是为了确保如下代码成立,由于Arrary.prototype.push
的返回为一个数字,而Arrary.prototype.forEach
方法期望其回调的返回类型是void
1 2 3 4 const src = [1 ,2 ,3 ];const dst = [0 ];src.forEach ((el ) => {dst.push (el)});
类(Class) TypeScript中的类和Java的类相似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Person { name :string ; age :number ; constructor (name:string ,age:number ) { this .name = name; this .age = age; } speak ( ) { console .log (`我叫:${this .name} ,今年${this .age} 岁了` ) } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Student extends Person { grade :string ; constructor (name:string ,age:number ,grade:string ) { super (name,age); this .grader = grade; } study ( ) { console .log (`${this .name} 正在学习中` ); } override speak ( ) { console .log (`我叫:${this .name} ,今年${this .age} 岁了,正在上${this .grade} ` ) } }
上述代码包含了:成员变量声明,构造函数声明,成员方法声明,成员变量继承,成员方法重写
属性修饰符 和Java类似,TypeScript中也有属性修饰符
修饰符
含义
具体规则
public(默认)
公共属性
可以被:类内部、子类、类外部访问
protected
受保护属性
可以被:类内部、子类访问
private
私有属性
可以被:类内部访问
readonly
只读属性
属性无法修改
1 2 3 4 5 6 7 class Person { public name :string ; protected age :number ; private gender :string ; ... }
属性简写(成员变量简写) 1 2 3 class Person { constructor (private name: string , public age: number , protected gender: string ) { } }
抽象类 概念和Java中的类似(都是面向对象编程OOP):
格式:
抽象类定义:
抽象类方法定义:
抽象类的实现:
实例:
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 abstract class Package { constructor (public weight:number ) {}; abstract calculate ():number ; printInfo ( ) { console .log (`包裹重量为${this .weight} kg,运费为${calculate()} 元` ); } } class StandardPackage extends Package { constructor (weight:number ,public unitPrice:number ) {super (weight)}; calculate ():number { return this .weight * this .unitPrice ; } } const s1 = new StandardPackage (20 ,15 );s1.printInfo ();
接口(Interface) interface是一种定义结构的方式,主要作用是为:类、对象、函数等规定一种契约,这样可以确保代码的一致性和类型安全,但要注意interface只能定义格式,不能包含任何实现
格式:
接口定义:
类接口实现:
1 class 类名 implements 接口名 {}
对象接口实现(很像之前学的type
):
函数接口实现:
1 修饰符 函数名:接口名 = (形参 ) => {...}
实例:
类接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface PersonInterface { name :string ; age :number ; speak (n :number ):void ; } class Person implements PersonInterface { constructor (public name:string ,public age:number ) {}; speak (n:number ) { for (let i=0 ;i < n;i++) { console .log (`我叫:${this .name} ,今年${this .age} 岁了` ); } } } const p1 = new Person ("张三" ,18 );p1.speak (4 );
对象接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface UserInterface { name:string; readonly gender:string; age?:number; run:(n:number) => void; } const user:UserInterface = { name:"张三", gender:"男", age:18, run(n): { console.log(`跑了${n}米`) } }
函数接口实现
1 2 3 4 5 6 7 interface CountInterface { (a :number ,b :number ):numbner; } const count :CountInterface = (x,y ) => { return x + y; }
接口继承 格式:
1 2 3 4 5 6 7 interface 接口1 { ... }; interface 接口2 extends 接口1 { ... }
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface PersonInterface { name :string ; age :number ; } interface StudentInterface extends PersonInterface { grade :string ; } const s1 :StudentInterface = { name :'张三' , age :18 , grade :'大二' }
接口合并 格式:
1 2 3 4 5 6 7 8 interface 接口1 { 属性1 :属性类型; 属性2 :属性类型; } interface 接口1 { 属性3 :属性类型; }
最后,接口1
中会有属性1、2、3
interface和type的区别 相同点:
interface和type都可以用于定义对象结构,两者在许多场景中是可以互换的
不同点:
interface:更专注于定义对象和类的结构,支持继承、合并
type:可以定义类型别名、联合类型、交叉类型,但不支持继承和自动合并
泛型 泛型允许在定义函数、类或接口时,使用类型参数来表示未指定的类型,这些参数在具体使用时才被指定具体的类型,泛型能让同一段代码适用于多种类型,同时仍然保持类型的安全性
示例:
1 2 3 4 5 6 7 function logMessage<T>(data :T):T { console .log (data); return data; } logMessage<number >(100 ); logMessage<string >("Hello" );
多个泛型:
1 2 3 4 5 6 7 8 function logMessage<T,U>(data1 : T,data2 : U): T | U { console .log (data1,data2); return Date .now () % 2 ? data1 : data2; } console .log (logMessage<number ,boolean >(100 ,false ));console .log (logMessage<string ,string >("Hello" ,"World" ));
接口泛型:
1 2 3 4 5 6 7 8 9 10 11 interface PersonInterface <T> { name :string ; age :number ; extraInfo :T; } const p1 :PersonInterface <number > { name :"张三" , age :16 , extraInfo :100 }
类泛型:
1 2 3 4 5 6 7 8 9 10 class Person <T> { constructor (public name:string ,public age:string ,public extraInfo:T ); speak ( ) { console .log (`我叫${this .name} ,今年${this .age} 岁了` ); console .log (extraInfo); } } let p1 = new Person <string >("张三" ,15 ,"吃饭" )
类型定义文件(.d.ts文件) 类型声明文件是TypeScript中的一种特殊文件,通常以.d.ts作为拓展名。它的主要作用是为现有的JavaScript代码提供类型信息,使得TypeScript能够在使用这些JavaScript库或模块时进行类型检查和提示
示例:
1 2 3 4 5 6 7 8 export function add (a,b ) { return a+b; } export function mul (a,b ) { return a*b; }
1 2 3 4 5 declare function add (a:number ,b:number ):number ;declare function mul (a:number ,b:number ):number ;export {add,mul};
1 2 3 4 5 6 7 import {add,mul} from "./demo.js" ;const x = add (2 ,3 );const y = mul (4 ,5 );console .log (x,y)
装饰器
装饰器本质是一种特殊的函数,它可以对:类、属性、方法、参数进行拓展,同时能让代码更简洁
目前,装饰器依然是实验性特性,需要开发者手动调整配置,来开启装饰器支持
装饰器共有5种:
类装饰器
属性装饰器
方法装饰器
访问器装饰器
参数装饰器
Tips:虽然TypeScript5.0
中可以直接使用类装饰器,但是为了确保其他装饰器可用,现阶段使用时,仍建议使用experimentalDecorators
配置来开启装饰器支持,而且不排除在未来的版本中,官方会进一步调整装饰器的相关语法
类装饰器 基本语法 类装饰器是一个应用在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 function Demo (target:Function ) { console .log (target); } @Demo class Person { constructor (public name:string ,public age:number ) {}; }
引用举例 需求:定义一个装饰器,实现Person实例调用toString
时返回JSON.stringify
的执行结果
没有使用装饰器的代码:
1 2 3 4 5 6 7 class Person { constructor (public name:string ,public age:number ) {}; } let p1 = new Person ("张三" ,18 );console .log (p1.toString ()); console .log (JSON .stringify (p1));
使用装饰器的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 function CustomString (target:Function ) { target.prototype .toString = function ( ) { return JSON .stringify (this ); } } @CustomString class Person { constructor (public name:string ,public age:number ) {}; } let p1 = new Person ("张三" ,18 );console .log (p1.toString ());
返回值 类装饰器有返回值:若类装饰器返回一个新的类,那这个新类将替换掉被装饰的类
类装饰器无返回值:若类装饰器无返回值或返回undefined,那被装饰的类不会被替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function demo ( ) { return class { test ( ) { console .log (200 ); console .log (300 ); console .log (400 ); } } } @demo class Person { test ( ) { console .log (100 ); } } console .log (Person );
构造类型(自定义类型) 在TypeScript中,Function类型所表示的范围十分广泛,包括:普通函数、箭头函数、方法等,但并非所有Function类型的函数都可以被new
关键字实例化,例如箭头函数是不能被实例化的,那么TypeScript中该如何声明一个构造类型呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 type Constructor = new (...args :any []) => {};function test (fn:Constructor ) {};class Person {};test (Person );
1 2 3 4 5 6 7 8 9 10 11 12 type Constructor = { new (...args : any ): {}; el :string ; } function test (fn:Constructor ){};class Person { static el :string ; } test (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 Constructor = new (...args :any []) => {}; function LogTime <T extends Constructor >(target :T) { return class extends target { createdTime :Date ; constructor (...args:any [] ){ super (args); this .createdTime = new Date (); } getTime ( ){ return `该对象创建于${this .createdTime} ` ; } } } @LogTime class Person { constructor (public name:string ,public age:number ) {}; speak ( ) { console .log (`我叫${this .name} ,今年${this .age} 岁了` ); } } const p1 = new Person ("张三" ,16 );console .log (p1);console .log (p1.getTime ());
装饰器工厂 修饰器工程是一个返回修饰器函数的函数 ,可以为修饰器添加参数,可以更灵活地控制装饰器的行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type Constructor = new (...args :any []) => {};function LogInfo (n:number ) { return function (target:Constructor ) { target.prototype .introduce = function ( ) { for (let i = 0 ; i < n; i++) { console .log (`我叫${this .name} ,我今年${this .age} 岁了` ); } } } } @LogInfo (5 )class Person { constructor (public name:string ,public age:number ) {}; } let p1 = new Person ("张三" ,12 );p1.introduce ();
装饰器组合 装饰器可以组合使用,执行顺序为:先由上到下
的执行所有的装饰器工厂,依次获取到装饰器,然后再由下到上
执行所有的装饰器
属性装饰器 基本语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function demo (target:object ,propertyKey:string ) { console .log (target); console .log (propertyKey); } class Person { @demo public name :string ; public age :number ; @demo static school :string ; constructor (name:string ,age:number ) { this .name = name; this .age = age; }; }
属性遮蔽 如下所示:
代码1:
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 class Person { public name :string ; public age :number ; constructor (name:string ,age:number ) { this .name = name; this .age = age; }; } let p1 = new Person ("张三" ,18 );let value = 99 ;Object .defineProperty (Person .prototype ,'age' ,{ get ( ) { return value; }, set (val ) { value = val; } }) console .log (p1.age ); console .log (Person .prototype .age );
代码2:
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 class Person { public name :string ; public age :number ; constructor (name:string ,age:number ) { this .name = name; this .age = age; }; } let value = 99 ;Object .defineProperty (Person .prototype ,'age' ,{ get ( ) { return value; }, set (val ) { value = val; } }) let p1 = new Person ("张三" ,18 );console .log (p1.age ); console .log (Person .prototype .age );
代码1和代码2输出结果的不同就是由于属性遮蔽导致的
应用场景 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 function State (target:object ,propertyKey:string ) { let key = `__${propertyKey} ` Object .defineProperty (target,propertyKey,{ get ( ) { return this [key]; }, set (newValue ) { console .log (`${propertyKey} 的值被修改了,最新的值为${newValue} ` ); this [key] = newValue }, }) } class Person { public name :string ; @State public age :number ; constructor (name:string ,age:number ) { this .name = name; this .age = age; }; } const p1 = new Person ("张三" ,18 ); p1.age = 19 ;
方法装饰器 基本语法 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 function Demo (target:object ,propertyKey:string ,descriptor:PropertyDescriptor ) { console .log (target); console .log (propertyKey); console .log (descriptor); } class Person { constructor (public name:string ,public age:number ) {}; @Demo speak ( ) { console .log (`我叫:${this .name} ,今年${this .age} 岁了` ); } @Demo static isAdult (age:number ) { return age>= 18 ; } }
获取方法本身或修改方法本身 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 function Demo (target:object ,propertyKey:string ,descriptor:PropertyDescriptor ) { const originnal = descriptor.value ; descriptor.value = () => { console .log ("修改了原始方法" ); } } class Person { constructor (public name:string ,public age:number ) {}; @Demo speak ( ) { console .log (`我叫:${this .name} ,今年${this .age} 岁了` ); } @Demo static isAdult (age:number ) { return age>= 18 ; } } const p1 = new Person ("张三" ,18 );p1.speak ();