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 相关文章推荐
JS 动态加载脚本的4种方法
May 05 Javascript
关于Aptana Studio生成自动备份文件的解决办法
Dec 23 Javascript
用JS控制回车事件的代码
Feb 20 Javascript
JavaScript高级程序设计阅读笔记(十六) javascript检测浏览器和操作系统-detect.js
Aug 14 Javascript
JavaScript在网页中画圆的函数arc使用方法
Nov 13 Javascript
jQuery中deferred对象使用方法详解
Jul 14 Javascript
javascript 数组的正态分布排序的问题
Jul 31 Javascript
BootStrap glyphicon图标无法显示的解决方法
Sep 06 Javascript
通过AngularJS实现图片上传及缩略图展示示例
Jan 03 Javascript
Nuxt.js实战详解
Jan 18 Javascript
element 结合vue 在表单验证时有值却提示错误的解决办法
Jan 22 Javascript
vue实现点击隐藏与显示实例分享
Feb 13 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
php中in_array函数用法分析
2014/11/15 PHP
PHP借助phpmailer发送邮件
2015/05/11 PHP
php计划任务之验证是否有多个进程调用同一个job的方法
2015/12/07 PHP
php修改数组键名的方法示例
2017/04/15 PHP
关于PHP定时发送服务的解决办法
2017/04/23 PHP
搜索附近的人PHP实现代码
2018/02/11 PHP
JavaScript 实现模态对话框 源代码大全
2009/05/02 Javascript
JQuery 拾色器插件发布-jquery.icolor.js
2010/10/20 Javascript
jquery怎样实现ajax联动框(一)
2013/03/08 Javascript
Javascript与jQuery方法的隐藏与显示
2015/01/19 Javascript
JavaScript判断浏览器类型的方法
2015/02/10 Javascript
jquery+php随机生成红包金额数量代码分享
2015/08/27 Javascript
JavaScript基于自定义函数判断变量类型的实现方法
2016/11/23 Javascript
jQuery EasyUI的TreeGrid查询功能实现方法
2017/08/08 jQuery
vue 2.0项目中如何引入element-ui详解
2017/09/06 Javascript
vue中动态绑定表单元素的属性方法
2018/02/23 Javascript
Vue循环组件加validate多表单验证的实例
2018/09/18 Javascript
JS异步处理的进化史深入讲解
2019/08/25 Javascript
解决layui的form里的元素进行动态生成,验证失效的问题
2019/09/14 Javascript
原生JS实现无缝轮播图片
2020/06/24 Javascript
解决ant design vue中树形控件defaultExpandAll设置无效的问题
2020/10/26 Javascript
python不带重复的全排列代码
2013/08/13 Python
Python中使用logging模块打印log日志详解
2015/04/05 Python
python实现清屏的方法
2015/04/30 Python
python实现的系统实用log类实例
2015/06/30 Python
python生成excel的实例代码
2017/11/08 Python
Python2.X/Python3.X中urllib库区别讲解
2017/12/19 Python
Python实现手写一个类似django的web框架示例
2018/07/20 Python
Python面向对象程序设计类的封装与继承用法示例
2019/04/12 Python
wxpython绘制圆角窗体
2019/11/18 Python
TensorFlow绘制loss/accuracy曲线的实例
2020/01/21 Python
新西兰优惠网站:Treat Me
2019/07/04 全球购物
采购人员的个人自我评价
2014/01/16 职场文书
新闻发布会策划方案
2014/06/12 职场文书
怀孕辞职信怎么写
2015/02/28 职场文书
这样写python注释让代码更加的优雅
2021/06/02 Python