目录
前言
TypeScript 装饰器 | 阮一峰 TypeScript 教程
- 最近,在进行学习 Nest 后台服务的时候发现,对于 TS 装饰器的用法还是有一些模糊,于是写一篇文章来详细讲解这个概念,目前设置到的知识点如下:
- 类、原型与原型链、this 指向与绑定
前置知识
类与实例
TypeScript 的 class 类型 | 阮一峰 TypeScript 教程
原型与原型链
- 原型:每一个 JavaScript 对象(
null
除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性,其实就是prototype
对象,主要作用是共享方法 - 原型链:由相互关联的原型组成的
链状结构
就是原型链
- 以一个经典的类继承举例
- 首先定义一个
Person
类,定义Teacher
继承Person
类,然后输出参数构造Teacher
类的实例t1
- 则原型链向上查找的过程为
t1
的隐式原型__proto__
指向类Teacher
的显示原型prototype
,以此类推执行父类Person
的prototype
,父类的__proto__
指向Object
的prototype
。最终Object
的__proto__
指向null
instanceof
:能否在向上查找的原型链中,找到和目标匹配的显示原型hasOwnProperty()
方法返回一个布尔值,表示对象自有属性(而不是继承来的属性)中是否具有指定的属性。
this 指向与绑定
相同点:都会改变函数的this
指向
绑定不同:
bind
不会改变原函数的this
关键字,返回一个新的函数,不会立刻调用call
、apply
都可以立即调用原函数
传参不同:
bind
、call
传入一组参数apply
传入数组参数
简介
- 装饰器(Decorator):是一种语法结构,在定义时修改类的行为,表现
@ + 表达式
- 表达式必须是一个函数,或者执行后得到一个函数
- 接收所装饰对象的一些相关值作为参数
- 函数要么修改原有值,要么返回新对象取代修饰的目标对象
- TypeScript 早期支持旧的装饰器语法(后续讲解),5.0 后使用标准语法,传统语法需要打开
--experimentalDecorators
编译参数 - 不同类型的装饰器可以修改/代替原目标对象的内容
- 结构如下所示:
type Decorator = (
value: DecoratedValue, // 所装饰的对象
context: { // 上下文对象
kind: string; // 类型,class, method, getter, setter, field, accessor
name: string | symbol; // 所装饰目标的名称
addInitializer?(initializer: () => void): void; // 完善类的初始化逻辑
static?: boolean; // 所装饰的对象是否为类的私有成员
private?: boolean; // 所装饰的对象是否为类的静态成员
access: { // 一个对象,包含了某个值的 get 和 set 方法
get?(): unknown;
set?(value: unknown): void;
};
}
) => void | ReplacementValue;
类装饰器
结构
type ClassDecorator = (
value: Function, // 当前类本身
context: {
kind: "class";
name: string | undefined;
addInitializer(initializer: () => void): void;
}
) => Function | void;
案例
function Greeter(value: any, context: any) {
// 代替当前的类
return class extends value {
age: number = 18
greet() {
console.log('Hello, ' + this.name, this.age)
}
}
// 代替当前的构造方法
const newConstructor = function (...args: any[]) {
const instance = new value(...args)
instance.age = 18
// 添加新的方法
instance.greet = function () {
console.log('Hello, ' + this.name, this.age)
}
return instance
}
// 复制原型链
newConstructor.prototype = value.prototype
return newConstructor
}
// @ts-ignore
@Greeter
class Person {
public name: string
constructor(name: string) {
this.name = name
}
greet() {}
}
const person = new Person('John')
person.greet() // Hello, John
方法装饰器
结构
// 方法装饰器会改写类的原始方法,实际上就是传递方法的代码结构,在基础上进行修改
type ClassMethodDecorator = (
value: Function, // 方法体
context: {
kind: "method";
name: string | symbol;
static: boolean; // 是否为静态方法
private: boolean; // 是否为只读属性
access: { get: () => unknown }; // 方法的存取器
addInitializer(initializer: () => void): void;
}
) => Function | void;
案例
function delay(ms: number) {
return function (target: any, context: any) {
return function (...args: any[]) {
console.log('延时执行', ms)
setTimeout(() => {
target.apply(target, args)
console.log('延时结束')
}, ms)
}
}
}
class Person {
public name: string
constructor(name: string) {
this.name = name
}
@delay(1000)
greet() {
console.log(this.name)
}
}
属性装饰器
结构
type ClassFieldDecorator = (
value: undefined, // 不能通过 value 获取装饰器的值
context: {
kind: "field";
name: string | symbol;
static: boolean;
private: boolean;
access: { get: () => unknown; set: (value: unknown) => void };
addInitializer(initializer: () => void): void;
}
) => (initialValue: unknown) => unknown | void;
案例
function fieldDecorator(target: any, context: any) {
const { kind, name } = context
if (kind === 'field') {
return function (value: string) {
console.log(`Decorating ${name} with value ${value}`)
return value
}
}
}
function twice(target: any, context: any) {
return (value: number) => value * 2
}
class Person {
@fieldDecorator
public name: string = 'John Doe'
@twice
public age: number = 30
}
const person = new Person()
console.log(person.name)
console.log(person.age)
getter、setter 装饰器
结构
type ClassGetterDecorator = (
value: Function,
context: {
kind: "getter";
name: string | symbol;
static: boolean;
private: boolean;
access: { get: () => unknown };
addInitializer(initializer: () => void): void;
}
) => Function | void;
type ClassSetterDecorator = (
value: Function,
context: {
kind: "setter";
name: string | symbol;
static: boolean;
private: boolean;
access: { set: (value: unknown) => void };
addInitializer(initializer: () => void): void;
}
) => Function | void;
案例
function lazy(target: any, { kind, name }: any) {
if (kind === 'getter') {
// 返回函数,这个函数会在实例上定义一个与装饰器目标同名的属性
return function (this: any) {
const result = target.call(this)
Object.defineProperty(this, name, {
value: result,
writable: false,
})
return result
}
}
}
class C {
@lazy
get value() {
console.log('正在计算……')
return '开销大的计算结果'
}
}
const inst = new C()
console.log(inst.value) // 正在计算…… 开销大的计算结果
console.log(inst.value) // 开销大的计算结果
accessor 装饰器
结构
type ClassAutoAccessorDecorator = (
value: { // 将变量变为私有属性,并自动添加 get/set 方法,静态属性和私有属性都可用
get: () => unknown;
set: (value: unknown) => void;
},
context: {
kind: "accessor";
name: string | symbol;
access: { get(): unknown; set(value: unknown): void };
static: boolean;
private: boolean;
addInitializer(initializer: () => void): void;
}
) => {
get?: () => unknown;
set?: (value: unknown) => void;
init?: (initialValue: unknown) => unknown; // 可以取代初始变量的值
} | void;
案例
function logAccess(value: any, context: any) {
const { get, set } = value
return {
get() {
const result = get.call(this)
console.log(`Getting ${String(context.name)}: ${result}`)
return result
},
set(newValue: number) {
console.log(`Setting ${String(context.name)} to ${newValue}`)
set.call(this, newValue)
},
init(initValue: number) {
console.log(`Initializing ${String(context.name)} with ${initValue}`)
return initValue + 1 // 返回初始化值
},
}
}
class Student {
@logAccess
accessor age: number = 0
}
const student = new Student()
console.log(student.age) // Getting age: 1
student.age = 20 // Setting age to 20;
装饰器执行顺序
function d(str: string) {
console.log(`评估 @d(): ${str}`)
return (value: any, context: any) => console.log(`应用 @d(): ${str}`)
}
function log(str: string) {
console.log(str + ' 被调用')
return str
}
@d('类装饰器')
class T {
@d('静态属性装饰器')
static staticField = log('静态属性值')
@d('实例属性装饰器')
instanceField = log('实例属性值')
@d('静态方法装饰器')
static staticMethod() {
return log('静态方法')
}
@d('实例方法装饰器 2')
@d('实例方法装饰器 1')
[log('instanceMethod')]() {
return log('实例方法')
}
@d('getter 装饰器')
get getter() {
return log('getter')
}
@d('setter 装饰器')
set setter(value: any) {
log('setter')
}
}
const t = new T()
console.log(t.instanceField)
// 阶段一:评估
评估 @d(): 类装饰器
评估 @d(): 静态属性装饰器
评估 @d(): 实例属性装饰器
评估 @d(): 静态方法装饰器
评估 @d(): 实例方法装饰器 2
评估 @d(): 实例方法装饰器 1
instanceMethod 被调用
评估 @d(): getter 装饰器
评估 @d(): setter 装饰器
// 阶段二:应用
应用 @d(): 静态方法装饰器
应用 @d(): 实例方法装饰器 1
应用 @d(): 实例方法装饰器 2
应用 @d(): getter 装饰器
应用 @d(): setter 装饰器
应用 @d(): 静态属性装饰器
应用 @d(): 实例属性装饰器
应用 @d(): 类装饰器
静态属性值 被调用
// 阶段三:实例执行
实例属性值 被调用
实例属性值
- 评估阶段(evaluation):计算 @ 符号后面表达式的值,确保是函数
- 评估阶段的执行,从上到小执行
- 遇到目标是函数的返回的属性,需要执行返回结果才行
- 应用阶段(application):评估之后得到的函数,应用到所装饰目标
- 多个装饰器的,从下到上依次执行
- 静态方法 => 实例方法 => getter => setter => 静态属性 => 实例属性 => 类装饰器
- 静态属性值由函数返回的,此时执行
- 实例属性值由函数返回的,实例调用才执行