我们知道Vue 2.0 是利用Ojbect.defineProperty 对对象的已有属性值的读取和修改进行劫持,但是这个API 不能监听对象属性的新增和删除,此外为了深度劫持对象的内部属性,必须在初始化的时候对内部属性进行递归调用Ojbect.defineProperty ,这就造成了一个性能上的消耗。为了解决这些问题,Vue 3.0 利用Proxy 重写了响应式逻辑并且优化了相关性能。
使用案例我们先来个示例看下Vue 3.0 的响应式API的写法: 
changePerson 能改变响应式数据person 的值,person 值的变化会触发组件重新渲染而更新DOM。
这里我们可以看到Vue 3.0 的使用中,开发者利用reactive 函数自己去确定哪些数据为响应式数据,这样就可以避免一些不必要的响应式的性能消耗。例如案例中我们就不需要让nowIndex 成为响应式数据。(当然Vue 2.0 也可以在data 函数外定义数据,这样也是非响应式数据) 我们接下来看看reactive 函数的实现原理!
reactive API相关的流程
reactive
代码说明: - 1.如果目标对象
target 是readonly对象,直接返回目标对象,因为readonly对象不能设置成响应式对象 - 2.调用
createReactiveObject 函数继续流程。
createReactiveObject 创建响应式对象
代码说明: - 1.如果目标对象不是数据或者对象,则直接返回对象,在开发环境给出错误警告提示。
- 2.如果
target 已经是一个Proxy 对象,则直接返回target, (target['__v_raw'] 设计非常巧妙:如果target 是Proxy 对象,target['__v_raw'] 触发get 方法,在缓存对象reactiveMap 中查找是否target 对象的Proxy 对象是否等于target 自身)。这里处理了一个例外,如果是给响应式对象执行readonly 函数则需要继续。 - 3.在
reactiveMap 中查找是否已经有了对应的Proxy 对象,则直接返回对应的Proxy 对象。 - 4.确保只有特定的数据能变成响应式,否则直接返回
target 。响应式白名单如下所示: 1.target 没有被执行过markRaw 方法,或者说target 对象没有__v_skip 属性值或者__v_skip 属性的值为false ; 2.target 不能是不可扩展对象,即target 没有被执行过preventExtensions ,seal 和freeze 这些方法; 3.target 为Object 或者Array ; 4.target 为Map ,Set ,WeakMap ,WeakSet ; - 5.通过使用
Proxy 函数劫持target 对象,返回的结果即为响应式对象了。这里的处理函数会根据target 对象不同而不同(这两个函数都是参数传入的): 1.Object 或者Array 的处理函数是collectionHandlers ; 2.Map ,Set ,WeakMap ,WeakSet 的处理函数是baseHandlers ; - 6.将响应式对象存入
reactiveMap 中缓存起来,key 是target , value 是proxy 。
mutableHandlers 处理函数
我们知道访问对象属性会触发get 函数,设置对象属性会触发set 函数,删除对象属性会触发deleteProperty 函数,in 操作符会触发has 函数,getOwnPropertyNames 会触发ownKeys 函数。我们接下来看看你这几个函数的代码逻辑。
get函数由于没有传参,isReadonly 和shallow 都是默认参数false。 
代码逻辑: - 1.如果获取
__v_isReactive 属性,返回true, 表示target已经是一个响应式对象了; - 2.获取
__v_isReadonly 属性,返回false;(readonly是响应式的另外一个API,暂不解释) - 3.获取
__v_raw 属性,返回target本身,这个属性用来判断target是否已经是响应式对象; - 4.如果target是数组,且命中了一些属性,例如includes, indexOf, lastIndexOf等,则执行的是数组的这些函数方法,并对数组的每个元素执行收集依赖track(
arr , TrackOpTypes.GET , i + ''),然后通过Reflect获取数组函数的值; - 5.Reflect求值;
- 6.判断是否是特殊的属性值:
symbol , __proto__ ,__v_isRef ,__isVu e, 如果是直接返回前面得到的res,不做后续处理; - 7.执行收集依赖;
- 8.如果是ref, 如果target不是数组或者key不是整数,就执行数据拆包,这里涉及到另外一个响应式APIref, 暂不解释;
- 9.如果res是对象,递归执行reactive,把res变成响应式对象。这里是一个优化小技巧,只有属性值被访问后才会被被劫持,避免了初始化就全劫持的性能消耗。
get函数的的调用时机回答这个问题前我们需要回到前面一篇关于setup的文章 爬虫进阶-JS自动渲染之Scrapy_splash组件的使用 JavaScript canvas实现九宫格切图效果 |