ES6中的Reflect
前言
很多同学在学习es6的时候都会产生这么一个疑问, 就是个 Reflect 到底是个啥, 它到底有什么作用,
其实要说明它的作用只需要一句话, 就是调用对象的基本方法, 或者叫基本操作或者内部方法都是一个意思
一句话结束了, 那剩下的事情就是要解释一下什么叫基本方法, 以及它的作用.
对象基本方法
这是ES官方文档里面的描述:
这是什么意思呢, 就是说作为一个js开发者, 无论你怎么操作一个对象, 最终都要对应到这里面的内部方法, 比如我们使用这么一个语法:
const obj = {}; |
也就是说, 我们书写的语法层面的代码, 它最终运行的时候, 实际上运行的是这个对象的内部方法, 对一个对象的所有操作, 都不能避免这一点.
Reflect 是什么
它是一个包含了与所有内部方法对应的静态方法的类, 它不能创建对象, 就和Math一样, 所有方法都是静态的, 它可以直接访问对象的内部方法, 上面的语法使用Reflect就是这样:
const obj = {}; |
这就是直接调用对象的内部[[Get]] / [[Set]] 方法来对对象添加键值和访问键, 在es6之前我们没有任何办法直接调用内部方法, 现在可以了. 官方还给我们提供了一个表格, 说明了对象的内部方法和反射方法的对应关系:
肯定有人疑惑了, 这么整的意义是什么, 我直接通过点属性名去get/set不就行了, 为什么需要这玩意?
这是因为, 如果你通过点语法就意味着你是间接去调用的内部方法, 这和直接调用内部方法是有区别的, 区别在哪呢?
我举例说明, 当大家使用某种语法操作对象时, 它会经过一系列规则和步骤, 这些规则和步骤中的某一步就是在调用内部方法, 好理解吧, 就相当于语法层面的操作是一个高级封装的功能, 而内部方法只是其中的核心实现, 每种语法对应了不同的规则和步骤, 如果你不希望有任何额外的规则和步骤存在, 你就需要直接调用内部方法.
比如下面的对象, 有abc三个属性, 其中c是一个getter属性
const obj = { |
我们看一下[[Get]] 内部方法的定义:
其中第二个参数Receiver是一个指定的this对象, 因为有时候我们访问一个对象属性时, 这个属性可能是一个getter, 它会执行一个函数, 这个函数内部的this就是这个Receiver.
上面的代码, 我们通过点属性的语法访问c属性, getter函数中默认的this是obj, 因此输出3.
此时看出问题没, 当我想改变getter中的this指向时, 我们完全没有任何办法, 但是通过Reflect就很容易做到:
const obj = { |
我们将{ a: 3, b: 4}作为this来访问c属性, 此时得到的结果是7. 我想现在你应该体验到区别了吧, 语法层面. 即点属性, 这种方式已经固定了this指向, 它实际上大致做了以下 ‘规则和步骤’:
- 定义
this
是obj
- 调用
Reflect.get(obj, 'c', obj)
当你不想要语法带来的额外规则和步骤时, 你就可以直接使用Reflect.
但到这可能又有人疑惑了, 这能干嘛呢, 谁没事会这样写代码呢, 别急, 接下来再通过一个例子加深理解
Reflect 应用
ES6中的Proxy大家不陌生吧, Reflect最常用的使用场景就是在Proxy中, 比如:
const proxy = new Proxy(obj, { |
大家对上面访问c的结果意外吗, 按照期望, 访问c时, 由于c是一个getter, 其中还访问了a和b, 应该打印三次 read c/a/b,
为啥呢?
因为你在proxy的get中返回的时候, 直接返回了target[key]
target是原始obj对象, 它的c属性getter中的this指向的是obj自身, 因此你访问proxy.c当然只打印read c了, 因为a和b的访问是发生在obj上, 并不是proxy上, 因此我们可以这么写来解决这个问题:
const proxy = new Proxy(obj, { |
我们修改了返回值, 强制让所有的属性访问都发生在proxy对象上, 这样就能完整的拦截所有proxy属性的访问了, 如果你对vue3的源码有所了解, 你可以看看它这块的处理.
我在举一个应用例子
const obj = { |
实际上Object.keys也会调用内部方法[[GetOwnProperty]]
但是它不是直接调用的, 它有额外的规则和步骤, 比如下面的代码运行后依然没有c属性, 也没有symbol属性.
Object.defineProperty(obj. 'c', { |
这就说明了通过语法api去访问对象方法, 它不是直接调用基本方法, 它会有语法api特定的逻辑, 官方文档中看一下对Object.keys
的方法说明:
可以很明显看到, 第二个大步骤中的第一步就是调用基本方法获取对象所有的key, 后续再有一系列的处理最终得到Object.keys
的结果.
所以我如果不希望有这些处理, 我就想直接拿到对象所有的key, 那我们就可以直接调用反射方法:
const keys = Reflect.ownKeys(obj); |
可以看到结果包含了对象中所有的定义的key.
总结
再看开始的一句话, Reflect就是直接调用对象的基本方法(内部方法), 在es6之前, 我们没有这个能力, 现在通过它我们可以触碰到更底层的基本操作, 有了它之后, 其实你还可以有更多的想象, 比如针对对象提供的官方的方法, 你都可以模拟了, 修改扩展api语法层面的”规则和步骤”.