---
title: "装饰器"
date: "2026-04"
category: "TS"
tags: ["TS"]
description: "ts这个装饰器从本质上来说仍然是一个高阶函数，和py的装饰器在使用场景上是差不多的：当我需要给某个对象进行一个「包装」，但又不想整个重写这个原有的方法时，那我就要用装饰器..."
source: "https://enriquemark.com/zh-hans/posts/%E8%A3%85%E9%A5%B0%E5%99%A8"
---

ts这个装饰器从本质上来说仍然是一个高阶函数，和py的装饰器在使用场景上是差不多的：当我需要给某个对象进行一个「包装」，但又不想整个重写这个原有的方法时，那我就要用装饰器。比如给已有的方法加log，这在py里面也是典型的@场景。

```typescript
function logger(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value  // 拿到原方法
    
    descriptor.value = function(...args: any[]) {
        console.log(`调用 ${key}`)
        return original.apply(this, args)
    }
    // 不需要 return，直接改 descriptor.value
}

class Greeter {
    @logger
    greet(name: string) {
        console.log(`hello ${name}`)
    }
}
```

这里的 `target, key, descriptor` 是ts的特色，或者说js的特色，本质上就是 `prototype` 机制需要的内容。`target` 直接指向这个方法或者类的原型链；而 `key` 比较简单，就是这个方法大的名字；`descriptor` 是这个方法的「设定」，而我们一般只关心 `descriptor.value`，也就是这个方法本身：在这里等价于py的func。

这也是ts麻烦的点。在py我可以直接return新的函数，里面的内容就是单纯func()，然后在它前面加我需要的逻辑包装就行了。但是在ts这里，我就得用一个很麻烦的机制重写这个方法。

```ts
function logger(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value  // 保存原方法

    descriptor.value = function(...args: any[]) {
        console.log(`调用 ${key}，参数:`, args)
        const result = original.apply(this, args)  // 调用原方法
        console.log(`${key} 返回:`, result)
        return result
    }

    // 不需要 return，直接改了 descriptor 就生效
}
```

从上面的整个逻辑就能看出来，想理解ts的@，关键就是理解js的原型链机制，一切方法的都是挂在原型链上的，你需要保证这个方法的正常性，就得把它的整个「全套」原封不动的带回去。比如 `this`和`function`，不用箭头函数的原因就是这里会丢this，如果丢了，那我原先方法里面的this操作就全没——其实这里就算不写装饰器也是一样的。

this本身就是指向自己的上级实例，也就是py里的self，我在类里面调用属性就要 `self.xxx` ，js这里就是 `this.xxx`，所有方法都是挂在自己的上级原型链之下的，而上级原型链的属性也是一样。`this.xxx` 指的就是上级实例下的xxx，js默认会顺着原型链查找。而箭头函数会丢失这个this上下文，它的this会指向定义时的外层对象，而不是当前调用对象。比如在这种包装场景下，我要是用箭头函数，指向的就是 `logger`，如果包装对象是某个类下的方法，那他就会完全吃不到自己所属类的属性，因为这里的this根本就没指向它。所以这里才得选择 `function`，this直接指向调用方（被包装对象）的上下文，然后在里面传的this才是指向正确的调用对象。

`original.apply(this, args)` 这一步在做的就是this上下文传递，保证this在最终结果上和包装对象的一致，而不会因为我对它的重写行为而丢失。
