目录
⼀、简介
1、装饰器本质是⼀种特殊的函数,它可以对:类、属性、⽅法、参数进⾏扩展,同时能让代码更简洁。
2. 装饰器⾃ 2015 年在 ECMAScript-6 中被提出到现在,已将近10年
3. 截⽌⽬前,装饰器依然是实验性特性 ,需要开发者⼿动调整配置,来开启装饰器⽀持
4. 装饰器有 5 种
1.类装饰器
2.属性装饰器
3.⽅法装饰器
4.访问器装饰器
5.参数装饰器
备注:虽然TypeScript5.0中可以直接使⽤类装饰器,但为了确保其他装饰器可⽤,现阶
段使⽤时,仍建议使⽤experimentalDecorators配置来开启装饰器⽀持,⽽且不排除在来
的版本中,官⽅会进⼀步调整装饰器的相关语法!
⼆、类装饰器
1. 基本语法
类装饰器是⼀个应⽤在类声明上的函数,可以为类添加额外的功能,或添加额外的逻辑
/*
Demo函数会在Person类定义时执⾏
参数说明:
○ target参数是被装饰的类,即:Person
*/
function Demo(target: Function) {
console.log(target)
}
// 使⽤装饰器
@Demo
class Person { }
2. 应⽤举例
需求:定义⼀个装饰器,实现 Person 实例调⽤ toString 时返回 JSON.stringify 的执⾏结果。
// 使⽤装饰器重写toString⽅法 + 封闭其原型对象
function CustomString(target: Function) {
// 向被装饰类的原型上添加⾃定义的 toString ⽅法
target.prototype.toString = function () {
return JSON.stringify(this)
}
// 封闭其原型对象,禁⽌随意操作其原型对象
Object.seal(target.prototype)
}
// 使⽤ CustomString 装饰器
@CustomString
class Person {
constructor(public name: string, public age: number) { }
speak() {
console.log('你好呀!')
}
}
/* 测试代码如下 */
let p1 = new Person('张三', 18)
// 输出:{"name":"张三","age":18}
console.log(p1.toString())
// 禁⽌随意操作其原型对象
interface Person {
a: any
}
// Person.prototype.a = 100 // 此⾏会报错:Cannot add property a, object is not
extensible
// console.log(p1.a)
3. 关于返回值
类装饰器有返回值:若类装饰器返回⼀个新的类,那这个新类将替换掉被装饰的类。
类装饰器⽆返回值:若类装饰器⽆返回值或返回 undefined,那被装饰的类不会被替换。
function demo(target:Function){
// 装饰器有返回值时,该返回值会替换掉被装饰的类
return class {
test(){
console.log(200)
console.log(300)
console.log(400)
}
}
}
@demo
class Person {
test(){
console.log(100)
}
}
console.log(Person)
4. 关于构造类型
在 TypeScript 中,Function类型所表示的范围⼗分⼴泛,包括:普通函数、箭头函数、⽅法等等。但并⾮Function类型的函数都可以被new关键字实例化,例如箭头函数是不能被实例化的,那么 TypeScript 中概如何声明⼀个构造类型呢?有以下两种⽅式:
仅声明构造类型
/*
○ new 表示:该类型是可以⽤new操作符调⽤。
○ ...args 表示:构造器可以接受【任意数量】的参数。
○ any[] 表示:构造器可以接受【任意类型】的参数。
○ {} 表示:返回类型是对象(⾮null、⾮undefined的对象)。
*/
// 定义Constructor类型,其含义是构造类型
type Constructor = new (...args: any[]) => {};
function test(fn:Constructor){}
class Person {}
test(Person)
声明构造类型 + 指定静态属性
// 定义⼀个构造类型,且包含⼀个静态属性 wife
type Constructor = {
new(...args: any[]): {}; // 构造签名
wife: string; // wife属性
};
function test(fn:Constructor){}
class Person {
static wife = 'asd'
}
test(Person)
5. 替换被装饰的类
对于⾼级⼀些的装饰器,不仅仅是覆盖⼀个原型上的⽅法,还要有更多功能,例如添加新的⽅法和 状态。
需求:设计⼀个 LogTime 装饰器,可以给实例添加⼀个属性,⽤于记录实例对象的创建时间,再添加⼀个⽅法⽤于读取创建时间。
// User接⼝
interface User {
getTime(): Date
log(): void
}
// ⾃定义类型Class
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 User {
constructor(
public name: string,
public age: number
) { }
speak() {
console.log(`${this.name}说:你好啊!`)
}
}
const user1 = new User('张三', 13);
user1.speak()
console.log(user1.getTime())
三、装饰器⼯⼚
装饰器⼯⼚是⼀个返回装饰器函数的函数,可以为装饰器添加参数,可以更灵活地控制装饰器的⾏为。
需求:定义⼀个 LogInfo 类装饰器⼯⼚,实现 Person 实例可以调⽤到 introduce ⽅法,且 introduce 中输出内容的次数,由 LogInfo 接收的参数决定。
interface Person {
introduce: () => void
}
// 定义⼀个装饰器⼯⼚ LogInfo,它接受⼀个参数 n,返回⼀个类装饰器
function LogInfo(n:number) {
// 装饰器函数,target 是被装饰的类
return function(target: Function){
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
) { }
speak() {
console.log('你好呀!')
}
}
let p1 = new Person('张三', 18)
// console.log(p1) // 打印的p1是:_classThis,转换的JS版本⽐较旧时,会出现,不必纠结
p1.speak()
p1.introduce()
四、装饰器组合
装饰器可以组合使⽤,执⾏顺序为:先【由上到下】的执⾏所有的装饰器⼯⼚,依次获取到装饰器,然后再【由下到上】执⾏所有的装饰器
装饰器组合 —— 执⾏顺序(控制台打印:test2⼯⼚ test3⼯⼚ test4 test3 test2 test1)从上到下 或者说从左到右
//装饰器
function test1(target:Function) {
console.log('test1')
}
//装饰器⼯⼚
function test2() {
console.log('test2⼯⼚')
return function (target:Function) {
console.log('test2')
}
}
//装饰器⼯⼚
function test3() {
console.log('test3⼯⼚')
return function (target:Function) {
console.log('test3')
}
}
//装饰器
function test4(target:Function) {
console.log('test4')
}
@test1
@test2()
@test3()
@test4
class Person { }
装饰器组合 —— 应⽤
// ⾃定义类型Class
type Constructor = new (...args: any[]) => {}
interface Person {
introduce():void
getTime():void
}
// 使⽤装饰器重写toString⽅法 + 封闭其原型对象
function customToString(target: Function) {
// 向被装饰类的原型上添加⾃定义的 toString ⽅法
target.prototype.toString = function () {
return JSON.stringify(this)
}
// 封闭其原型对象,禁⽌随意操作其原型对象
Object.seal(target.prototype)
}
// 创建⼀个装饰器,为类添加⽇志功能和创建时间
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}`;
}
};
}
// 定义⼀个装饰器⼯⼚ LogInfo,它接受⼀个参数 n,返回⼀个类装饰器
function LogInfo(n:number) {
// 装饰器函数,target 是被装饰的类
return function(target: Function){
target.prototype.introduce = function () {
for (let i = 0; i < n; i++) {
console.log(`我的名字:${this.name},我的年龄:${this.age}`)
}
}
}
}
@customToString
@LogInfo(3)
@LogTime
class Person {
constructor(
public name: string,
public age: number
) { }
speak() {
console.log('你好呀!')
}
}
const p1 = new Person('张三',18)
console.log(p1.toString())
p1.introduce()
console.log(p1.getTime())
上面说的这么多全都是类的装饰器,接下来是属性装饰器了
五、属性装饰器
- 基本语法