原因是因为:
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,以当前页⾯组件级别做⼀个整体更新。
链接:https://juejin.cn/post/6854573221526634509
因篇幅问题不能全部显示,请点此查看更多更全内容