什么是数据响应式
从一开始使用 Vue 时,对于之前的 jq 开发而言,一个很大的区别就是基本不用手动操作 dom,data 中声明的数据状态改变后会自动重新渲染相关的 dom。
换句话说就是 Vue 自己知道哪个数据状态发生了变化及哪里有用到这个数据需要随之修改。
因此实现数据响应式有两个重点问题:
如何知道数据发生了变化? 如何知道数据变化后哪里需要修改?对于第一个问题,如何知道数据发生了变化,Vue3 之前使用了 ES5 的一个 API Object.defineProperty Vue3 中使用了 ES6 的 Proxy,都是对需要侦测的数据进行 变化侦测 ,添加 getter 和 setter ,这样就可以知道数据何时被读取和修改。
第二个问题,如何知道数据变化后哪里需要修改,Vue 对于每个数据都收集了与之相关的 依赖 ,这里的依赖其实就是一个对象,保存有该数据的旧值及数据变化后需要执行的函数。每个响应式的数据变化时会遍历通知其对应的每个依赖,依赖收到通知后会判断一下新旧值有没有发生变化,如果变化则执行回调函数响应数据变化(比如修改 dom)。
下面详细分别介绍 Vue2 及 Vue3 的数据变化侦测及依赖收集。
Vue2
变化侦测
Object 的变化侦测
转化响应式数据需要将 Vue 实例上 data 属性中定义的数据通过递归将所有属性都转化为 getter/setter 的形式,Vue 中定义了一个 Observer 类来做这个事情。
function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true })}class Observer { constructor(value) { this.value = value; def(value, '__ob__', this); if (!Array.isArray(value)) { this.walk(value); } } walk(obj) { for (const [key, value] of Object.entries(obj)) { defineReactive(obj, key, value); } }}
直接将一个对象传入 new Observer() 后就对每项属性都调用 defineReactive 函数添加变化侦测,下面定义这个函数:
function defineReactive(data, key, val) { let childOb = observe(val); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { // 读取 data[key] 时触发 console.log('getter', val); return val; }, set: function (newVal) { // 修改 data[key] 时触发 console.log('setter', newVal); if (val === newVal) { return; } val = newVal; } })}function observe(value, asRootData) { if (typeof val !== 'object') { return; } let ob; if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__; } else { ob = new Observer(val); } return ob;}
函数中判断如果是对象则递归调用 Observer 来实现所有属性的变化侦测,根据 __ob__ 属性判断是否已处理过,防止多次重复处理,Observer 处理过后会给数据添加这个属性,下面写一个对象试一下:
const people = { name: 'c', age: 12, parents: { dad: 'a', mom: 'b' }, mates: ['d', 'e']};new Observer(people);people.name; // getter cpeople.age++; // getter 12 setter 13people.parents.dad; // getter {} getter a
打印 people 可以看到所有属性添加了 getter/setter 方法,读取 name 属性时打印了 people.age++ 修改 age 时打印了 getter 12 setter 13 说明 people 的属性已经被全部成功代理监听。
新闻热点
疑难解答