Vue2 响应式原理
Vue 通过Object.defineProperty把data中的属性成响应式也就是每个属性都有get和set方法,Vue模板编译的时候视图上绑定的数据都会实例化一个 Watcher ,在这个实例化的过程会触发属性的 get() 方法,然后Watcher实例会将自身指向 Dep.target ,之后在get()方法中将这个Dep.target存放在Dep实例对象的subs数组中,data的一个属性可能在页面的多个地方使用,当页面渲染到使用的地方时都会重新生成一个 Watcher 实例并指向Dep.target然后存入subs数组中,这样就完成了依赖收集。当页面中的数据发生改变时,会触发对应属性的set方法,这时会调用Observe对象的dep.notify()方法会去遍历subs数组,这个数组中存放的都是Watcher实例,这些实例去执行自己的run方法实现试图更新。
缺点:
●不能监听对象属性的新增和删除
●在初始化递归(这个递归是深层次的)的时候Object.defineProperty会带来性能负担。而Vue3 的深度响应式是逐级实现的。
Vue3 响应式原理
Vue3 创建Proxy对象的过程:
1判断对象有没有被Proxy代理过,如果代理过直接返回
2如果没有被代理过的话会判断对象是否是【Set、Map、WeakMap、WeakSet】数据类型,如果是的话选择用collectionHandlers方法作为handle函数,如果传入的是普通对象就用传进来的mutableHandles作为Proxy的hander对象。
3最后new Proxy一个代理对象,并将这个对象与传进来的目标对象建立联系并保存。
Vue3 用effect副作用钩子来代替Vue2watcher 来进行数据变化后视图的重新渲染。
track依赖收集器:
生成的代理对象会以键值对形式存放在targetMap中,在这里面键是生成的代理对象,值是depMap。
在depMap是一个map集合 ,里面的键是当前get访问的属性名,值是deps一个Set集合里面存放的是effect。
track的作用就是找到与当前proxy和key对应的dep。并且与当前的activeEffect也就是effect副作用钩子建立联系,实现依赖收集。
依赖收集流程总结:
在进行页面渲染时访问真实的data属性会触发get,get方法首先经过不同的reactive,通过track方法进行依赖收集。track方法通过当前的proxy和访问的属性key来找到对应的dep。将当前的activeEffect存入对应的dep数组中如果触发set,就能在书中找到对应的effect依次执行。
派发更新
当页面中的数据发生改变时会触发set方法,在set方法中我们首先通过toRaw判断当前proxy对象和建立响应式存入reactiveToRaw的proxy对象是否相等。然后判断这个代理对相关中是否有这个要修改的属性,如果有就直接执行trigger(target, TriggerOpTypes.SET, key, value, oldValue),如果没有说明是新增的属性就执行执行trigger(target, TriggerOpTypes.ADD, key, value)。trigger中的逻辑是首先从targetMap中根据当前Proxy找到对应的depsMap,根据depsMap中对应的deps,然后通过add方法分离出对应的effect回调函数和computed回调函数。依次执行computedRunners 和 effects 队列里面的回调函数,如果发现需要调度处理,放进scheduler事件调度。此时的effect队列中有我们上述负责渲染的renderEffect,还有通过effectAPI建立的effect,以及通过watch形成的effect。我们这里只考虑到渲染effect。至于后面的情况会在接下来的文章中和大家一起分享。
Vue3响应式原理总结:
初始化阶生成对应的Proxy对象,然后生成一个负责渲染的effect。通过解析template,替换真实data属性时触发get然后调用stack方法将Proxy对象和key形成对应的deps,将负责渲染的effect存入deps。当我们要修改属性时首先调用trigger方法,然后通过Proxy对象和key找到对应的deps,依次执行deps中的effect来进行页面渲染。
$set实现原理
在new Vue()的时候$set方法被注入到Vue的原型上。
首先判断在不是生产环境的条件下,如果第一个参数是undefined或null或者是原始值,就会弹出警告。
然后判断传入的对象是不是数组,如果是数组先修改数组的长度,然后再调用重写的slice()方法去修改数组并且触发页面响应。
如果是普通对象,并且传入的属性是已经存在的就直接修改原始数据。
Vue2重写数组8个方法的思路
- 通过Object.defineProperty()定义数组中的八个方法是不可枚举的属性。这样防止遍历数组时会遍历到这些方法。
- 然后把原始的方法在重写的方法中进行调用,然后使用dep.nofify()方法通知相关组件进行重新渲染
- Vue2在每个重写的数组方法中还会调用dependArray()方法来确保嵌套数组的响应式。
Vue3 重写数组8个方法的思路
Vue3通过Proxy直接对数组进行劫持,不需要重写8个原生方法
通过对属性的get和set进行拦截实现依赖收集和派发更新。
Vue3数组长度的length属性也是直接通过get和set方直接进行监听的。