Reflect#
迪士尼版花木蘭有一首歌叫 Reflection,木蘭看著水面的自己,沉思自己未來的選擇,歌詞和畫面就同時包含了 Reflection 這個詞的這兩層含義,即:
- 反射、映出
- 沉思、內省
編程中的 Reflect 翻譯為 “反射” 確實會讓人迷糊,其實這裡應該往 “自省” 的方向靠。Reflect 是 JavaScript 元編程的重要方法之一,元編程可以從更高維度實現你想要的功能,例如用程序調整程序本來的表現、調用一些內部方法、用編程的方式寫代碼。當然,Reflect 並非 JavaScript 的獨占術語,go、java 等語言也有 Reflect 方法。
JavaScript 的 Reflect 有以下方法:
Reflect.apply()
Reflect.construct()
Reflect.defineProperty()
Reflect.deleteProperty()
Reflect.get()
Reflect.getOwnPropertyDescriptor()
Reflect.getPrototypeOf()
Reflect.has()
Reflect.isExtensible()
Reflect.ownKeys()
Reflect.preventExtensions()
Reflect.set()
Reflect.setPrototypeOf()
看到這一堆東西,不得不感慨一句,熟悉,太熟悉了,這裡面不少方法好像都在哪裡用過呀?確實,Reflect
有意把一些掛在 Object
、Function
的方法都放到這裡來了。
// 例如熟悉的 apply
Function.prototype.apply.call(Math.floor, undefined, [1.75])
// 可以寫成
Reflect.apply(Math.floor, undefined, [1.75])
這帶來了使用 Reflect 的第一個明顯的好處,簡化代碼。此外,apply
其實是調用了 JavaScript 引擎的內部方法 [[Call]]
,所以 Reflect 才會歸入元編程的範疇。
再例如,Reflect.ownKeys
承擔了 Object.getOwnPropertyNames
的功能,對對象的屬性進行檢視:
const object1 = {
a: 1,
b: 2,
c: 3,
}
console.log(Reflect.ownKeys(object1))
console.log(Object.getOwnPropertyNames(object1))
// 預期輸出: Array ["a", "b", "c"]
Reflect.deleteProperty()
和 Reflect.has()
這樣的方法則是承擔了一些運算符的功能:
const obj = { x: 1, y: 2 }
Reflect.deleteProperty(obj, 'x') // true
console.log(obj) // { y: 2 }
// deleteProperty 和 delete 的功能就一樣,但是 deleteProperty 會返回操作結果,delete 會靜默操作
// 雖然還是想吐槽一句,delete 操作真的會失敗嗎...
const obj = { x: 1, y: 2 }
delete obj.x
console.log(obj) // { y: 2 }
到了 get
、set
等方法,跟前面一樣,調用了內部方法 [[Get]]
、[[Set]]
:
const object1 = {
x: 1,
y: 2,
}
console.log(Reflect.get(object1, 'x'))
但這就迷惑起來了,明明 object1.x
也是調用內部方法 [[Get]]
,而且更簡單,為什麼要用 Reflect.get
這麼麻煩的方法呢?答案是搭配 Proxy
。
Proxy#
Proxy
很好理解,相信大家也在某些領域(狗頭)常常見到這個詞,在這裡也是那個熟悉的意思,就是代理,Proxy
實例是一個對象,你對這個對象的操作會被其他函數代理。
最基礎的 Proxy
長這樣:
const target = {
message1: 'hello',
message2: 'everyone',
}
const handler = {}
const proxy1 = new Proxy(target, handler)
事實上這樣的 Proxy
什麼也不會發生,一切照常,若是你想要發生點什麼呢?可以從 handler
做文章。
function proxyFactory(traps) {
let target = {}
let handler = {}
for (let i = 0; i < traps.length; i++) {
let trap = traps[i]
handler[trap] = function (...args) {
console.log('trap: ' + trap, args)
return Reflect[trap](...args)
}
}
let test = new Proxy(target, handler)
return test
}
可以看到 proxyFactory
需要傳入 traps
列表,函數運行,把 trap
放置到 handler
,然後 new Proxy(target, handler)
,使用 trap
代理對象操作。
那麼什麼是 trap
呢?說是 “陷阱” 也不錯,其實就是讓一些操作 “陷入” 到這個 “陷阱”,直接運行陷阱函數。
The function that define the behavior for the corresponding object internal method. (This is analogous to the concept of traps in operating systems.)
根據 MDN 定義,trap 是定義對象內部方法表現的函數,這和 Reflect
的功能完全契合,事實上 Reflect
的方法名稱就跟 trap
的名稱完全一致,於是我們可以這樣設定:
let test = proxyFactory(Reflect.ownKeys(Reflect))
這樣得到的 test
對象會在你進行所有內部方法調用時打出日誌,例如:
test.a = 1
// trap: set (4) [{…}, 'a', 1, Proxy]
// trap: getOwnPropertyDescriptor (2) [{…}, 'a']
// trap: defineProperty (3) [{…}, 'a', {…}]
test.a
// trap: get (3) [{…}, 'a', Proxy]
'a' in test
// trap: has (2) [{…}, 'a']
例子中只是把操作和參數打印出來,實際上你可以在 trap
中對對象進行任意處理,這樣一下子編程思路就廣起來了,例如 Vue3 的響應式原理就與 Reflect
和 Proxy
密不可分的。
https://ssshooter.com/2023-01-30-javascript-reflect/
Takeaway#
Reflect
是 JavaScript 元編程的重要對象,它可以調用對象內部方法,對對象進行一些內部操作Reflect
讓Function.prototype.apply.call
、Object.getOwnPropertyNames
等一些函數更直觀、更簡潔Reflect
承擔了in
等運算符的功能,且返回操作結果Reflect
可以跟Proxy
完美搭配,可以任意定義對象操作行為