Skip to content

Commit 1b71beb

Browse files
younggglcyxcatliu
authored andcommittedAug 18, 2024··
wip: ch decorator, part 3 & 4
1 parent 1fc9e44 commit 1b71beb

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed
 

‎advanced/decorator.md

+137
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,140 @@
169169
}
170170
```
171171

172+
173+
## 装饰器的类别
174+
175+
通过以上例子,相信读者已经对装饰器有一定了解,且认识到了装饰器在一些场景的强大之处。在此引用[阮一峰 es6 教程](https://es6.ruanyifeng.com/#docs/decorator#%E7%AE%80%E4%BB%8B%EF%BC%88%E6%96%B0%E8%AF%AD%E6%B3%95%EF%BC%89)稍做总结:
176+
177+
> 装饰器是一种函数,写成`@ + 函数名`,可以用来装饰四种类型的值。
178+
>
179+
> -
180+
> - 类的属性
181+
> - 类的方法
182+
> - 属性存取器(accessor, getter, setter
183+
184+
> 装饰器的执行步骤如下。
185+
>
186+
> 1. 计算各个装饰器的值,按照从左到右,从上到下的顺序。
187+
> 2. 调用方法装饰器。
188+
> 3. 调用类装饰器。
189+
190+
不管是哪种类型的装饰器,它们的函数签名都可以认为是一致的,即均接收 `value`, `context` 两个参数,前者指被装饰的对象,后者指一个存储了上下文信息的对象。
191+
192+
## contextmetadata 二三讲
193+
194+
四种装饰器的 context,均包含以下信息:
195+
196+
- kind
197+
198+
描述被装饰的 value 的类型,可取 `class`, `method`, `field`, `getter`, `setter`, `accessor` 这些值。
199+
200+
- name
201+
202+
描述被装饰的 value 的名字。
203+
204+
- addInitializer
205+
206+
一个方法,接收一个回调函数,使得开发者可以侵入 value 的初始化过程作修改。
207+
208+
`class` 来说,这个回调函数会在类定义最终确认后调用,即相当于在初始化过程的最后一步。
209+
210+
对其他的 value 来说,如果是被 `static` 所修饰的,则会在类定义期间被调用,且早于其他静态属性的赋值过程;否则,会在类初始化期间被调用,且早于 value 自身的初始化。
211+
212+
以下是 `@bound` 类方法装饰器的例子,该装饰器自动为方法绑定 `this`
213+
214+
```ts
215+
const bound = (value, context: ClassMemberDecoratorContext) {
216+
if (context.private) throw new TypeError("Not supported on private methods.");
217+
context.addInitializer(function () {
218+
this[context.name] = this[context.name].bind(this);
219+
});
220+
}
221+
```
222+
223+
- metadata
224+
225+
和装饰器类似,[metadata](https://github.com/tc39/proposal-decorator-metadata) 也是处于 stage 3 阶段的一个提案。装饰器只能访问到类原型链、类实例的相关数据,而 metadata 给了开发者更大的自由,让程序于运行时访问到编译时决定的元数据。
226+
227+
举个例子:
228+
229+
```ts
230+
function meta(key, value) {
231+
return (_, context) => {
232+
context.metadata[key] = value;
233+
};
234+
}
235+
236+
@meta('a', 'x')
237+
class C {
238+
@meta('b', 'y')
239+
m() {}
240+
}
241+
242+
C[Symbol.metadata].a; // 'x'
243+
C[Symbol.metadata].b; // 'y'
244+
```
245+
246+
在上述程序中,我们通过访问类的 `Symbol.metadata` ,读取到了 meta 装饰器所写入的元数据。对元数据的访问,有且仅有这一种形式。
247+
248+
注意一点,metadata 是作用在类上的,即使它的位置在类方法上。想实现细粒度的元数据存储,可以考虑手动维护若干 `WeakMap`
249+
250+
251+
除了类装饰器以外,其他3种装饰器的 context 还拥有以下 3 个字段:
252+
253+
- static
254+
255+
布尔值,描述 value 是否为 static 所修饰。
256+
257+
- private
258+
259+
布尔值,描述 value 是否为 private 所修饰。
260+
261+
- access
262+
263+
一个对象,可在运行时访问 value 相关数据。
264+
265+
以类方法装饰器为例,用 `access.get` 可在运行时读取方法值,`access.has` 可在运行时查询对象上是否有某方法,举个例子:
266+
267+
```ts
268+
const typeToYellingMap = {
269+
cat: 'meow~ meow~',
270+
}
271+
272+
let yellingMethodContext: ClassMethodDecoratorContext
273+
274+
class Animal {
275+
type: string
276+
constructor(type: string) {
277+
this.type = type
278+
}
279+
280+
@yelling
281+
greet() {
282+
console.log(`Hello, I'm a(n) ${this.type}!`)
283+
}
284+
285+
accessor y = 1
286+
}
287+
288+
function yelling(originalMethod: any, context: ClassMethodDecoratorContext) {
289+
yellingMethodContext = context
290+
return function (this: any, ...args: any[]) {
291+
console.log(typeToYellingMap[this.type as keyof typeof typeToYellingMap])
292+
originalMethod.call(this, ...args)
293+
}
294+
}
295+
296+
const xcat = new Animal('cat')
297+
xcat.greet() // meow~ meow~
298+
// Hello, I'm a(n) cat!
299+
yellingMethodContext.access.get(xcat).call(xcat) // meow~ meow~
300+
// Hello, I'm a(n) cat!
301+
console.log(yellingMethodContext.access.has(xcat)) // true
302+
```
303+
304+
`getter` 类别的装饰器,其 `context.access` 同样拥有 `has`, `get` 两个方法。
305+
306+
对于 `setter` 类别的装饰器,则是 `has``set` 方法。
307+
308+
`filed``accessor` 类别的装饰器,拥有 `has`, `get`, `set` 全部三个方法。

0 commit comments

Comments
 (0)
Please sign in to comment.