原文: ponyfoo.com/articles/es…git
不少强类型语言长期以来都有其反射(Reflection)API(如 Python 或 C#),而 JavaScript 做为一种动态语言,则几乎用不着反射。在 ES6 特性里引入的少许扩展之处中,容许开发者用Proxy
访问此前的一些语言内部行为就算得上一项。es6
你可能会反驳,尽管在规范和社区中没有明确那么称呼过,但 JS 在 ES5 中已经有反射特性了。诸如 Array.isArray
, Object.getOwnPropertyDescriptor
, 甚至 Object.keys
这些,在其余语言中都是典型的被列为反射的方法。而内置的 Reflect
对象则更进了一步,将这些方法概括在一块儿。这颇有用,是吧?为何要用超类 Object
的静态反射方法(如getOwnPropertyDescriptor
或 create
)呢?毕竟Object
表示一个基本原型更合适,而不是成为反射方法的仓库。用一个专有接口暴露更多反射方法更有意义。github
和 Math
同样, Reflect
也是不能用 new
或 call
调用的静态对象,全部方法也是静态的。ES6 Proxy
中的陷阱(traps) API 和 Reflect
中的方法一一对应。数组
JS 中的反射 API 有一些值得研究的特性。浏览器
和 Object 中等价的 Reflect 反射方法同时也提供了更有意义的返回值。好比,Reflect.defineProperty方法返回一个布尔值,表示属性是否被成功定义;而对应的Object.defineProperty
则返回其首个参数中接收到的对象 -- 这并非颇有用。安全
举例来讲,如下代码演示了Object.defineProperty
如何工做:
bash
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
代码块,并使得代码更易维护。app
对于以前只能用关键字作的事情,一些反射方法提供了程序化的替代方案。好比,Reflect.deleteProperty(target, key)
等价于 delete target[key]
表达式。在 ES6 以前,若是想要调用一个方法达到删除的效果,也只能建立一个专用的工具方法包裹住delete
关键字。dom
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
同样, 还有一些其余的方法,提供了更多便利。
在 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
例子中的简单多了。
在 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)
复制代码
和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)
复制代码
虽然 ES6 标准把__proto__
做为一个陈旧(legacy)属性归入其中,但仍是强烈不建议直接使用,而是应该用Object.setPrototypeOf
和 Object.getPrototypeOf
代替,相对应的是Reflect
中两个同名方法;能够将这两个方法视为__proto__
的 getter/setter,且不会有浏览器兼容性问题。
话说回来,“哪儿哪儿都Object.setPrototypeOf
一下”看起来时髦,其实仍是不用为妙。
--------------------------------------