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()对数组进行更新
  • 重新解析模板,进而更新页面
 

posted on 2024-07-09 22:26  梁飞宇  阅读(9)  评论(0)    收藏  举报