概念
什么是反射?
在计算机学中,反射式编程(英语:reflective programming)或反射(英语:reflection),是指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。[1]用比喻来说,反射就是程序在运行的时候能够“观察”并且修改自己的行为。
– 来自维基百科
反射就是指程序运行阶段(RunTime)拦截程序,以达到获取、修改自身元数据的目的。这在其他高级语言中是非常常见的概念。
在JavaScript 这种动态语言中,反射可以说是无处不在。
我们随时可以去新增、修改对象的属性,甚至可以重新定义对象中的方法,如我们日常使用的 apply 、 defineProperty 操作就属于典型的反射。
什么是元数据?
元数据可以理解为:描述数据的数据。
比如现在有一个类,那么类里面的属性是描述这个类的数据。
描述属性的数据就可以理解为元数据,如属性的类型、方法属性的返回值等。
Reflect API
Reflect 是 JavaScript 的一个 API,见 MDN。
它封装了一系列对对象底层静态操作方法,重新实现了类似 Object.defineProperty
、Object.apply
等函数。
为什么需要Reflect
理解了上面反射的概念后,有人不禁有些疑惑。运行时改变数据?这对JavaScript 来说不是很正常的事情吗? 毕竟JavaScript 本身就是 runtime 的语言啊,像:
1 | const a = document.querySelectorAll('a') |
这不就是一个标准的反射吗? 除了有点难记之外,好像也没啥不对的地方。
看一下 Reflect 的写法:
1 | const a = document.querySelectorAll('a') |
嚯~,好像规整了很多。
Reflect 存在的意义就是规整反射API,将这些API 放到一个对象中统一管理。
现阶段Reflect 实现的API
Reflect.apply()
Reflect.construct()
Reflect.defineProperty()
Reflect.deleteProperty()
Reflect.get()
Reflect.getOwnPropertyDescriptor()
Reflect.getPrototypeOf()
Reflect.has()
Reflect.isExtensible()
Reflect.ownKeys()
Reflect.preventExtensions()
Reflect.set()
Reflect.setPrototypeOf()
很眼熟吧… 很多都是原有方法的重新实现。
Reflect Metadata
Reflect API 对元数据的操作并不完善,因此 ES7 的规范额外提案了一些对 MetaData 操作。
不过目前浏览器兼容并不好, 我们可以通过 reflect-metadata 库来做 polyfill。
1 | npm i reflect-metadata -s |
reflect-metadata
并没有导出任何模块, 所以直接在程序入口引用即可。
1 | import 'reflect-metadata'; |
获取预设元信息
Reflect Matedata 默认预设了几个 metadataKey
,分别用于获取目标的类型、接受的参数类型、返回的结果类型。
- design:type
- design:paramtypes
- design:returntype
1 | function Prop(options: { default: string }) { |
快速添加元数据
Reflect MetaData 是可以直接作为装饰器使用的,可以快捷的添加元数据。
1 | metadata('custom:name', 'a custom class') . |
注意:
自属性上获取元数据的时候,第二个参数需要是一个实例,而非是原始 class 本身。
自定义元数据
除了上面讲 Reflect Metadata 当做装饰器的用法外, Reflect 还暴露了一些控制 metadata 的方法.
如通过 defineMetadata 在自定义装饰器内部去操作 metadata
1 | Reflect.defineMetadata("defaultValue", 'custom:name', target, key); |
业务用途
元数据的操作略微有点偏底层,一般常用在框架级的底层设计中。
如深度Angular 的依赖注入、控制反转;Vue Class Template 的模型注册、属性收集等。
我们来尝试设计一个简单的 Vue @Props 装饰器,来达到设置 prop 默认值的效果:
1 | // APP 启动器 |
喏 👆🏻。。 默认值已经添加进去了,同样的道理,我们可以通过 metadata 来标记元素,来达到收集、处理的目的。
常见问题
Reflect.getMetadata 获取到是 undefined
需要在 tsconfig.json 中开启配置
1
2"experimentalDecorators": true,
"emitDecoratorMetadata": true,