专栏首页云前端[译]深入理解 ES6 中的反射

[译]深入理解 ES6 中的反射

原文: https://ponyfoo.com/articles/es6-reflection-in-depth

为什么需要反射 ?

很多强类型语言长期以来都有其反射(Reflection)API(如 Python 或 C#),而 JavaScript 作为一种动态语言,则几乎用不着反射。在 ES6 特性里引入的少量扩展之处中,允许开发者用Proxy访问此前的一些语言内部行为就算得上一项。

你可能会反驳,尽管在规范和社区中没有明确那么称呼过,但 JS 在 ES5 中已经有反射特性了。诸如 Array.isArray, Object.getOwnPropertyDescriptor, 甚至 Object.keys 这些,在其他语言中都是典型的被列为反射的方法。而内置的 Reflect 对象则更进了一步,将这些方法归纳在一起。这很有用,是吧?为什么要用超类 Object的静态反射方法(如getOwnPropertyDescriptorcreate)呢?毕竟Object表示一个基本原型更合适,而不是成为反射方法的仓库。用一个专有接口暴露更多反射方法更有意义。

Reflect 对象

Math一样, Reflect 也是不能用 newcall 调用的静态对象,所有方法也是静态的。ES6 Proxy 中的陷阱(traps) API 和 Reflect 中的方法一一对应。

JS 中的反射 API 有一些值得研究的特性。

用 Reflect 返回值 vs 通过 Object 反射

和 Object 中等价的 Reflect 反射方法同时也提供了更有意义的返回值。比如,Reflect.defineProperty方法返回一个布尔值,表示属性是否被成功定义;而对应的Object.defineProperty则返回其首个参数中接收到的对象 -- 这并不是很有用。

举例来说,以下代码演示了Object.defineProperty如何工作:

try {
 Object.defineProperty(target, 'foo', { value: 'bar' })
 // yay!
} catch (e) {
 // oops.
}

相反,用Reflect.defineProperty就会感觉自然得多:

var yay = Reflect.defineProperty(target, 'foo', { value: 'bar' })
if (yay) {
 // yay!
} else {
 // oops.
}

这种方法免去了使用try/catch 代码块,并使得代码更易维护。

作为一等公民的关键字操作

对于之前只能用关键字做的事情,一些反射方法提供了程序化的替代方案。比如,Reflect.deleteProperty(target, key) 等价于 delete target[key] 表达式。在 ES6 之前,如果想要调用一个方法达到删除的效果,也只能创建一个专用的工具方法包裹住delete关键字。

var target = { foo: 'bar', baz: 'wat' }
delete target.foo
console.log(target)
// <- { baz: 'wat' }

现在用 ES6 中的 Reflect.deleteProperty

var target = { foo: 'bar', baz: 'wat' }
Reflect.deleteProperty(target, 'foo')
console.log(target)
// <- { baz: 'wat' }

deleteProperty一样, 还有一些其他的方法,提供了更多便利。

更简单的向 new 中传入任意数量的参数列表

在 ES5 里,有个难办的事:如何创建一个 new Foo 并传递任意数量的参数呢?没办法直接实现,而不管怎么弄都会相当麻烦。你不得不创建一个中介对象,借助其将获得的参数变成一个数组;然后对原本的目标对象的构造函数应用这个参数数组,并将结果在中介对象的构造函数中返回。很简单,是不是?- 你说 no 是几个意思?

var proto = Dominus.prototype
Applied.prototype = proto
function Applied (args) {
 return Dominus.apply(this, args)
}
function apply (a) {
 return new Applied(a)
}

使用 apply 实在是简单?,谢天谢地。

apply(['.foo', '.bar'])
apply.call(null, '.foo', '.bar')

但这难道不是很愚蠢吗?谁会那样做呢?事实是在 ES5 中,每个人都有个合理的理由去这样用。好在 ES6 中解决这个问题就好多了,其中一个方法是使用 spread 操作符:

new Dominus(...args)

另一种方式是借助 Reflect

Reflect.construct(Dominus, args)

这两种方式可都比 dominus 例子中的简单多了。

在函数上apply的正确方式

在 ES5 中如果想调用一个任意数量参数的方法,可以使用.apply传递一个this上下文以及需要的参数。

fn.apply(ctx, [1, 2, 3])

如果担心fn可能会调用到其本身被覆盖掉的apply方法,可以靠一种安全但比较冗长的替代方法:

Function.prototype.apply.call(fn, ctx, [1, 2, 3])

而 ES6 中虽然可以用 spread 语法替代.apply解决任意数量参数的问题:

fn(...[1, 2, 3])

上述办法却没法在需要定义this上下文时发挥作用;此时若不想用Function.prototype的冗长方式的话,就要用Reflect帮忙了:

Reflect.apply(fn, ctx, args)

Proxy陷阱中的默认行为

Reflect API 方法天生一对的,自然是作为Proxy陷阱中的默认行为。

前面已经谈到过Proxy 中的陷阱(traps) API 和 Reflect 中的方法一一对应,但并未触及为何它们的接口如此匹配。可以这样解释:因为它们的参数和返回值都匹配。在代码中,这意味着可以在proxy handlers中像下面这样取得get陷阱的默认行为:

var handler = {
 get () {
   return Reflect.get(...arguments)
 }
}
var target = { a: 'b' }
var proxy = new Proxy(target, handler)
console.log(proxy.a)
// <- 'b'

实际上还可以更简单一点;当然了(只是示例而已),如果真写成这样也就没必要了:

var handler = {
 get: Reflect.get
}

proxy handlers 中设置陷阱的重要功能是,可以插入一些诸如用抛出错误结束或在控制台打印日志语句等自定义的功能,并在默认情形下像这样返回:

return Reflect[trapName](...arguments)

最后, 还有 __proto__

虽然 ES6 标准把__proto__ 作为一个陈旧(legacy)属性纳入其中,但还是强烈不建议直接使用,而是应该用Object.setPrototypeOfObject.getPrototypeOf代替,相对应的是Reflect中两个同名方法;可以将这两个方法视为__proto__的 getter/setter,且不会有浏览器兼容性问题。

话说回来,“哪儿哪儿都Object.setPrototypeOf一下”看起来时髦,其实还是不用为妙。

本文分享自微信公众号 - 云前端(fewelife),作者:lua

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-11-23

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何获得一个 iPhone X 一样炫酷的罗马计数法出生年份

    人们十年如一日(不是说杜X斯的广告)的又迎来了一年一度的苹果发布会,作为史上被提前爆料最多的一届,还是在产品的命名上给了人们一些惊喜或是纳闷儿之处 --- 传说...

    江米小枣
  • 重拾CSS规范之z-index

    z-index 会为当前的盒创建层叠上下文,也会指定该盒在父级层叠上下文中的层叠等级。

    江米小枣
  • [译] 对比 React Hooks 和 Vue Composition API

    原文:https://dev.to/voluntadpear/comparing-react-hooks-with-vue-composition-api-4b...

    江米小枣
  • Vue 3 高阶指南之 Reflect

    Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象...

    公众号---人生代码
  • 穿越十年后看互联网+:服饰行业的金矿在哪里?

    现在市场上炒得火热的可穿戴设备未来出路在何方?做可穿戴的创业者应该关注哪些机会?传统服饰厂商又到底如何借助互联网进行转型?本文以智能童鞋为例,用故事的形式,提前...

    华章科技
  • 阔别多年 Slax 9.2.1 发布:基于 Debian “Stretch”

    消失了很长时间的 Slax Linux 终于发布了更新。昨日,Slax Linux 的作者 Tomas Matejice 发布了 Slax Linux 9.2....

    Debian中国
  • 《股民的自我修养》读书笔记三:关于降维攻击

    《三体》这部书里描述太阳系是这样毁灭的:外星人与地球人大战,使出了最终极的武器,先改变自己的维度,把自己从三维生物降低到二维生物,然后用一种二向箔...

    凌帅出口
  • ERP视角下制造业成本管理问题探究

    本文结合ERP理论,对制造业成本管理中出现的问题进行了分析,并论述一些解决对策和ERP系统对企业成本管理的作用,希望能对我国制造业企业的发展有所帮助。

    SAP斯凯普斯 Qasim
  • 2020QEcon大会完美收官,腾讯WeTest携四大质量专家畅谈“质效合一”

    ? 导语   9月5日,2020年首届全球软件质量&效能大会(下文称QECon)圆满落幕。大会现场超过60多位品质先行者同堂共聚,带来近百个优质重磅的议题分享...

    WeTest质量开放平台团队
  • Less快速入门

    Less快速入门 Web前端学习笔记之——Less快速入门 ---- 什么是Less Less 是一门 CSS 预处理语言,它扩充了 CSS 语言,增加了...

    李郑

扫码关注云+社区

领取腾讯云代金券