Vue监视数据改变
问题:
<body><div id="app"><h2>人员列表</h2><!-- 点击按钮后,马冬梅信息变成马老师 --><button @click="updateMei">更新马冬梅信息</button><ul><li v-for="(p,index) of persons">{{p.name}}-{{p.age}}-{{p.sex}}</li></ul></div></body><script>Vue.config.productionTip = false;const vm = new Vue({el: '#app',data: {persons: [{id:'001', name:'马冬梅', age: 19, sex:'女'},{id:'002', name:'周冬雨', age: 20, sex:'女'},{id:'004', name:'温兆伦', age: 22, sex:'男'},{id:'003', name:'周杰伦', age: 21, sex:'男'}]},methods: {updateMei() {this.persons[0].name = '马老师';this.persons[0].age = 50;this.persons[0].sex = '男';}},});</script>
但是当updateMei()改成直接修改数组时,Vue就监测不到了:
methods: {updateMei() {// 数组对象正常被修改了,但是Vue监测不到this.persons[0] = {id:'001', name:'马老师', age: 50, sex:'男'}}},
手写数据代理
模仿vue的new Vue数据代理:
let data = {name: '张三',age: 10};const obs = new Observer(data);// 准备一个vm实例let vm = {};vm._data = data = obs;console.log(vm);function Observer(obj) {const keys = Object.keys(obj);keys.forEach((k) => {Object.defineProperty(this, k, {get() {return obj[k];},set(val) {console.log(`${k}被修改了`)obj[k] = val;}})})}
此时vm._data和传入的用户data完全一样,当vm._data里面的属性修改时,马上就可以被对应的setter捕获到,可以在setter中进行方法增强。
相比vue,还缺少的功能:
- vue中,除了可以用
vm._data.age操作属性修改,还可以直接使用vm.age等操作属性修改 - vue中,
data的属性可以是对象,vue使用了递归获取data所有层级的属性。
Vue的set()方法
Vue中,data的一级属性如果缺少会报错,但是二级属性如果缺少则不会报错。因为二级属性如果缺少会变成undefined,vue默认把undefined处理为空。
例如:
<div id='app'><!-- data中没有直接的teacher属性,此处会报错 --><h2>{{teacher}}</h2><!-- data中存在student属性,但是student不存在age属性,此时不会报错,只是显示为空 --><h2>{{student.age}}</h2></div><script>const vm = new Vue({el: '#app',data: {student: {name: 'tom'}}})</script>
此时,如果要为student对象添加age属性,使用js直接添加Vue不识别,不是响应式的属性:
// 下面两种写法,虽然可以给student对象正常添加上age属性,但是vue不识别,vue不会给添加的age属性生成setter/gettervm._data.student.age = 18;vm.student.age = 18;
vue提供了set()方法可以动态添加vue识别的响应式属性:
// 参数:target、添加的属性、属性值// 这样为student添加的sex属性,和之前的name属性没有区别,也一样有setter、getterVue.set(vm.student, 'sex', 18);// 或者使用vm的$set方法vm.$set(vm.student, 'sex', 18);
vue的set()方法只能给data的二级属性添加属性,不能直接给data添加属性:
// 此时会报错,只能给data的二级属性添加属性,不能直接添加到data上Vue.set(vm._data, 'address', 'China');// 所以,set方法的第一个参数不能是vm/vm._data
Vue中数组数据监测
Vue不会对数组对象的数组元素生成setter、getter,所以数组元素直接修改时Vue监测不到。
const vm = new Vue({data: {student: {name: 'tom',friends:['jarry', 'tonny', 'marry']}}});// 数组第0个元素的值正常修改成了jany,但是vue监测不到这个变化,页面还是会展示jarry。// 因为数组的索引值不会被生成getter/settervm._data.student.friends[0] = 'jany';
但是,调用js中操作数组的对应方法去修改数组数据,vue就可以正常监测到了:
- push:在末尾追加
- pop:删除最后一个元素
- shift:删除第一个元素
- unshift:数组头上插入一个元素
- splice:在指定位置插入/替换元素
- sort:数组排序
- revese
因为filter方法不会影响原数组的元素,所以没有filter。
对象数组使用者几个方法添加修改元素时,添加上来的也是响应式的。
const vm = new Vue({data: {student: {friends:[{name: 'jerry', age: 20},{name: 'tony', age:26}],hobby: ['踢球', '看电视', '听音乐']}},method: {addSex() {// 向student添加一个属性sexthis.$set(this.student, 'sex', '男');}addFriend() {// 向数组开头插入一个元素,添加进来的对象也可以正常修改里面的name、agethis.student.friends.unshift({name: 'jack', age: 70})},updateFriend() {// 这里正常修改数组元素下的属性,vue也可以正常监测到this.student.friends[0].name = '张三';},addHobby() {// 向爱好数组中末尾添加一个元素this.student.hobby.push('读书');},updateHobby() {// 将第一个爱好改为开车this.student.hobby.splice(0, 1, '开车');// 或者使用set()方法,将数组对象的第0项改为开车// this.$set(this.student.hobby, 0, '开车');}}});
Vue监视数据的原理
Vue会监视data中所有层次的数据。
通过setter实现监视,且要在 new Vue时就传入要监测的数据:
对象中使用 js 直接追加的属性,vue默认不做响应式处理
如果要给后添加的属性做响应式,需要使用对应的API
Vue.set(target, propertyName/index, val);vm.$set(target, propertyName/index, val);
Vue对数组中数据的监视,通过包裹数组更新元素的方法(例如splica/push/pop等)实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进行更新页面
在Vue修改数组中的某个元素一定要使用如下方法:
push、pop、shift、unshift、splice、sort、reverse- 或者使用:
Vue.set()、vm.$set()
Vue.set()、vm.$set()不能给vm或者vm的根数据对象添加属性,即方法的第一个参数不能是vm/vm._data。
