如何追踪变化

当你把一个普通的 JavaScript 对象传入 Vue 实例作为data选项,Vue 将遍历此对象所有的 property,并使用Object.defineProperty把这些 property 全部转为getter/setter。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装vue-devtools来获取对检查数据更加友好的用户界面。

每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

先通过Object.defineProperty get set拦截,每次数据更改的时候,通过set拦截,通知我们的Watcher进行触发更新,Watcher是观察者,它收集了很多跟状态相关的依赖,以及发布者和订阅者,触发这些订阅者进行调用,这样组件就会更新了。把所有的组件代码进行更新,在更新的过程中先创建一份新的虚拟dom,再对比老的虚拟dom节点,两个在对比过程中,以最小的代价找出不同,再更新到dom中。图示:

Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。

检测变化的注意事项

Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在data对象上存在才能让 Vue 将它转换为响应式的。

var vm = new Vue({  data:{    a:1  }})// `vm.a` 是响应式的vm.b = 2// `vm.b` 是非响应式的

对于数组:

Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

举个例子:

var vm = new Vue({  data: {    items: ['a', 'b', 'c']  }})vm.items[1] = 'x' // 不是响应性的vm.items.length = 2 // 不是响应性的

声明响应式 property

由于 Vue 不允许动态添加根级响应式 property,所以你必须在初始化实例前声明所有根级响应式 property,哪怕只是一个空值:

var vm = new Vue({  data: {    // 声明 message 为一个空值字符串    message: ''  },  template: '{{ message }}'})// 之后设置 `message`vm.message = 'Hello!'

如果你未在data选项中声明message,Vue 将警告你渲染函数正在试图访问不存在的 property。

这样的限制在背后是有其技术原因的,它消除了在依赖项跟踪系统中的一类边界情况,也使 Vue 实例能更好地配合类型检查系统工作。但与此同时在代码可维护性方面也有一点重要的考虑:data对象就像组件状态的结构 (schema)。提前声明所有的响应式 property,可以让组件代码在未来修改或给其他开发人员阅读时更易于理解。

Object.defineProperty有以下缺点:

1、无法监听es6的Set、Map变化;

2、无法监听Class类型的数据;

3、属性的新加或者删除也无法监听;

4、数组元素的增加和删除也无法监听;

针对Object.defineProperty的缺点,ES6 Proxy都能够完美的解决,它唯一的缺点就是,对IE不友好,所以vue3在检测到如果是使用IE的情况下(没错,IE11都不支持Proxy),会自动降级为Object.defineProperty的数据监听系统。

虚拟DOM树:

vdom、vnode、virtual dom、virtual node指的都是虚拟DOM。

013-key设置_哔哩哔哩_bilibili