JavaScriptReflectMetadata详解

更新时间:2023-07-25 11:41:20 阅读: 评论:0

JavaScriptReflectMetadata详解
引⾔
在 ES6 的规范当中,就已经存在 Reflect API 了。简单来说这个 API 的作⽤就是可以实现对变量操作的函数化,也就是反射。具体的关于这个 API 的内容,可以查看这个
然⽽我们在这⾥讲到的,却是 Reflect ⾥⾯还没有的⼀个规范,那么就是 Reflect Metadata。
Metadata
想必对于其他语⾔的 Coder 来说,⽐如说 Java 或者 C#,Metadata 是很熟悉的。最简单的莫过于通过反射来获取类属性上⾯的批注(在JS 当中,也就是所谓的装饰器)。从⽽可以更加优雅的对代码进⾏控制。
⽽ JS 现在有,虽然现在还在 Stage2 阶段。但是 JS 的装饰器更多的是存在于对函数或者属性进⾏⼀些操作,⽐如修改他们的值,代理变量,⾃动绑定 this 等等功能。
所以,后⽂当中我就使⽤ TypeScript 来进⾏讲解,因为 TypeScript 已经完整的实现了装饰器。
虽然 Babel 也可以,但是需要各种配置,⼈懒,不想配置那么多。
但是却⽆法实现通过反射来获取究竟有哪些装饰器添加到这个类/⽅法上。
于是 Reflect Metadata 应运⽽⽣。
Reflect Metadata
Relfect Metadata,简单来说,你可以通过装饰器来给类添加⼀些⾃定义的信息。然后通过反射将这些信息提取出来。当然你也可以通过反射来添加这些信息。 就像是下⾯这个例⼦所⽰。
@adata('name', 'A')
class A {
@adata('hello', 'world')
public hello(): string {
return 'hello world'
}
}
// 这⾥为什么要⽤ new A(),⽤ A 不⾏么?后⽂会讲到
是不是很简单,那么我简单来介绍⼀下~
概念
⾸先,在这⾥有四个概念要区分⼀下:
1. Metadata Key {Any} 后⽂简写 k。元数据的 Key,对于⼀个对象来说,他可以有很多元数据,每⼀个元数据都对应有⼀个 Key。⼀个
国泰民安造句很简单的例⼦就是说,你可以在⼀个对象上⾯设置⼀个叫做 'name' 的 Key ⽤来设置他的名字,⽤⼀个 'created time' 的 Key 来表⽰他创建的时间。这个 Key 可以是任意类型。在后⾯会讲到内部本质就是⼀个 Map 对象。
2. Metadata Value {Any} 后⽂简写 v。元数据的类型,任意类型都⾏。
3. Target {Object} 后⽂简写 o。表⽰要在这个对象上⾯添加元数据
4. Property {String|Symbol} 后⽂简写 p。⽤于设置在那个属性上⾯添加元数据。⼤家可能会想,这个是⼲什么⽤的,不是可以在对象
上⾯添加元数据了么?其实不仅仅可以在对象上⾯添加元数据,甚⾄还可以在对象的属性上⾯添加元数据。其实⼤家可以这样理解,当你给⼀个对象定义元数据的时候,相当于你是默认指定了 undefined 作为 Property。 下⾯有⼀个例⼦⼤家可以看⼀下。
⼤家明⽩了上⾯的概念之后,我之前给的那个例⼦就很简单了~不⽤我多说了。
安装/使⽤
卖花歌
下⾯不如正题,我们怎么开始使⽤ Reflect Metadata 呢?ipcs
⾸先,你需要安装 reflect-metadata polyfill,然后引⼊之后就可以看到在 Reflect 对象下⾯有很多关于 Metadata 的函数了。因为这个还没有进⼊正式的协议,所以需要安装垫⽚使⽤。
啥,Reflect 是啥,⼀个全局变量⽽已。
你不需要担⼼这个垫⽚的质量,因为连 Angular 都在使⽤呢,你怕啥。
之后你就可以安装我上⾯写的⽰例,在 TypeScript 当中去跑了。
类/属性/⽅法装饰器
看这个例⼦。
@adata('name', 'A')
class A {
@adata('name', 'hello')
hello() {}
心静的诗句}
const objs = [A, new A, A.prototype]
const res = objs.map(obj => [
])
// ⼤家猜测⼀下 res 的值会是多少?
想好了么?再给你 10 秒钟
10
9
8
7
6
5
4
3
2
1
res
吃晚饭英语怎么说
[
['A', undefined, 'A', undefined],
[undefined, 'hello', undefined, undefined],
[undefined, 'hello', undefined, 'hello'],
]
那么我来解释⼀下为什么回是这样的结果。
⾸先所有的对类的修饰,都是定义在类这个对象上⾯的,⽽所有的对类的属性或者⽅法的修饰,都是定义在类的原型上⾯的,并且以属性或者⽅法的 key 作为 property,这也就是为什么 getMetadata 会产⽣这样的效果了。
那么带 Own 的⼜是什么情况呢?
这就要从元数据的查找规则开始讲起了
原型链查找
类似于类的继承,查找元数据的⽅式也是通过原型链进⾏的。
就像是上⾯那个例⼦,我实例化了⼀个 new A(),但是我依旧可以找到他原型链上的元数据。
举个例⼦
class A {
@adata('name', 'hello')
hello() {}
}
const t1 = new A()
const t2 = new A()
Reflect.defineMetadata('otherName', 'world', t2, 'hello')
衰草连天什么意思
⽤途
其实所有的⽤途都是⼀个⽬的,给对象添加额外的信息,但是不影响对象的结构。这⼀点很重要,当你给对象添加了⼀个原信息的时候,对象是不会有任何的变化的,不会多 property,也不会有的 property 被修改了。
但是可以衍⽣出很多其他的⽤途。
Anuglar 中对特殊字段进⾏修饰 (Input),从⽽提升代码的可读性。
可以让装饰器拥有真正装饰对象⽽不改变对象的能⼒。让对象拥有更多语义上的功能。
API
namespace Reflect {
// ⽤于装饰器
metadata(k, v): (target, property?) => void
// 在对象上⾯定义元数据
defineMetadata(k, v, o, p?): void
// 是否存在元数据
hasMetadata(k, o, p?): boolean
查英语
hasOwnMetadata(k, o, p?): boolean
// 获取元数据
getMetadata(k, o, p?): any
getOwnMetadata(k, o, p?): any
// 获取所有元数据的 Key
getMetadataKeys(o, p?): any[]
getOwnMetadataKeys(o, p?): any[]
// 删除元数据
deleteMetadata(k, o, p?): boolean
}
⼤家可能注意到,针对某些操作,会有 Own 的函数。这是因为有的操作是可以通过原型链进⾏操作的。这个后⽂讲解。
深⼊ Reflect Metadata
实现原理
如果你去翻看官⽹的⽂档,他会和你说,所有的元数据都是存在于对象下⾯的 [[Metadata]] 属性下⾯。⼀开始我也是这样认为的,新建⼀个Symbol('Metadata'),然后将元数据放到这个 Symbol 对应的 Property 当中。直到我看了源码才发现并不是这样。请看例⼦
@adata('name', 'A')
class A {}
哈哈,并没有所谓的 Symbol,那么这些元数据都存在在哪⾥呢?
其实是内部的⼀个 WeakMap 中。他正是利⽤了 WeakMap 不增加引⽤计数的特点,将对象作为 Key,元数据集合作为 Value,存到WeakMap 中去。
如果你认真探寻的话,你会发现其内部的数据结构其实是这样的
WeakMap<any, Map<any, Map<any, any>>>
是不是超级绕,但是我们从调⽤的⾓度来思考,这就⼀点都不绕了。
<(o).get(p).get(k)
先根据对象获取,然后在根据属性,最后根据元数据的 Key 获取最终要的数据。
十个优点End
因为 Reflect Metadata 实在是⽐较简单,这⾥就不多讲解了。更多内容请查看
题外话
其实看了源码之后还是挺惊讶的,按照⼀般的套路,很多 polyfill 会让你提供⼀些前置的 polyfill 之后,当前的 polyfill 才能使⽤。但是reflect-metadata 竟然内部⾃⼰实现了很多的 polyfill 和算法。⽐如
Map, Set, WeakMap, UUID。最惊讶的莫过于 WeakMap 了。不是很仔细的阅读了⼀下,好像还是会增加引⽤计数。

本文发布于:2023-07-25 11:41:20,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1095995.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:数据   对象   添加   定义   属性   信息   装饰   获取
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图