|
169 | 169 | }
|
170 | 170 | ```
|
171 | 171 |
|
| 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 | +## context 与 metadata 二三讲 |
| 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