---
title: "Decorators"
date: "2026-04"
category: "TS"
tags: ["TS"]
description: "The ts decorator is essentially still a higher-order function, and its use cases are about the same as py decorators: when I need to \"wrap\" some object but don't want to rewrite the whole original..."
source: "https://enriquemark.com/en/posts/%E8%A3%85%E9%A5%B0%E5%99%A8"
---

The ts decorator is essentially still a higher-order function, and its use cases are about the same as py decorators: when I need to "wrap" some object but don't want to rewrite the whole original method, I use a decorator. Like adding a log to an existing method, which is the classic @ case in py too.

```typescript
function logger(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value  // grab the original method
    
    descriptor.value = function(...args: any[]) {
        console.log(`calling ${key}`)
        return original.apply(this, args)
    }
    // no return needed, just change descriptor.value
}

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

The `target, key, descriptor` here is a ts thing, or really a js thing, essentially what the `prototype` mechanism needs. `target` points straight at the prototype chain of the method or the class; `key` is the easy one, it's the name of the method; `descriptor` is the method's "settings," and we usually only care about `descriptor.value`, which is the method itself, the equivalent of py's func here.

This is also the annoying part of ts. In py I can return a new function directly, the body of it is plain func(), and then I put whatever logic wrapping I need in front of it. But in ts I have to rewrite the method with a very annoying mechanism.

```ts
function logger(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value  // save the original method

    descriptor.value = function(...args: any[]) {
        console.log(`calling ${key}, args:`, args)
        const result = original.apply(this, args)  // call the original method
        console.log(`${key} returned:`, result)
        return result
    }

    // no return needed, changing descriptor is enough to make it work
}
```

You can see from the whole logic above that understanding ts's @ comes down to understanding js's prototype chain mechanism. Every method hangs on the prototype chain, and to keep a method working you have to hand its whole "kit" back untouched. `this` and `function`, for example. The reason not to use an arrow function is that this gets lost here, and once it's lost, all the this operations inside my original method are gone, which is the same even without a decorator.

this points at its own parent instance, which is self in py. Inside a class I access a property with `self.xxx`, and in js it's `this.xxx`. All methods hang under their own parent prototype chain, and the properties on that parent prototype chain work the same way. `this.xxx` means the xxx under the parent instance, and js looks it up along the prototype chain by default. An arrow function loses this context, its this points at the outer object at definition time instead of the current calling object. In a wrapping case like this, if I use an arrow function it points at `logger`, and if the wrapped thing is a method under some class, it gets nothing at all from the properties of the class it belongs to, because this doesn't point at it. So here I have to pick `function`, and this points straight at the context of the caller (the wrapped object), and then the this passed inside points at the right calling object.

What `original.apply(this, args)` does is pass the this context along, so that this ends up matching the wrapped object instead of getting lost because I rewrote it.

---

**Translation note.** I wrote this in Chinese. This English version is an LLM translation, so the wording is not mine even though the thinking is. Original: [装饰器](</zh-hant/posts/装饰器>).
