SSShooter

SSShooter

Write like you're running out of time.

JavaScript 的 Reflect 和 Proxy

Reflect#

迪士尼版花木蘭有一首歌叫 Reflection,木蘭看著水面的自己,沉思自己未來的選擇,歌詞和畫面就同時包含了 Reflection 這個詞的這兩層含義,即:

  • 反射、映出
  • 沉思、內省

編程中的 Reflect 翻譯為 “反射” 確實會讓人迷糊,其實這裡應該往 “自省” 的方向靠。Reflect 是 JavaScript 元編程的重要方法之一,元編程可以從更高維度實現你想要的功能,例如用程序調整程序本來的表現、調用一些內部方法、用編程的方式寫代碼。當然,Reflect 並非 JavaScript 的獨占術語,gojava 等語言也有 Reflect 方法。

JavaScript 的 Reflect 有以下方法:

看到這一堆東西,不得不感慨一句,熟悉,太熟悉了,這裡面不少方法好像都在哪裡用過呀?確實,Reflect 有意把一些掛在 ObjectFunction 的方法都放到這裡來了。

// 例如熟悉的 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 }

到了 getset 等方法,跟前面一樣,調用了內部方法 [[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 的響應式原理就與 ReflectProxy 密不可分的。

https://ssshooter.com/2023-01-30-javascript-reflect/

Takeaway#

  • Reflect 是 JavaScript 元編程的重要對象,它可以調用對象內部方法,對對象進行一些內部操作
  • ReflectFunction.prototype.apply.callObject.getOwnPropertyNames 等一些函數更直觀、更簡潔
  • Reflect 承擔了 in 等運算符的功能,且返回操作結果
  • Reflect 可以跟 Proxy 完美搭配,可以任意定義對象操作行為

Reference#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。