原因是因为:   

vue不能检测data中数组的变动,如利用索引直接改变一个项的值的时候,利用arr.length修改数组的长度的时候, 还有由于vue2.0 使用的是object.definepropoty进行的数据监听,导致Vue不能检测对象属性的添加和删除。

解决方法:

Vue.set() 响应式新增与修改数据
此时我们需要知道Vue.set()需要哪些参数,官方API:Vue.set()

调用方法:Vue.set( target, key, value )

target:要更改的数据源(可以是对象或者数组)

key:要更改的具体数据

value :重新赋的值

 首先我们来看看vue2.0的响应式原理

目前浏览监测对象的变化的方式有Object.defineProperty和ES6的Proxy两种,在设计vue2.0的时候Proxy在浏览器的支持还并不是特别的友好,因此vue2.0是基于Object.defineProperty来实现的

Object.defineProperty(obj, prop, descriptor)

  • obj:要在其上定义属性的对象
  • prop:要定义或修改的属性的名称
  • descriptor:将被定义或修改的属性描述符

Object.defineProperty()是es5新增的方法,它的作用是可以通过该API直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。Vue框架内部大量使用了此API为对象定义属性,其响应式原理也是通过此API自定义setter与getter而完成的。

ECMAScript有两种属性

  1. 数据属性[[Configurable]]、[[Enumerable]]、[[Writable]]、[[Value]]
  2. 访问器属性[[Configurable]]、[[Enumerable]]、[[Get]]、[[Set]]

访问器属性包含两个函数get,和set,在读取访问器属性的时候,会调用getter函数,这个函数负责返回有效的值,在写入访问器属性的时候会调用setter函数冰川乳新的值,这个函数负责决定如何处理数据访问器。

var obj = {};
var a;
Object.defineProperty(obj, 'a', {
  get: function() {
    console.log('get a val'); 
    return a;
  },
  set: function(newVal) {
    console.log('set a val:' + newVal);
    a = newVal;
  }
});
obj.a;          // get a val 
obj.a = '111';  // set a val: 111

所以Object.defineProperty 把obj 的 a 属性转化为 getter 和 setter,可以实现 obj.a 的数据监控,Vue正式基于这个特性实现了响应式。 Vue 会遍历对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter

由于javascript的限制,Object.defineProperty()不能监测到数组的改变,vue对数组和对象使用了2种不同的方式实现,对于Object类型来说,通过劫持getter和setter来实现监测改变;对于Array来说,通过拦截器,拦截数组相关api(push、pop、shift、unshift...)来实现监测改变。所以在这里我们可以发现通过数组的下标去改变数组的某一项或者直接改变数组的长度的时候因为vue没有做这方面的处理而Object.defineProperty又监听不到所以vue是无法对此进行监听的

 

然后是观察者模式

vue是基于观察者模式来实现数据更新之后触发一系列的相关依赖来自动更新视图。那么先来了解一下什么是观察者模式,观察者模式是指一个对象维持一系列的依赖于他的对象,将有关状态变更自动的通知给他们。 观察者模式的基本要素

  • Subject (目标)
  • Observer (观察者)

 

定义一个收集所有依赖的容器

Watcher是一个中介的角色,数据发生变化时通知它,然后它再通知其他地方。 他就是负责具体的脏活累活

  • 1、收集依赖
  • 2、负责执行cb来更新所有的依赖


总结

vue如何实现响应式?具体实现上对象和数组稍有不同:

  • 1、对象:在create阶段,会递归的将data中的数据递归的添加get、set访问器属性,页面在mount阶段会创建全局的Watcher,并且mount阶段需要执行render渲染,会调用页面数据对应的get函数,每个数据的key都有对应的dep依赖,执行dep.depend()时会将 将dep 添加至当前watcher的subs队列中去。当页面数据更新后,调用set函数,执行通知。
  • 2、数组:在create阶段,如果是数组类型时,给会执行数组改变方法添加拦截器,同时也会给数据添加get和set访问器属性,只是数组改变时并不会触发set函数,页面在mount阶段执行render,调用数据对应的get函数,并调用childObj.dep.depend()收集watcher,(childObj.dep是什么?在初始化的data的时候会递归的将array转成observer,所以childObj.dep指的是数组array的依赖)。在array数据更新之后,会执行拦截器中的__obj__.dep.notify()执行通知,set并不会触发。

通知之后页面怎么更新渲染? 当发送通知之后,会将watcher添加至队列中由vue统一调度执行更新,后期vue将会进行patch,对比虚拟dom,以当前页面组件级别做一个整体更新。