原文: 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
一下」看起來時髦,其實仍是不用爲妙。
--------------------------------------