稍微学一下Vue的数据响应式(Vue2及Vue3区别)(3)

class Watcher { constructor(vm, expOrFn, cb) { this.vm = vm; // 保存通过表达式获取数据的方法 this.getter = parsePath(expOrFn); this.cb = cb; this.value = this.get(); } get() { // 将自身 Watcher 实例挂到全局对象上 window.target = this; // 获取表达式对应的数据 // 会自动触发该数据的 getter // getter 中收集依赖时从全局对象上拿到这个 Watcher 实例 let value = this.getter.call(this.vm, this.vm); window.target = undefined; return value; } update() { const oldValue = this.value; this.value = this.get(); // 将旧值与新值传递给回调函数 this.cb.call(this.vm, this.value, oldValue); } }

对于第一点,主动将自己收集到 Dep 实例中,Watcher 中设计的非常巧妙,在 get 中将自身 Watcher 实例挂到全局对象上,然后通过获取数据触发 getter 来实现依赖收集。

第二点实现很简单,只需要将构造函数参数中的回调函数保存并在 update 方法中调用即可。

构造函数中的 parsePath 方法就是从 Vue 实例的 data 上通过表达式获取数据,比如表达式为 "user.name" 则需要解析该字符串然后获取 data.user.name 数据。

总结

数据先通过调用 new Observer() 为每项属性添加变化侦测,并创建一个 Dep 实例用来保存相关依赖。在读取属性值时保存依赖,修改属性值时通知依赖;

Dep 实例的 subs 属性为一个数组,保存依赖是向数组中添加,通知依赖时遍历数组一次调用依赖的 update 方法;

依赖是一个 Watcher 实例,保存了数据变化时需要进行的操作,并将实例自身放到全局的一个位置,然后读取数据触发数据的 getter,getter 中从全局指定的位置获取到该 Watcher 实例并收集在 Dep 实例中。

稍微学一下Vue的数据响应式(Vue2及Vue3区别)

以上就是 Vue2 中的响应式原理,在 Observer 处理完后,外界只需要通过创建 Watcher 传入需要监听的数据及数据变化时的响应回调函数即可。

Vue3

Vue3 中每个功能单独为一个模块,并可以单独打包使用,本文仅简单讨论 Vue3 中与数据响应式相关的 Reactive 模块,了解其内部原理,与 Vue2 相比又有何不同。

因为该模块可以单独使用,先来看一下这个模块的用法示例:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>vue3 demo</title> </head> <body> <div> <div></div> <button>+1</button> </div> <script src="https://www.jb51.net/article/vue3.js"></script> <script> const countEl = document.querySelector('#count') const btnEl = document.querySelector('#btn') // 定义响应式数据 const state = reactive({ count: 0, man: { name: 'pan' } }) // 定义计算属性 let double = computed(() => { return state.count * 2 }) // 回调函数立即执行一次,内部使用到的数据更新时会重新执行回调函数 effect(() => { countEl.innerHTML = `count is ${state.count}, double is ${double.value}, man's name is ${state.man.name}` }) // 修改响应式数据触发更新 btnEl.addEventListener('click', () => { state.count++ }, false) </script> </body> </html>

通过示例可以看到实现 Vue3 这个数据响应式需要有 reactive、computed、effect 这几个函数,下面仍然通过从变化侦测及依赖收集两个方面介绍,简单实现这几个函数。

变化侦测

示例中的 reactive 函数是对数据进行响应式化的,因此该函数的功能就类似于 Vue2 中的 defineReactive 函数的 getter/setter 处理,处理后能够对数据的获取及修改操作进行捕获。

const toProxy = new WeakMap() const toRaw = new WeakMap() const baseHandler = { get(target, key) { console.log('Get', target, key) const res = Reflect.get(target, key) // 递归寻找 return typeof res == 'object' ? reactive(res) : res }, set(target, key, val) { console.log('Set', target, key, val) const res = Reflect.set(target, key, val) return res } } function reactive(target) { console.log('reactive', target) // 查询缓存 let observed = toProxy.get(target) if (observed) { return observed } if (toRaw.get(target)) { return target } observed = new Proxy(target, baseHandler) // 设置缓存 toProxy.set(target, observed) toRaw.set(observed, target) return observed }

reactive 中使用 Proxy 对目标进行代理,代理的行为是 baseHander ,然后对目标对象及代理后的对象进行缓存,防止多次代理。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/8b8761c4f8bd7f689160344597b39941.html