vue2.x之监测数据
监测对象
我们在前面已经了解到,vue监测对象是使用Object.defineProperty()来进行数据劫持的,这样就给数据添加了一个代理,每次对象的属性被修改时就会调用setter, 对象的属性被获取的时候就会调用getter。
下面我们简单地实现一下vue的响应式
let obj = { name: '北京吴彦祖', age: 18, job: { name: 'coder', weekdays: 5 } } // 创建一个监视的实例对象,用于监视data属性的变化 const reactiveObj = new Observer(obj) let vm = {} vm._data = obj = reactiveObj function Observer (obj) { // 这个类里面就需要给每个属性都添加响应式 const keys = Object.keys(obj) keys.forEach(key => { Object.defineProperty(this, key, { get () { console.log(`${this}上的${key}正在被读取`) return obj[key] }, set (value) { console.log(`${this}上的${key}正在被修改`) obj[key] = value } }) }) }
⏰ 特别说明一下:
为什么Object.defineProperty()第一个参数是this,而不是obj。是因为这个this拿到的是实例化对象,也就是上述代码中的reactiveObj(代理之后的对象)。如果写成obj的话,那么get里面返回值是obj[key],也就是说再次读取obj的属性值,这样的话就会造成一个死循环,最终导致内存溢出。
监测数组
vue里面监听数组的方式其实是对Array.property上的数组的方法进行了封装。换句话说,在vue里面访问数组的这些方法已经不再是访问数组原型上的那些方法了。具体有以下这些方法
- push()
- pop()
- shift()
- unshift()
- sort()
- splice()
- reverse()
<template> <div id="app"> <div>姓名:{{ obj.name }}</div> <div>年龄:{{ obj.age }}</div> <ul> <li v-for="(item,index) in obj.leaders" :key="index"> {{ item.name }} - {{ item.age }} </li> </ul> <button @click="addLeader">添加领导信息</button> </div> </template> <script> export default { name: 'App', data() { return { obj: { name: '北京吴彦祖', age: 18, leaders: [ { name: '一个专政的独裁则', age: 40 }, { name: '一个温柔的管理者', age: 35 } ] } } }, methods: { addLeader() { let newLeader = { name: '仙女🧚♀️领导', age: 30 } this.obj.leaders.push(newLeader) this.obj.leaders.pop() this.obj.leaders.shift() this.obj.leaders.unshift(newLeader) this.obj.leaders.splice(0, 1) this.obj.leaders.sort((a, b) => a - b) this.obj.leaders.reverse() this.obj.leaders = [{ name: '仙女领导', age: 30 }] this.obj.leaders[0].name = '仙女领导' }, }, } </script>
上面除了重写的修改数组的七个方法外,重新给数组赋值,或者给数组的某些项的属性重新设置值都是会触发响应式的。
⏰下面我们来看一下具体原理
主要就是继承Array,然后重写数组上的方法
// 继承Array原型上的所有属性 const extendArr = Object.create(Array.prototype) const arrMethods = [ 'push', 'pop', 'shift', 'unshift', 'sort', 'splice', 'reverse' ] // 重新包装上述数组的方法 arrMethods.forEach(item => { const oldItem = Array.prototype[item] const newItem = function (...args) { oldItem.apply(this, args) } extendArr[item] = newItem })
只有上述这几种方法才具有响应式
手动更新视图
对于对象类的数据,如果初始化data的时候没有写某个属性,而是通过methods里的方法给他添加属性,不是响应的,视图不会及时更新。 对于数组类的数据,如果使用了上面的那七种方法之外的方法,那么视图也是不会刷新的。比如使用了concat, filter等。 对于这种问题,vue给我们提供了一种方法this.$set()。
⏰ vue.set()是向响应式对象添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为vue无法探测普通的新增属性。
🌰示例
<template> <div id="app"> <div>姓名:{{ obj.name }}</div> <div>年龄:{{ obj.age }}</div> <div>工作:{{ obj.job }}</div> <button @click="addJob">添加职位信息</button> </div> </template> <script> export default { name: 'App', data() { return { obj: { name: '北京吴彦祖', age: 18, } } }, methods: { addJob() { this.obj.job = 'coder', console.log('职位',this.obj.job); }, }, } </script>
效果如下:
数据改变了,页面视图没有更新。
使用全局set
语法:
Vue.set( target, propertyName/index, value )
代码:
<template> <div id="app"> <div>姓名:{{ obj.name }}</div> <div>年龄:{{ obj.age }}</div> <div>工作:{{ obj.job }}</div> <button @click="addJob">添加职位信息</button> </div> </template> <script> import Vue from 'vue' export default { name: 'App', data() { return { obj: { name: '北京吴彦祖', age: 18, } } }, methods: { addJob() { Vue.set(this.obj,'job','coder') console.log('职位',this.obj.job); }, }, } </script>
效果:
使用组件set
语法:
vm.$set( target, propertyName/index, value )
代码:
<template> <div id="app"> <div>姓名:{{ obj.name }}</div> <div>年龄:{{ obj.age }}</div> <div>工作:{{ obj.job }}</div> <button @click="addJob">添加职位信息</button> </div> </template> <script> export default { name: 'App', data() { return { obj: { name: '北京吴彦祖', age: 18, } } }, methods: { addJob() { this.$set(this.obj, 'job', 'coder') console.log('职位',this.obj.job); }, }, } </script>
效果:
❗️ 我们在实际开发项目中,主要还是组件化开发,所以使用this.$set()的情况居多。
小结
vue监视数据的原理:
1. vue会监视data所有对象的所有层级
2. 监测对象中的数据: 通过setter实现监视,且要在new Vue时就传入要监测的数据
- 对象中后追加的属性,vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用:Vue.set( target, propertyName/index, value )或者vm.$set( target, propertyName/index, value )
3.监测数组中的数据: 通过包裹数组更新元素的方法实现,本质上就做了两件事:
- 调用原生对应的方法push(), pop(), shift(), unshift(), sort(), splice(), reverse()对数组进行更新
- 重新解析模板,进而更新页面