一篇文章让你搞懂Reflect和Reflect metadata(JavaScript)

引言

Javascript 里经常看到 Reflect 的使用,但一直搞不明白他和 Object 的区别, 加上在工作中也没有经常用到, 一直处于懵懵懂懂的状态。所以下定决心, 用这篇文章彻彻底底把它搞明白

本文主要涵盖以下4个板块:

  • Reflect

    • 什么是 Reflect APIs(Reflect与Object的区别)
    • 简单的例子: Object vs Reflect
    • Reflect简单总结
  • Reflect Metadata(npm package)
    • 出现背景
    • 如何安装
    • Reflect Metadata 的用法
  • 结语
  • 参考文献

Reflect

什么是 Reflect APIs

Reflect是一个内建的对象,用来提供方法去拦截JavaScript的操作。Reflect的所有属性和方法都是静态的。

这个解释是相当抽象的, 用人话来讲就是: Reflect提供了一系列静态方法来对JS对象进行操作

在ES6之前, Javascript一直没有统一的namespace来管理对其他object的操作 比如我们可能使用Object.keys(car)获取car这个对象的所有属性
但我们会用property in car这种形式判断某个属性是否存在于car. 相信你能看得出来, 这导致了代码上的割裂, 我们为什么不能有一个新的一系列APIs, 支持所有这些对于对象的操作呢?所以这个时候 Javascript推出了Reflect, 你可以用Reflect.has(car, property)去判断某个属性存在于car 同时可以用 Reflect.ownKeys(car)去获取car的所有属性. 并且所有都是通过调用Reflect里的静态function, 是不是方便和统一很多呢?

以下是Reflect下面的13个static functions以及其对应的JS其他方式的实现:

  • Reflect.apply(target, thisArgument, argumentsList)

    • 类似于Function.prototype.apply()
  • Reflect.construct(target, argumentsList[, newTarget])
    • 类似于new target(...argumentsList)
  • Reflect.defineProperty(target, propertyKey, attributes)
    • 类似于Object.defineProperty()
  • Reflect.deleteProperty(target, propertyKey)
    • 类似于delete target[propertyKey]
  • Reflect.get(target, propertyKey[, receiver])
    • 类似于target[propertyKey]
  • Reflect.getOwnPropertyDescriptor(target, propertyKey)
    • 类似于Object.getOwnPropertyDescriptor()
  • Reflect.getPrototypeOf(target)
    • 类似于Object.getPrototypeOf()
  • Reflect.has(target, propertyKey)
    • 类似于property in target
  • Reflect.isExtensible(target)
    • 类似于Object.isExtensible(target)
  • Reflect.ownKeys(target)
    • 类似于Object.keys(target)
  • Reflect.preventExtensions(target)
    • 类似于Object.preventExtensions(target)
  • Reflect.set(target, propertyKey, value[, receiver])
    • 类似于target[property] = value
  • Reflect.setPrototypeOf(target, prototype)
    • 类似于Object.setPrototypeOf(target, prototype)

通过上面的function, 你可以发现, Reflect里面的所有function在目前JS都能找到对应的实现(Object, Function),而现在你不需要去记忆那些复杂的语法,全部都可以用Reflect搞定。JS官方也建议developer能够转而开始使用Reflect 未来关于对象操作的新特性都会添加到Reflect而不是Object。但是为了backwards-compatibility, Object里面已经存在的function也不会被移除

延伸阅读: Comparing Reflect and Object methods

简单的例子: Object vs Reflect

如果去给car对象定义一个新的property, 在Object里面我们需要用到try...catch去捕捉错误
Reflect.defineProperty()则会直接return true or false代表成功与否

// Object实现
try {Object.defineProperty(car, name, desc);// property defined successfully
} catch (e) {// possible failure (and might accidentally catch the wrong exception)
}// Reflect实现
if (Reflect.defineProperty(car, name, desc)) {// success
} else {// failure
}

Reflect简单总结

Reflect提供了一系列的静态方法(static functions)来操作其他Javascript对象, 其优势在于:

  1. 函数化所有对象操作
  2. 更丰富的返回值
  3. 更统一的命名
  4. 对于function apply更可靠的的支持 - Reflect.apply(f, obj, args)
    • const car = () => {}
      car.apply = (v) => {console.log('car.apply() runs with', v)}car.apply(void 0, [1]); // car本身的apply()会覆盖Function.prototype.apply
      Reflect.apply(car.apply, car, [1]) // Reflect则可以避免这个问题
      
  5. 更好的控制this-binding
    • Reflect.set()
    • Reflect.get()
  6. Proxy里的操作一一对应

延伸阅读:

  • Harmony-reflect
  • Proxy

Reflect Metadata

Reflect metadata 主要用于在代码声明时添加/读取某个对象的元数据(metadata)

出现背景

Reflect Metadata 是一个广泛使用的第三方npm package,同时该 package 的作者Ron Buckton也是Typescript的核心开发者(core contributor) 他于 2015 年提交了将 metadata 纳入 Typescript 官方的提案(ES7),但目前为止他手上还有太多其他工作没有完成, 所以并没有提上议程。感兴趣的朋友可以去以下链接查看具体信息:

  • Metadata Proposal - ECMAScript: https://rbuckton.github.io/reflect-metadata/
  • Ron Buckton在 github 的回应: https://github.com/rbuckton/reflect-metadata/issues/9

如何安装

在 TypeScript 使用, 你只需要:

  • npm i reflect-metadata --save
  • tsconfig.json 里配置 emitDecoratorMetadata 选项
  • 在使用的文件引入import 'reflect-metadata' 或者在index.d.ts全局引入

Reflect Metadata 的用法

Reflect Metadata 的 API 可以用作给Object及其属性添加元数据:

const car = {brand: 'BMW',model: 'X6 2012',price: 99999,getMaxSpeed() {console.log(`Max speed is 200km/h`);}
}// 添加一个叫`desc`的元数据到car这个对象
Reflect.defineMetadata('desc', 'This is a good car', car);
// 添加一个叫`desc`的元数据到car的price上(这里的price可以是其他值或car不存在的属性)
Reflect.defineMetadata('desc', 'This is so cheap', car, 'price');
// 添加一个叫`desc`的元数据到car的model上(这里的model可以是其他值或car不存在的属性)
Reflect.defineMetadata('note', 'This model is too old', car, 'model');// 检查metadata是否存在
console.log(Reflect.hasMetadata('desc', car)); // 输出: true
console.log(Reflect.hasMetadata('desc', car, 'price')); // 输出: true
console.log(Reflect.hasMetadata('note', car, 'model')); // 输出: true
console.log(Reflect.hasMetadata('desc', car, 'brand')); // 输出: false// 获取元数据
console.log(Reflect.getMetadata('desc', car)); // 输出: 'This is a good car'
console.log(Reflect.getMetadata('desc', car, 'price')); // 输出: 'This is so cheap'
console.log(Reflect.getMetadata('note', car, 'model')); // 输出: 'This model is too old'
console.log(Reflect.getMetadata('desc', car, 'brand')); // 输出: undefined// 获取对象上所有元数据的keys
console.log(Reflect.getMetadataKeys(car)) // 输出: ['desc']
console.log(Reflect.getMetadataKeys(car, 'price')) // 输出: ['desc']
console.log(Reflect.getMetadataKeys(car, 'model')) // 输出: ['note']
console.log(Reflect.getMetadataKeys(car, 'brand')) // 输出: []

也可以用做装饰器(decorator), 作用于类(class)或者类的属性。
我们这里写一个可以在不用new instance的情况下, 计数Class有多少methods和properties的装饰器:


// 把metadata都定义到这个全局变量
const globalMeta = Object.create(null);const propertyCollector = (target: Object,propertyKey: string | symbol, descriptor?: any) => {// 把property的名字都放进一个叫properties的arrayconst properties = Reflect.getMetadata('properties', globalMeta);if (properties) {Reflect.defineMetadata('properties', [...properties, propertyKey], globalMeta);} else {Reflect.defineMetadata('properties', [propertyKey], globalMeta);}
}const methodCollector = (target: Object,propertyKey: string | symbol, descriptor: PropertyDescriptor) => {// 把methods的名字都放进一个叫methods的arrayconst methodss = Reflect.getMetadata('methods', globalMeta);if (methodss) {Reflect.defineMetadata('methods', [...methodss, propertyKey], globalMeta);} else {Reflect.defineMetadata('methods', [propertyKey], globalMeta);}
}const classCollector = (constructor: Function) => {// 把class的名字存到globalMeta的classNameReflect.defineMetadata('className', constructor.name, globalMeta);
}@classCollector
class Car {@propertyCollectorprivate speed = 0;@propertyCollectorprivate brand = 'BMW';@propertyCollectorprivate model = 'X6 2012';@methodCollectorrun() {this.speed = 100;console.log(`The car is running at 100km/h`)}@methodCollectorstop() {this.speed = 0;console.log(`The car stopped`)}
}const className = Reflect.getMetadata('className', globalMeta);
const properties = Reflect.getMetadata('properties', globalMeta);
const methods = Reflect.getMetadata('methods', globalMeta);console.log(
`Class [${className}] has ${properties.length} properties: ${properties.join(', ')}\t
Class [${className}] has ${methods.length} methods: ${methods.join(', ')}`,
);// 输出
// Class [Car] has 3 properties: speed, brand, model
// Class [Car] has 2 methods: run, stop

结语

希望通过这篇文章, 你能对Reflect有一个新的理解, 未来也将会写当ReflectProxy联动在一起的美妙用法(开启JS里meta programming的新篇章)

参考文献

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
  2. https://blog.greenroots.info/javascript-why-reflect-apis
  3. https://medium.com/jspoint/introduction-to-reflect-metadata-package-and-its-ecmascript-proposal-8798405d7d88

一篇文章让你搞懂Reflect和Reflect metadata(JavaScript)相关推荐

  1. 一篇文章带你搞懂网络层(网际层)-- 地址篇

    网络层(Network Layer)是OSI模型中的第三层(TCP/IP模型中的网际层),提供路由和寻址的功能,使两终端系统能够互连且决定最佳路径,并具有一定的拥塞控制和流量控制的能力.相当于发送邮件 ...

  2. 一篇文章带你搞懂 DEX 文件的结构

    From:https://blog.csdn.net/sinat_18268881/article/details/55832757 Dex文件格式详解:https://www.jianshu.com ...

  3. 一篇文章带你搞懂微信小程序的开发过程

    点击上方"前端进阶学习交流",进行关注 回复"前端"即可获赠前端相关学习资料 今 日 鸡 汤 只解沙场为国死,何须马革裹尸还. 大家好,我进阶学习者. 前言 小 ...

  4. 谷歌SEO很复杂?一篇文章带你搞懂它(外贸人必读)

    这篇文章是对谷歌SEO流程的一个梳理,此文会用通俗易懂的语言告诉你做Google SEO必须知道的常识.建议和谷歌优化的方法思路. 任何关于谷歌SEO的疑问,可到此文⬇️留言,免费咨询: Google ...

  5. 一篇文章带你搞懂前端面试技巧及进阶路线

    大家好,我是若川.最近有很多朋友给我后台留言: 自己投了不少简历,但是收到的面试邀请却特别少: 好不容易收到了大厂的面试邀请,但由于对面试流程不清楚,准备的特别不充分,结果也挂了: 对于面试官的问题, ...

  6. 区块链应用 | 一篇文章让你搞懂区块链,非标通证应用将首先落地

    近期,区块链领域火热,越来越多的力量正在加入这一市场,就在昨日美图还发布了区块链白皮书. 不过,区块链市场喧嚣,陷入各种炒币的喧嚣.就在日前,CSDN副总裁孟岩以<区块链关键技术>的口述形 ...

  7. 一篇文章带你搞懂慢SQL以及优化的策略

    文章目录 一.什么是慢SQL ? 二.为什么要对慢SQL进行优化? 三.数据库性能 1. 最大数据量 2. 最大并发数 3. 查询耗时0.5秒 4. 具体实施 四.数据库表的设计 1. 数据类型 2. ...

  8. 一篇文章带你搞懂数据链路层

    数据链路层,简称链路层.两个主机之间的数据传输,总是在一段一段的链路上面传送的,也就是说,在两个相邻结点之间(主机与路由器之间 或者 两个路由器之间)传送数据是直接传送的(点对点).这时,就需要使用专 ...

  9. 一篇文章让你搞懂原型和原型链

    每一个构造函数在被创建的时候,会自动创建一个相应的对象,这个对象就是原型对象,这个函数有一个指向该对象的指针.举个例子: 下面创建了一个函数person. function person () { } ...

  10. 一篇文章带你搞懂JS对象的自我销毁

    在日常的JS组件开发中,往往会有一些较为复杂的DOM操作及事件监听,尤其是在处理UI层面的widgets时候更为明显.常常会花很多精力在对象的init上,而当组件需要被移除时则仅仅是把所在DOM草草的 ...

最新文章

  1. 简单借还书管理系统c语言,急求程序!!!简单图书馆借/还书管理子系统
  2. 推荐一个比较好的SQL工具——SQL Prompt
  3. 搜索专题【2010】四2.过河问题
  4. tablewidget 行数自适应_控制|基于自适应遗传算法的增程式电动汽车能量管理策略优化...
  5. ajax会占用服务器端内存吗,javascript - 为什么不允许在jquery datatable服务器端处理ajax成功使用? - 堆栈内存溢出...
  6. 如何使用 Python 将图片变为字符的模样
  7. MYSQL互为主从同步(Windows)
  8. 数据结构 2-3-1 线性表的单链表实现
  9. Apache Kafka简介与安装(一)
  10. hibernate java.lang.NoSuchMethodError: javax.persistence.OneToMany.orphanRemoval()Z
  11. 简单实用的php爬虫系统
  12. 那些年用过的机械键盘--游戏人生键盘风云#入坑keychron#
  13. 三八定律时间管理思想
  14. 猫咪APP 服务器不稳定,这下知道猫咪为啥经常情绪不稳定了不?
  15. 链路追踪Zipkin
  16. 中国文学通史之各个阶段介绍
  17. LayUI 之动态树形菜单
  18. 计算机桌面底下显示条,详细教您电脑屏幕出现条纹怎么办
  19. python财务编程_Python笔记 财务小白的 day4 python编程基础(2)
  20. 浅析TSINGSEE智能视频分析网关的AI识别技术及应用场景

热门文章

  1. java画图曲线_绘图(直线和曲线)
  2. 股票python量化交易026-数据回测的概念以及现有框架
  3. mac电脑怎么清空浏览器缓存?Chrome浏览器 for Mac清理缓存的方法
  4. 桌面快捷方式图标不能删除的原因以及处理方法
  5. 2019配电安规电子版_2018年配电安规.docx
  6. matlab计算方阵对应特征值的特征向量
  7. 程序验证(六):纳尔逊-欧朋算法(Nelson-Oppen Procedure)
  8. matlab中双引号_在matlab中单引号和双引号各有什么意义?为什么有的函数参数要加单引号,有的要加双引号?...
  9. dorado java_dorado事件
  10. oracle12c不使用cdb模式,Oracle 12c 使用Non-CDB来创建PDB