JavaScript 的 Reflect 和 Proxy
/ 6 min read
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
的方法都放到这里来了。
这带来了使用 Reflect 的第一个明显的好处,简化代码。此外,apply
其实是调用了 JavaScript 引擎的内部方法 [[Call]]
,所以 Reflect 才会归入元编程的范畴。
再例如,Reflect.ownKeys
承担了 Object.getOwnPropertyNames
的功能,对对象的属性进行检视:
Reflect.deleteProperty()
和 Reflect.has()
这样的方法则是承担了一些运算符的功能:
到了 get
、set
等方法,跟前面一样,调用了内部方法 [[Get]]
、[[Set]]
:
但这就迷惑起来了,明明 object1.x
也是调用内部方法 [[Get]]
,而且更简单,为什么要用 Reflect.get
这么麻烦的方法呢?答案是搭配 Proxy
。
Proxy
Proxy
很好理解,相信大家也在某些领域(狗头)常常见到这个词,在这里也是那个熟悉的意思,就是代理,Proxy
实例是一个对象,你对这个对象的操作会被其他函数代理。
最基础的 Proxy
长这样:
事实上这样的 Proxy
什么也不会发生,一切照常,若是你想要发生点什么呢?可以从 handler
做文章。
可以看到 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
的名称完全一致,于是我们可以这样设定:
这样得到的 test
对象会在你进行所有内部方法调用时打出日志,例如:
例子中只是把操作和参数打印出来,实际上你可以在 trap
中对对象进行任意处理,这样一下子编程思路就广起来了,例如 Vue3 的响应式原理就与 Reflect
和 Proxy
密不可分的。
Takeaway
Reflect
是 JavaScript 元编程的重要对象,它可以调用对象内部方法,对对象进行一些内部操作Reflect
让Function.prototype.apply.call
、Object.getOwnPropertyNames
等一些函数更直观、更简洁Reflect
承担了in
等运算符的功能,且返回操作结果Reflect
可以跟Proxy
完美搭配,可以任意定义对象操作行为