Vue监视数据改变

问题:

  1. <body>
  2. <div id="app">
  3. <h2>人员列表</h2>
  4. <!-- 点击按钮后,马冬梅信息变成马老师 -->
  5. <button @click="updateMei">更新马冬梅信息</button>
  6. <ul>
  7. <li v-for="(p,index) of persons">
  8. {{p.name}}-{{p.age}}-{{p.sex}}
  9. </li>
  10. </ul>
  11. </div>
  12. </body>
  13. <script>
  14. Vue.config.productionTip = false;
  15. const vm = new Vue({
  16. el: '#app',
  17. data: {
  18. persons: [
  19. {id:'001', name:'马冬梅', age: 19, sex:'女'},
  20. {id:'002', name:'周冬雨', age: 20, sex:'女'},
  21. {id:'004', name:'温兆伦', age: 22, sex:'男'},
  22. {id:'003', name:'周杰伦', age: 21, sex:'男'}
  23. ]
  24. },
  25. methods: {
  26. updateMei() {
  27. this.persons[0].name = '马老师';
  28. this.persons[0].age = 50;
  29. this.persons[0].sex = '男';
  30. }
  31. },
  32. });
  33. </script>

但是当updateMei()改成直接修改数组时,Vue就监测不到了:

  1. methods: {
  2. updateMei() {
  3. // 数组对象正常被修改了,但是Vue监测不到
  4. this.persons[0] = {id:'001', name:'马老师', age: 50, sex:'男'}
  5. }
  6. },

手写数据代理

模仿vue的new Vue数据代理:

  1. let data = {
  2. name: '张三',
  3. age: 10
  4. };
  5. const obs = new Observer(data);
  6. // 准备一个vm实例
  7. let vm = {};
  8. vm._data = data = obs;
  9. console.log(vm);
  10. function Observer(obj) {
  11. const keys = Object.keys(obj);
  12. keys.forEach((k) => {
  13. Object.defineProperty(this, k, {
  14. get() {
  15. return obj[k];
  16. },
  17. set(val) {
  18. console.log(`${k}被修改了`)
  19. obj[k] = val;
  20. }
  21. })
  22. })
  23. }

此时vm._data和传入的用户data完全一样,当vm._data里面的属性修改时,马上就可以被对应的setter捕获到,可以在setter中进行方法增强。

相比vue,还缺少的功能:

  1. vue中,除了可以用vm._data.age操作属性修改,还可以直接使用vm.age等操作属性修改
  2. vue中,data的属性可以是对象,vue使用了递归获取data所有层级的属性。

Vue的set()方法

Vue中,data的一级属性如果缺少会报错,但是二级属性如果缺少则不会报错。因为二级属性如果缺少会变成undefined,vue默认把undefined处理为空。

例如:

  1. <div id='app'>
  2. <!-- data中没有直接的teacher属性,此处会报错 -->
  3. <h2>{{teacher}}</h2>
  4. <!-- data中存在student属性,但是student不存在age属性,此时不会报错,只是显示为空 -->
  5. <h2>{{student.age}}</h2>
  6. </div>
  7. <script>
  8. const vm = new Vue({
  9. el: '#app',
  10. data: {
  11. student: {
  12. name: 'tom'
  13. }
  14. }
  15. })
  16. </script>

此时,如果要为student对象添加age属性,使用js直接添加Vue不识别,不是响应式的属性:

  1. // 下面两种写法,虽然可以给student对象正常添加上age属性,但是vue不识别,vue不会给添加的age属性生成setter/getter
  2. vm._data.student.age = 18;
  3. vm.student.age = 18;

vue提供了set()方法可以动态添加vue识别的响应式属性:

  1. // 参数:target、添加的属性、属性值
  2. // 这样为student添加的sex属性,和之前的name属性没有区别,也一样有setter、getter
  3. Vue.set(vm.student, 'sex', 18);
  4. // 或者使用vm的$set方法
  5. vm.$set(vm.student, 'sex', 18);

vue的set()方法只能给data的二级属性添加属性,不能直接给data添加属性:

  1. // 此时会报错,只能给data的二级属性添加属性,不能直接添加到data上
  2. Vue.set(vm._data, 'address', 'China');
  3. // 所以,set方法的第一个参数不能是vm/vm._data

Vue中数组数据监测

Vue不会对数组对象的数组元素生成setter、getter,所以数组元素直接修改时Vue监测不到。

  1. const vm = new Vue({
  2. data: {
  3. student: {
  4. name: 'tom',
  5. friends:['jarry', 'tonny', 'marry']
  6. }
  7. }
  8. });
  9. // 数组第0个元素的值正常修改成了jany,但是vue监测不到这个变化,页面还是会展示jarry。
  10. // 因为数组的索引值不会被生成getter/setter
  11. vm._data.student.friends[0] = 'jany';

但是,调用js中操作数组的对应方法去修改数组数据,vue就可以正常监测到了:

  • push:在末尾追加
  • pop:删除最后一个元素
  • shift:删除第一个元素
  • unshift:数组头上插入一个元素
  • splice:在指定位置插入/替换元素
  • sort:数组排序
  • revese

因为filter方法不会影响原数组的元素,所以没有filter。

对象数组使用者几个方法添加修改元素时,添加上来的也是响应式的。

  1. const vm = new Vue({
  2. data: {
  3. student: {
  4. friends:[
  5. {name: 'jerry', age: 20},
  6. {name: 'tony', age:26}
  7. ],
  8. hobby: ['踢球', '看电视', '听音乐']
  9. }
  10. },
  11. method: {
  12. addSex() {
  13. // 向student添加一个属性sex
  14. this.$set(this.student, 'sex', '男');
  15. }
  16. addFriend() {
  17. // 向数组开头插入一个元素,添加进来的对象也可以正常修改里面的name、age
  18. this.student.friends.unshift({name: 'jack', age: 70})
  19. },
  20. updateFriend() {
  21. // 这里正常修改数组元素下的属性,vue也可以正常监测到
  22. this.student.friends[0].name = '张三';
  23. },
  24. addHobby() {
  25. // 向爱好数组中末尾添加一个元素
  26. this.student.hobby.push('读书');
  27. },
  28. updateHobby() {
  29. // 将第一个爱好改为开车
  30. this.student.hobby.splice(0, 1, '开车');
  31. // 或者使用set()方法,将数组对象的第0项改为开车
  32. // this.$set(this.student.hobby, 0, '开车');
  33. }
  34. }
  35. });

Vue监视数据的原理

Vue会监视data中所有层次的数据。

通过setter实现监视,且要在 new Vue时就传入要监测的数据:

  • 对象中使用 js 直接追加的属性,vue默认不做响应式处理

  • 如果要给后添加的属性做响应式,需要使用对应的API

    1. Vue.set(target, propertyName/index, val);
    2. vm.$set(target, propertyName/index, val);

Vue对数组中数据的监视,通过包裹数组更新元素的方法(例如splica/push/pop等)实现,本质就是做了两件事:

  • 调用原生对应的方法对数组进行更新
  • 重新解析模板,进行更新页面

在Vue修改数组中的某个元素一定要使用如下方法:

  • pushpopshiftunshiftsplicesortreverse
  • 或者使用:Vue.set()vm.$set()

Vue.set()vm.$set()不能给vm或者vm的根数据对象添加属性,即方法的第一个参数不能是vm/vm._data