JavaScript Reflect Metadata实现详解


Posted in Javascript onDecember 12, 2019

引言

在 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,简单来说,你可以通过装饰器来给类添加一些自定义的信息。然后通过反射将这些信息提取出来。当然你也可以通过反射来添加这些信息。 就像是下面这个例子所示。

@Reflect.metadata('name', 'A')
class A {
 @Reflect.metadata('hello', 'world')
 public hello(): string {
  return 'hello world'
 }
}

Reflect.getMetadata('name', A) // 'A'
Reflect.getMetadata('hello', new A()) // '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 呢?

首先,你需要安装 reflect-metadata polyfill,然后引入之后就可以看到在 Reflect 对象下面有很多关于 Metadata 的函数了。因为这个还没有进入正式的协议,所以需要安装垫片使用。

啥,Reflect 是啥,一个全局变量而已。

你不需要担心这个垫片的质量,因为连 Angular 都在使用呢,你怕啥。

之后你就可以安装我上面写的示例,在 TypeScript 当中去跑了。

类/属性/方法 装饰器

看这个例子。

@Reflect.metadata('name', 'A')
class A {
 @Reflect.metadata('name', 'hello')
 hello() {}
}

const objs = [A, new A, A.prototype]
const res = objs.map(obj => [
 Reflect.getMetadata('name', obj),
 Reflect.getMetadata('name', obj, 'hello'),
 Reflect.getOwnMetadata('name', obj),
 Reflect.getOwnMetadata('name', obj ,'hello')
])
// 大家猜测一下 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 {
 @Reflect.metadata('name', 'hello')
 hello() {}
}

const t1 = new A()
const t2 = new A()
Reflect.defineMetadata('otherName', 'world', t2, 'hello')
Reflect.getMetadata('name', t1, 'hello') // 'hello'
Reflect.getMetadata('name', t2, 'hello') // 'hello'
Reflect.getMetadata('otherName', t2, 'hello') // 'world'

Reflect.getOwnMetadata('name', t2, 'hello') // undefined
Reflect.getOwnMetadata('otherName', t2, 'hello') // 'world'

用途

其实所有的用途都是一个目的,给对象添加额外的信息,但是不影响对象的结构。这一点很重要,当你给对象添加了一个原信息的时候,对象是不会有任何的变化的,不会多 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 当中。直到我看了源码才发现并不是这样。请看例子

@Reflect.metadata('name', 'A')
class A {}

Object.getOwnPropertySymbols(A) // []

哈哈,并没有所谓的 Symbol,那么这些元数据都存在在哪里呢?

其实是内部的一个 WeakMap 中。他正是利用了 WeakMap 不增加引用计数的特点,将对象作为 Key,元数据集合作为 Value,存到 WeakMap 中去。

如果你认真探寻的话,你会发现其内部的数据结构其实是这样的

WeakMap<any, Map<any, Map<any, any>>>

是不是超级绕,但是我们从调用的角度来思考,这就一点都不绕了。

weakMap.get(o).get(p).get(k)

先根据对象获取,然后在根据属性,最后根据元数据的 Key 获取最终要的数据。

End

因为 Reflect Metadata 实在是比较简单,这里就不多讲解了。更多内容请查看Spec

题外话

其实看了源码之后还是挺惊讶的,按照一般的套路,很多 polyfill 会让你提供一些前置的 polyfill 之后,当前的 polyfill 才能使用。但是 reflect-metadata 竟然内部自己实现了很多的 polyfill 和算法。比如 Map, Set, WeakMap, UUID。最惊讶的莫过于 WeakMap 了。不是很仔细的阅读了一下,好像还是会增加引用计数。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JavaScript 上万关键字瞬间匹配实现代码
Jul 07 Javascript
引入autocomplete组件时JS报未结束字符串常量错误
Mar 19 Javascript
jQuery的bind()方法使用详解
Jul 15 Javascript
jquery自定义右键菜单、全选、不连续选择
Mar 01 Javascript
Bootstrap表格和栅格分页实例详解
May 20 Javascript
js 动态给元素添加、移除事件的实现方法
Jul 19 Javascript
详解Angular开发中的登陆与身份验证
Jul 27 Javascript
深入浅出ES6新特性之函数默认参数和箭头函数
Aug 01 Javascript
基于百度地图实现产品销售的单位位置查看功能设计与实现
Oct 21 Javascript
微信小程序 中wx.chooseAddress(OBJECT)实例详解
Mar 31 Javascript
iView框架问题整理小结
Oct 16 Javascript
vue多级复杂列表展开/折叠及全选/分组全选实现
Nov 05 Javascript
JS动态显示倒计时效果
Dec 12 #Javascript
如何用vue-cli3脚手架搭建一个基于ts的基础脚手架的方法
Dec 12 #Javascript
js实现倒计时秒杀效果
Mar 25 #Javascript
vue el-table实现自定义表头
Dec 11 #Javascript
Vue如何获取数据列表展示
Dec 11 #Javascript
vue el-table实现行内编辑功能
Dec 11 #Javascript
Vue.js实现可编辑的表格
Dec 11 #Javascript
You might like
Yii2框架视图(View)操作及Layout的使用方法分析
2019/05/27 PHP
PHP 实现文件压缩解压操作的方法
2019/06/14 PHP
火狐4、谷歌12不支持Jquery Validator的解决方法分享
2011/06/20 Javascript
jQuery实现类似淘宝购物车全选状态示例
2013/06/26 Javascript
JavaScript作用域与作用域链深入解析
2013/12/06 Javascript
jquery 提示信息显示后自动消失的具体实现
2013/12/18 Javascript
详解JavaScript正则表达式中的global属性的使用
2015/06/16 Javascript
JavaScript获取图片像素颜色并转换为box-shadow显示
2016/03/11 Javascript
关于jquery中动态增加select,事件无效的快速解决方法
2016/08/29 Javascript
纯JS实现表单验证实例
2016/12/24 Javascript
vue路由懒加载的实现方法
2018/03/12 Javascript
用Node写一条配置环境的指令
2019/11/14 Javascript
antd多选下拉框一行展示的实现方式
2020/10/31 Javascript
[52:00]2018DOTA2亚洲邀请赛 4.1 小组赛 A组加赛 LGD vs Optic
2018/04/02 DOTA
python 垃圾收集机制的实例详解
2017/08/20 Python
Python浅复制中对象生存周期实例分析
2018/04/02 Python
Python图像处理之gif动态图的解析与合成操作详解
2018/12/30 Python
Django命名URL和反向解析URL实现解析
2019/08/09 Python
Python爬虫:将headers请求头字符串转为字典的方法
2019/08/21 Python
django-rest-swagger对API接口注释的方法
2019/08/29 Python
python基于三阶贝塞尔曲线的数据平滑算法
2019/12/27 Python
Python基于yield遍历多个可迭代对象
2020/03/12 Python
Numpy 理解ndarray对象的示例代码
2020/04/03 Python
TensorFlow低版本代码自动升级为1.0版本
2021/02/20 Python
发现两个有趣的CSS3动画效果
2013/08/14 HTML / CSS
基于IE10/HTML5 开发
2013/04/22 HTML / CSS
《听鱼说话》教学反思
2014/02/15 职场文书
2014年十一国庆节爱国演讲稿
2014/09/23 职场文书
员工工作及收入证明
2014/10/28 职场文书
2015年仓管员工作总结
2015/04/21 职场文书
公司辞职信模板
2015/05/13 职场文书
教你用Python matplotlib库制作简单的动画
2021/06/11 Python
Nginx配置文件详解以及优化建议指南
2021/09/15 Servers
MongoDB支持的数据类型
2022/04/11 MongoDB
vue3语法糖内的defineProps及defineEmits
2022/04/14 Vue.js
Django框架中视图的用法
2022/06/10 Python