vue2.x出现数组项改变但是依然触发视图更新的原因

197 阅读2分钟

前言

根据vue官方文档的说法,因为javascript本身的原因,使用直接改变数组项的值的方式没有办法触发视图的更新。文档

但是我们在项目开发中经常会有直接改变数组项的操作,发现视图依然更新了,并没有出现文档中说明的情况。

举例

<div id="app"></div>
<button id="btn">点我</button>
<script>
  var data = {
    test: {
      a: 1
    },
    arrTest: [0, 1]
  }

  var vm = new Vue({
    el: "#app",
    data: data,
    template: "<div><div>{{ test.a }}</div><div>{{ arrTest[1] }}</div></div>",
    updated: function () {
      console.log('组件被重绘了')
    }
  })

  var btn = document.getElementById('btn')
  btn.addEventListener('click', function (e) {
    data.test.a++  
    data.arrTest[1] = new Date().getTime()  
    console.log(data);
  })
</script>

上面的例子可以体现,哪怕通过data.arrTest[1] = xxx的方式直接改变数组项也可以直接使视图更新。

分析

经过多次测试,发现视图的更新其实跟data.test.a ++ 这一步操作有关系,如果没有这一步操作,则视图不会更新。测试表明data.arrTest[1] = xxx的方式其实是不能触发视图更新的,本质上是data.test.a ++触发的视图更新。但是为什么data.test.a的更新的同时也导致了<div>{{ arrTest[1] }}</div>的重绘呢?不是应该只是重绘<div>{{ test.a }}</div>吗?按照vue数据绑定的原理,data.test.a的改变应该只通知<div>{{ test.a }}</div>啊?

原理

vue的数据绑定理论上是通过观察者模式实现的(VUE2.x数据绑定原理),也就是说它可以做到当data.test.a改变时应该只改变<div>{{ test.a }}</div>,但是为什么导致了整个组件的更新呢?

这里就是vue引入虚拟dom(Vnode)的原因了,每个变量绑定的依赖全部记录下来并且改变时通知相应的的依赖,如果依赖过多就导致内存占用和计算效率问题。所以为了平衡依赖追踪的细粒度和执行效率,vue选择数据并没有绑定到具体某一个依赖,而是绑定到一个组件,当这个变量改变时就找到使用了这个变量的组件,然后将组件重新编译成新的Vnode,然后将新的Vnode和目前渲染在页面上的Vnode进行对比找出不同,然后只更新新旧Vnode不同的地方。

这也就是为什么data.test.a的改变带动了<div>{{ arrTest[1] }}</div>的更新,因为data.test.a是可以触发依赖更新的,从而导致了整个组件重新检查需要更新的地方,所以<div>{{ arrTest[1] }}</div>也就蹭上了更新的顺风车。如果单纯的改变数组项(data.arrTest[1] = xxx)的值是不会触发视图更新的。(可以把data.test.a++这句代码删除试试)

学习过程,有问题希望指出,非常感谢哦