VUE2 学习笔记6 vue数据监测原理
Vue监测数据改变的原理
数据:指的是存在data中的数据。
如果我们修改了data中存储的数据,Vue会监测到这种修改,并且把修改反应到页面上。
Vue的这种监测是通过Vue上默认的监视实现的。与watch不同,watch是Vue提供给开发者使用的方法。但不管是默认监视,还是watch,背后的原理都类似。
了解Vue的监视原理是很重要的,如果不知道,当自己写的某种数据修改不能被Vue识别到,就很难去分析成因。
一种Vue无法监视到数据修改的情况:
如果用访问下标的方法替换数组中某个对象元素,把整个元素都替换成新的,虽然后台数据已经修改了,但Vue无法检测到这种修改,因此页面的数据也无法改变。
而且,有时候,页面上的数据没有改变,但通过控制台输出修改后的数据,再打开开发者工具,开发者工具中的数据会发生改变,但页面上的内容仍然不变,这种错乱的现象令人迷惑。
- {{ data.name }}:{{data.age}} -- {{index}}
.styleBackgroundColor{ background-color: aqua; } .styleContent{ width:300px; height: 200px; } .styleBorder{ border: 2px black solid; } let vm = new Vue({ el: \'#root\', data() { return { listData:[ {name:\'catcat\',age:5,id:\'001\'}, {name:\'dogcat\',age:8,id:\'002\'}, {name:\'QAQ\',age:15,id:\'003\'}, {name:\'QAQTAT\',age:15,id:\'004\'}, {name:\'123\',age:15,id:\'005\'}, ], } }, methods:{ changeData(){ this.listData[0] = {name:\'test\',age:\'changed\',id:\'001\'}; } } })
在这个例子中,页面上显示原始数组信息,当点击change按钮时,修改数组中的第一条数据,但这时令人迷惑的情况发生了,页面上的数据并不会发生改变,但在控制台打印listData数组,后台的数据已经被更新,这种更新并没能传递到页面上:
这与Vue的监测机制相关。
Vue如何监测对象数据的改变:
Vue会对data中的数据加工,生成_data,_data中为每个属性都添加了set和get,当data中数据改变时,会触发数据对应的set函数,set函数的执行代表有数据发生变化,会引起Vue模版的重新解析,Vue模版会使用新的数值解析指令,最终在页面上显示出新的数据。
要对数据进行监测,要靠Observer函数进行实现,Observer函数是一个构造函数,能够创建一个监视的实例对象。
在Observer构造函数内部,首先会汇总对象中所有属性,并形成数组。然后会遍历这个数组,为Observer实例对象添加所有属性,对每个属性设置get和set,在get函数内部,返回被监视data的属性值,在set函数内部,把被监视data的属性值修改掉。
简单的模拟代码如下(实际vue底层代码要更复杂和完善一些):
let data = { name: \'catcat\', age: 10, } function Observer(obj){ //获取数据中的所有属性 let keys = Object.keys(obj); for(const k of keys){ //把所有属性添加到Observer实例上,并且增加set和get方法 Object.defineProperty( this, k , { get(){ return obj[k]; }, set(value){ console.log(\'发现修改,重新进行模版解析等行为\'); obj[k] = value; } } ) } } //创建监听实例 let obs = new Observer(data); const vm = {}; vm._data = data = obs;
对于这段代码,当前对data数据只考虑了一层数值,当data数值是对象时,当前的代码对对象并不生效。但Vue会对对象及对象内嵌套的对象进行递归,为每层的数据都添加get和set,直到没有层级结构。Vue对藏在数组中的对象,也都为对象的属性赋予了get和set。
对于下列这种数据:
const vm = new Vue({ data:{ a:{ f:100, b:{ g:\'123123\', c:{ d:1, e:2, } } }, list:[ { name:\'a\',age:1, }, { name:\'n\',age:2, } ], }, })
可以看到a以及内层的bf,b内层的cg,c内层的de,上面都有get和set。
对于list,list内部的对象属性也有get和set。
Vue.set
对于data中的数据,在vue实例创建阶段配置好的数据,是响应式的,但对于后加入的数据,由于没有set和get,并不是响应式的。
比如:
{{a.cat}}
const vm = new Vue({ el:\'#id\', data:{ a:{ f:100, b:{ g:\'123123\', c:{ d:1, e:2, } } } }, })
对于这段代码,页面上显示了data中的a.cat,如果后续给a添加了cat属性,页面上还是无法显示出cat,因为后添加的cat并不是响应式的,页面无法监测到后台新加了数据。
如果希望后添加的数据也有响应式,需要使用Vue上提供的一个api。
Vue.set(target 往谁的身上追加属性,key 要追加的属性名 字符串格式,value 追加的属性值)
除了Vue.set,Vue实例对象上也有一个相同功能的方法,vm.$set。语法是一模一样的。
要找到追加属性的位置,可以不用vm._data.a,用vm.a也可以。
{{a.cat}}
const vm = new Vue({ el:\'#id\', data:{ a:{ f:100, } }, methods:{ add(){ Vue.set(this.a,\'cat\',\'ttt\'); //this.$set(this.a,\'cat\',\'ttt\'); } } })
不过,set Api也有一些缺点。set Api只能给data中某一个对象追加属性,不能够给data追加属性。vm和vm._data不能作为target。
Vue如何监测数组数据的改变:
对于数组数据,vue不会为数组内部的元素添加set和get。Vue对数组其实也有监视机制,只不过不是通过set和get进行,因此通过下标修改数组元素,Vue不会监听到这种修改。
{{data}}
const vm = new Vue({ el:\'#id\', data:{ list:[ \'123\', {name:\'a\',age:2}, 100 ] }, methods:{ change(){ console.log(\'change\'); this.list[0] = \'aa\'; } } })
页面上有change按钮,当点击按钮的时候修改数组第一条数据,但点击按钮之后,页面上的数据并不会发生变化。
当对数组数据使用push pop shift unshift splice sort reverse这些能够修改数组本身的方法时,Vue才能监测到。
methods:{ change(){ console.log(\'change\'); this.list.shift(); this.list.unshift(\'aa\'); } }
对于不修改数组本身的方法,比如filter,只使用filter也是无法被vue监测到的,需要把filter的返回值赋值给整个数组,vue才能监测到这种修改。
因此,回到最开始的问题,为什么把数组第一条数据用下标修改之后,页面上的数据无法进行更新,是因为vue无法监测到对下标访问的修改。
vue是如何监测到对数组使用了修改数组本身的方法:vue使用了包装的手段,也就是说,在vue中调用数组的push等方法,这时使用的并不是Array原型对象上的push,而是vue自己写的push。在vue的push中,一开始还是调用了array原型上的push,然后,vue会重新解析模版,生成新的虚拟DOM,diff算法,然后更新页面。
除了使用push pop等方法修改数组,能实现响应式,用Vue.set vm.$set Api也可以响应式修改数组。
数据劫持:对于一个数据,把数据及数据内层所有子变量都赋予一个get和set,把这种动作行为叫做劫持。当有人修改或读取数值时,都会被劫持,进入set或get。