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

class Observer { constructor(value) { this.value = value; def(value, '__ob__', this); if (Array.isArray(value)) { value.__proto__ = arrayMethods; } else { this.walk(value); } } walk(obj) { for (const [key, value] of Object.entries(obj)) { defineReactive(obj, key, value); } } }

再来验证一下:

const people = { name: 'c', age: 12, parents: { dad: 'a', mom: 'b' }, mates: ['d', 'e'] }; new Observer(people); people.mates[0]; // getter (2) ["d", "e"] people.mates.push('f'); // mutator: (2) ["d", "e"] ["f"]

现在数组的修改也能被侦测到了。

依赖收集

目前已经可以对 Object 及 Array 数据的变化进行截获,那么开始考虑一开始提到的 Vue 响应式数据的第二个问题:如何知道数据变化后哪里需要修改?

最开始已经说过,Vue 中每个数据都需要收集与之相关的依赖,用来表示该数据变化时需要进行的操作行为。

通过数据的变化侦测我们可以知道数据何时被读取或修改,因此可以在数据读取时收集依赖,修改时通知依赖更新,这样就可以实现数据响应式了。

依赖收集在哪

为每个数据都创建一个收集依赖的对象 dep,对外暴露 depend(收集依赖)、notify(通知依赖更新)的两个方法,内部维护了一个数组用来保存该数据的每项依赖。

对于 Object,可以在 getter 中收集,setter 中通知更新,对 defineReactive 函数修改如下:

function defineReactive(data, key, val) { let childOb = observe(val); // 处理每个响应式数据时都创建一个对象用来收集依赖 let dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { // 收集依赖 dep.depend(); return val; }, set: function (newVal) { if (val === newVal) { return; } val = newVal; // 通知依赖更新 dep.notify(); } }) }

上面代码中依赖是收集在一个 Dep 实例对象上的,下面看一下 Dep 这个类。

class Dep { constructor() { this.subs = []; } addSub(sub) { this.subs.push(sub); } removeSub(sub) { if (this.subs.length) { const index = this.subs.indexOf(sub); this.subs.splice(index, 1); } } depend() { if (window.target) { this.addSub(window.target); } } notify() { const subs = this.subs.slice(); for (let i = 0; i < subs.length; i++) { subs[i].update(); } } }

Dep 的每个实例都有一个保存依赖的数组 subs,收集依赖时是从全局的一个变量上获取到并插入 subs,通知依赖时就遍历所有 subs 成员并调用其 update 方法。

Object 的依赖收集和触发都是在 defineProperty 中进行的,因此 Dep 实例定义在 defineReactive 函数中就可以让 getter 和 setter 都拿到。

而对于 Array 来说,依赖可以在 getter 中收集,但触发却是在拦截器中,为了保证 getter 和 拦截器中都能访问到 Dep 实例,Vue 中给 Observer 实例上添加了 dep 属性。

class Observer { constructor(value) { this.value = value; this.dep = new Dep(); def(value, '__ob__', this); if (Array.isArray(value)) { value.__proto__ = arrayMethods; } else { this.walk(value); } } walk(obj) { for (const [key, value] of Object.entries(obj)) { defineReactive(obj, key, value); } } }

Observer 在处理数据响应式时也将自身实例添加到了数据的 __ob__ 属性上,因此在 getter 和拦截器中都能通过响应式数据本身的  __ob__.dep 拿到其对应的依赖。修改 defineReactive 和 拦截器如下:

function defineReactive(data, key, val) { let childOb = observe(val); let dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { dep.depend(); // 给 Observer 实例上的 dep 属性收集依赖 if (childOb) { childOb.dep.depend(); } return val; }, ... }) } ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(method => { const original = ArrayProto[method]; def(arrayMethods, method, (...args) => { const result = original.apply(this, args); const ob = this.__ob__; ob.dep.notify(); return result; }) })

依赖长什么样

现在已经知道了依赖保存在每个响应式数据对应的 Dep 实例中的 subs 中,通过上面 Dep 的代码可以知道,收集的依赖是一个全局对象,且该对象对外暴露了一个 update 方法,记录了数据变化时需要进行的更新操作(如修改 dom 或 Vue 的 Watch)。

首先这个依赖对象的功能主要有两点:

需要主动将自己收集到对应响应式数据的 Dep 实例中;

保存数据变化时要进行的操作并在 update 方法中调用;

其实就是一个中介角色,Vue 中起名为 Watcher。

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

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