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))
// Expected output: 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#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。