Vue的数据响应式

120 阅读4分钟

Vue2文档中的内在一节的深入响应式原理中所述,“Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。这使得状态管理非常简单直接,不过理解其工作原理同样重要,这样你可以避开一些常见的问题。”我们来看看数据更新的常见的问题。

一、数据响应式常见问题

用户更改数据,视图自动刷新,UI响应数据的变化。const vm = new Vue({data: {n: 0}}),如果修改data,会导致UI中的n响应,就是响应式的。

import Vue from "vue/dist/vue.js"; 
Vue.config.productionTip = false;

let vm = new Vue({
  data: {
    obj: {
      a: 1,
      message: undefined
    },
  },
  template:`<div>{{obj.message}}</div>`
}).$mount("#app");

vm.obj.message = 'xxxxx'

可以看到确实可以看到页面中变为xxxxx了,如果想要看到初始的message可以加个setTimeout来看。那是不是我们的message改变都能监听到并且作出响应呢?看下面的例子:

import Vue from "vue/dist/vue.js"; 
Vue.config.productionTip = false;

let vm = new Vue({
  data: {
    obj: {
      a: 1,
    },
  },
  template:`<div>{{obj.message}}</div>`
}).$mount("#app");

vm.obj.message = 'xxxxx'

可以看到并没有响应,当obj中初始没有message是,我们直接对obj加上message属性页面是不会被监听并且响应的。这是由于Vue的数据响应式是基于Object.defineProperty,而对象属性的添加和删除无法被Object.defineProperty监听。

我们如何让Vue将它转化为响应式呢。第一种方法,在data对象声明中考虑到要添加的。由于 Vue 不允许动态添加根级响应式 property,所以你必须在初始化实例前声明所有根级响应式 property,哪怕只是一个空值,本例中可以把属性message设为undefined。

import Vue from "vue/dist/vue.js"; 
Vue.config.productionTip = false;

let vm = new Vue({
  data: {
    obj: {
      a: 1,
      message: undefined,
    },
  },
  template:`<div>{{obj.message}}</div>`
}).$mount("#app");

vm.obj.message = 'xxxxx'

第二种方法是使用Vue提供了特定的方法Vue.set(object, propertyName, value) 向嵌套对象添加响应式 property。还可以使用vm.set实例方法,它是全局Vue.set方法的别名。vm.set实例方法,它是全局 Vue.set方法的别名。vm.set的作用是新增响应式 property,自动创建监听和代理(通过Object.defineProperty给对象添加getter和setter,getter和setter用于对属性的读写进行监控),从而触发UI更新(异步)。

import Vue from "vue/dist/vue.js"; 
Vue.config.productionTip = false;

let vm = new Vue({
  data: {
    obj: {
      a: 1,
    },
  },
  template:`<div>{{obj.message}}</div>`
}).$mount("#app");

// Vue.set(vm.obj,'message','xxxxx')
vm.$set(vm.obj,'message','xxxxx')

vm.obj.message = 'xxxxx'

如果需要为数字元素重新赋值,可以使用 vm.$set(array, indexOfItem, newValue) 方法,当data中有数组很大时,不能提前声明所有的Key怎么办呢?

import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;

new Vue({
  data: {
    array: ["a", "b", "c","d"]
  },
  template: `
    <div>
      {{array}}
      <button @click="setE">set e</button>
    </div>
  `,
  methods: {
    setE() {
      this.array[4] = "e"; 
    }
  }
}).$mount("#app");

我们按下set e的按钮不会有变化,UI并没有刷新,Vue通过 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些方法包括push()、pop()、shift()、unshift()、splice()、sort()和reverse()。你可以在Vue2教程中列表渲染的数组更新检测的变更方法找到他们。

我们可以将this.array[4] = "e"; 改为 this.array.push('e'),可以看到UI确实更新了。

setE() {
       // this.array[4] = "e"; 
      this.array.push('e')
    }

我们将本例的array打印出来,发现它已经不是我们所熟知的数组了,这应该是尤大所说的变更方法吧。原型链中多了一层,其中正好是上面提及的7个方法。

为什么我们使用数组时,直接使用下标赋值无法被监听呢?这是出于性能来考虑的,对象和数组如果要监听每个元素,就是对其每个属性使用Object.defineProperty进行劫持,而数组监听key是以数组为数字下标的key,数组的数据量可能特别大,就可能非常耗费性能,Vue没有对元素的下标进行响应式处理,而是补充了7个变更的数组API,通过对数组原型链上的7个方法进行劫持,从而执行响应式处理。

二、关于Object.defineProperty

2.1、属性描述符

我们写一个普通的对象,但是这个普通对象对应的属性描述符可不仅仅只有一个value数据值哦,还有三个其他的特性:writable(可写)、configurable(可配置)和enumerable(可枚举)。

let myObject = {a: 2}
Object.getOwnPropertyDescriptor(myObject,"a")

我们可以通过下面方式来设置属性的值,但是只要的目的肯定不仅仅是设置值,而是为了修改其他三个值。

let myObject = {}
Object.defineProperty(myObject,'a',{
  value: 2,
  writable: true, // 是否可修改属性的值
  configurable: true, // 是否可配置,configurable: false是单向操作不可撤销
  enumerable: true // 是否可枚举
})

myObject.a 

2.2、Getter和Setter

getter和setter可以改写默认操作,但是只能应用在单个属性上,getter会在获取属性值的时候调用,而setter会在设置属性值的时候调用,二者都是隐藏函数。当为一个属性定义了getter或者setter或者二者都有的话,JS会忽略value和writable的特性,而去关心get和set还有是否可枚举、可配置。

let myObject = {
  get b(){return 2}
}
Object.defineProperty(myObject,'a',{
  get: function(){return this.b * 2},
  enumerable: true
})
myObject.b // 2
myObject.a // 4

myObject.a = 5 // 5
myObject.a // 4

可以通过对象文字语法get a(){}和defineProperty()显式定义。都会在对象中创建一个不包含值的属性,对这个属性的访问会自动调用这个隐藏函数。只设置getter没有设置setter,所以我们对a的值进行set操作会忽略赋值操作。我们设置了getter还应该设置setter才能让属性更合理。

let myObject = {
  get a(){
    return this._a_
  },
  set a(val){
    this._a_ = val* 2
  }
}

myObject.a = 2  // 2
myObject.a  // 4

我们对a进行赋值操作时,setter会覆盖单a的默认的赋值操作。

重要参考:

深入响应式原理 — Vue.js

Vue 响应式原理剖析 —— 数据更新常见问题 | Kayo's Melody