| 
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