关于 Reflect(反射) 大多数人都不会陌生,MDN 文档里对其描述:Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handler (en-US) 的方法相同,Reflect 不是一个函数对象,因此它是不可构造的。
读了这段话,可能大家都会比较迷惑,这讲的是啥?对此产生了一些疑问:Reflect 的作用是啥?为什么要有他?
那么这篇文章将和大家一起去探究这些问题,理解 Reflect 的本质!
Reflect的本质
首先直接下结论,Reflect 的本质是直接调用对象的基本操作(基本方法)
,那么什么是对象的基本操作(基本方法)呢?我们可以去看下 ES 的官方文档,下面这个表格就罗列了对象的所有基本操作:
简单来说,在代码语法层面,无论我们怎么操作一个对象,实际上都是在调用这些基本方法!
举个例子,下面我们看一段的代码,读取对象中的属性和给对象设置属性,实际上内部间接
调用了对象的基本方法 GET 和 SET:
把这段代码使用 Reflect 改写下,直接
调用对象的基本方法:
两种方式其实效果是一样的,很明显可以看出使用 Reflect 反而更麻烦,代码可读性降低,那么为什么要使用 Reflect 呢?因此,接下来我们要探究 Reflect 的好处。
Reflect的好处
对于代码语句:obj.a 实际上会进行很多操作,包括其他操作和调用对象的基本方法(如上图),也就是所谓的间接操作,进行额外的步骤再调用对象的基本方法,因此这种方式在某些情况下是得不到直接调用对象的基本方法所产生的结果,如果我们不希望有这些额外的步骤存在,想要得到直接调用对象的基本方法所产生的结果,那么这个时候 Reflect 就派上用场了!
通过两个例子来说明:
例一
对象里面有个属性是一个方法,里面用到了 this,通过 obj.c 的方式去访问的话无法修改 this 指向,但是通过 Reflect.get 是可以做到的:
Reflect 中的 [[GET]] 方法可以接受一个 Receiver 参数,这个参数可以指定 this 指向,使用 obj.c 这种方式里面很多额外的步骤,他不给你指定 this,而使用 Reflect 可以通过传参的方式一步到位!
那么正常情况下其实我们是不需要写出这种代码的,毕竟可读性差,所以说 Reflect 的使用是需要在特地的场景下,不是啥场景都能用的,没必要反而更加麻烦,Reflect 搭配 Proxy 就是一个很好的使用场景,帮助我们解决问题,下文再聊两者的结合使用,这里接着看第二个例子。
例二
使用 Object.keys 获取对象的键:
如果对象里面有 Symbol 类型或者不可枚举的键,那么 Object.keys 是拿不到这些键的:
Object.keys 内部其实是调用了 [[GetOwnProperty]] 方法,但是它也有很多额外的步骤,这些步骤里面排除了一些类型的属性,所以拿不到想要的结果,我们看下 Object.keys 内部做了哪些事:
可以看到内部做了很多操作,因此如果我们想要排除忽略这些额外的操作,直接拿到结果,使用 Reflect 即可
通过上面两个例子可以知道,其实 Reflect 在某些场景下是很有用的,用好 Reflect 是有助于开发的!
Proxy+Reflect
对 Reflect 有了比较深入的理解之后,再来研究下 Vue3 响应式原理中为什么要搭配使用 Proxy 和 Reflect?其实就是要解决只使用 Proxy 带来的问题。
先看段使用代理对象的代码,这种情况下用到了对象的三个属性,但是却只读取到 c
这是为什么呢?因为 proxy.c 返回的是原始对象 obj 中的 c,target 其实就是 obj,里面的 this 是指向 target 的,此时读取 a b 是收不到任何通知的,必须要读代理里面的 a b 才能收到通知,这个时候就需要修改 this 指向才行,上面已经提到过 Reflect.get 是可以修改 this 指向的。
使用 Reflect 修改代码如下:
这样便得到了我们想要的结果。
相信大家对 Reflect 的本质有了更加深入的理解,一句话概括 Reflect:直接调用对象的基本方法(基本操作)!