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