vue3.0响应式原理
2020/1/27 11:09:22
本文主要是介绍vue3.0响应式原理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
写在前面
目前,Vue 的反应系统是使用 Object.defineProperty 的 getter 和 setter。 但是,Vue 3 将使用 ES2015 Proxy 作为其观察者机制。 这消除了以前存在的警告,使速度加倍,并节省了一半的内存开销。同时使用新的Composition Api,更好的逻辑复用,类型推导,更高的性能。
2.0的不足
使用递归对数据进行劫持,多层嵌套内存消耗大,性能不高。 只能劫持预先设置好的数据,直接添加的数据无法劫持。可以通过vue.set(xxx)。 对数组的操作只能是内部劫持的7种方法,直接修改下标不能触发响应式。
3.0的提升
- 逻辑组合和复用
- 类型推导:Vue3.0 最核心的点之一就是使用 TS 重构,以实现对 TS 丝滑般的支持。而基于函数 的 API 则天然对类型推导很友好。
- 打包尺寸:每个函数都可作为 named ES export 被单独引入,对 tree-shaking 很友好;其次所有函数名和 setup 函数内部的变量都能被压缩,所以能有更好的压缩效率。
3.0的一些基本用法
关于3.0的一些基本api的用法这里就不详细介绍,我们今天着重讲一下,reactive和effect的实现,也就是3.0核心的响应式原理是怎么实现,以及怎样收集依赖。Composition Api
reactive
/** * 这是第一步,实现数据的劫持 */ function reactive(target) { return createReactiveObject(target) } function createReactiveObject(target) { if(!isObject(target)) { return target } // 说明已经代理过了 const proxyed = toProxy.get(target) if (proxyed) { return proxyed } // 防止反复代理 // reactive(proxy) reactive(proxy) if (toRow.has(target)) { return target } const handles = { get(target, key, receiver) { let result = Reflect.get(target, key, receiver) // 如果是多层次的对象的,我们需要递归代理 return isObject(result) ? reactive(result) : result }, set(target, key, value, receiver) { let oldValue = target[key] // 我们不知道设置是否成功,所以要做一个反射,来告诉我们是否成功 let flag = Reflect.set(target, key, value, receiver) return flag }, // 删除的同上 deleteProperty() { } } const observe = new Proxy(target, handles) return observe } 复制代码
上面的代码很简单,我们递归对我们的数据实现了劫持,我们用Reflect反射,这里set中如果直接用target[key] = value来赋值会报错,用Reflect可以返回是否set成功。 这里有几个问题我们需要解决:
- 多次重复代理同一个对象
let name = {a:123} reactive(name) reactive(name) reactive(name) 复制代码
- 代理过的对象多次代理
let name = {a:123} let proxy = reactive(name) reactive(proxy) reactive(proxy) 复制代码
为了解决上面的俩个问题,源码里面用俩个WeakMap来做映射表关系的。所以上面的代码我们增加如下代码。同样WeakMap也是es6中的,不知道的同学可以去看看。WeakMap
const toProxy = new WeakMap() // 用来放 当前对象:代理过的对象 const toRow = new WeakMap() // 用来放 代理过的对象: 当前对象 // 说明已经代理过了 const proxyed = toProxy.get(target) if (proxyed) { return proxyed } // 防止反复代理 // reactive(proxy) reactive(proxy) if (toRow.has(target)) { return target } // 对已经代理过的,进行保存 toProxy.set(target, observe) toRow.set(observe, target) 复制代码
对数组的特殊处理
我们知道在2.0中对数组我们只能调用特定的7个方法才能让数据是响应式的。但在3.0中用Proxy我们能直接监听到数组的变换。
/** * 这里是数组的一个处理,如果push[1,2] => [1,2,3] * 这里会触发两次的set,一次是下标2的set,一次是length的set * 但是length的set的触发在这里是无意义的,length的修改并不需要是响应式的 * oldValue !== value 可以规避length的修改带来的影响 */ if (!isOwnProperty(target, key)) { console.log('设置新的属性') // 修改属性 } else if (oldValue !== value) { console.log('修改原有的属性') } 复制代码
####effect以及依赖收集 下面我们来实现响应式原理,在vue3.0中effect是一个非常有用的api,它会首先执行一次,然后依赖的数据改变了会自动在执行传入的函数,相当于computed。(我是这么理解的)
// 栈数组,先进后出 /** * 依赖收集 (数据: [effect]) * 每个数据对应它的依赖,数据一变执行方法 */ const activeEffectStacks = [] /** * 建立依赖关系 * 数据结构 * * (WeakMap): { * target: (Map) { * key: (Set) [effect,effect] * } * } */ const targetMap = new WeakMap() function track(target, key) { let effect = activeEffectStacks[activeEffectStacks.length - 1] if (effect) { let depsMap = targetMap.get(target) if (!depsMap) { depsMap = new Map() targetMap.set(target, depsMap) } let deps = depsMap.get(key) if (!deps) { deps = new Set() depsMap.set(key, deps) } if (!deps.has(effect)) { deps.add(effect) } } } /** * 第二步,实现数据的响应式 * 数据变通知依赖的数据更新 * * 副作用,先会执行一次,当数据变话的时候在执行一次 * 这里面设计到一个依赖收集的东西,源码里面用一个栈(数组[])来做的 * */ function effect(fn) { const effectFun = createReactiveEffect(fn) effectFun() } function createReactiveEffect(fn) { const effect = function() { run(effect, fn) } return effect } function run(effect, fn) { try { // 栈里面已经拿到数据了以后,清掉保证数据量 // try 保证fn执行报错时,一定能将栈清除 activeEffectStacks.push(effect) fn() } finally{ activeEffectStacks.pop(effect) } } // 这里增加依赖收集 get(target, key, receiver) { let result = Reflect.get(target, key, receiver) // 进行依赖收集 /** * 这里很巧妙,在第一次调用effect的时候,一定能触发一次target的get方法的 * 此时我们将依赖的关系建立 */ track(target, key) // 如果是多层次的对象的,我们需要递归代理 return isObject(result) ? reactive(result) : result }, 复制代码
这里我们主要将一下这个收集依赖的数据结构关系。用一个weakMap来放[target, Map], Map[key, Set],Set[effect],这样我们就能建立一个target,key,effect的依赖关系,每次target[key]改变的时候我们就能将对应的effect循环执行一遍。
trigger 依赖触发
/** * 依赖的触发 */ function trigger(target, type, key) { // 这里先不做type的区分 const depsMap = targetMap.get(target) if (depsMap) { const deps = depsMap.get(key) if (deps) { deps.forEach(effect => { effect() }) } } } // 我们在get的时候触发依赖 if (!isOwnProperty(target, key)) { trigger(target, 'add', key) console.log('设置新的属性') // 修改属性 } else if (oldValue !== value) { trigger(target, 'set', key) console.log('修改原有的属性') } 复制代码
好了,这样我们一个完整的一个数据劫持,依赖收集,依赖触发就基本完成。 完整代码
这篇关于vue3.0响应式原理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-04package.json 文件位置在哪?-icode9专业技术文章分享
- 2024-10-01Craco.js学习:从入门到实践指南
- 2024-10-01Create-React-App学习:入门与实践指南
- 2024-10-01CSS-in-JS学习:从入门到实践指南
- 2024-09-30JSX语法学习:从入门到初步掌握
- 2024-09-30Mock.js学习:入门教程与实战演练
- 2024-09-30React Hooks学习:从入门到实践
- 2024-09-30受控组件学习:React中的基础入门教程
- 2024-09-29JS定时器教程:初学者必看指南
- 2024-09-29JS对象教程:初学者的全面指南