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/getter
vm._data.student.age = 18;
vm.student.age = 18;
vue提供了set()
方法可以动态添加vue识别的响应式属性:
// 参数:target、添加的属性、属性值
// 这样为student添加的sex属性,和之前的name属性没有区别,也一样有setter、getter
Vue.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/setter
vm._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添加一个属性sex
this.$set(this.student, 'sex', '男');
}
addFriend() {
// 向数组开头插入一个元素,添加进来的对象也可以正常修改里面的name、age
this.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
。