Vue数据响应式

218 阅读2分钟

Vue 最独特的特性之一,是其非侵入性的响应式系统,数据模型仅仅是普通的 JavaScript 对象,而当你修改它们时,视图会进行更新,能够及时地反映在页面上,使得数据状态的管理更加直接。那么Vue是怎么做到这样的数据响应式呢?

Getter和Setter

Getter和Setter相当于对一个对象的某个属性的监控,当读取属性时调用Getter,修改属性时调用Setter,通过Object.defineProperty()方法进行定义,定义和使用的方式如下代码所示:

let obj = {}
let _n = 1
Object.defineProperty(obj, 'a', {
  get() {
      return _n
    },
  set(value) {
      _n = value
    }
})
obj.a = 3 //调用set函数
console.log(obj.a) //obj.a调用get函数

Vue如何追踪变化

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter,这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。(Object.defineProperty是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。)

Vue对于对象和数组的处理

当我们在写数组或者对象的时候,通常情况下并不能精确预估到所有的属性值,对于数组,我们通常不能判断一共有多少个数据,对于对象,我们通常也不能完全明确拥有多少属性,因此不能将所有属性在Vue实例的data选项里列举出来,导致Vue并不能将那些没有列举出的属性转为getter/setter,从而不能达到响应式处理,怎么解决呢?

对于对象

对于已经创建的Vue实例,Vue 不允许动态添加“根级别”的响应式 property,但是,可以使用 Vue.set(object, propertyName, value) 或者 this.$set(object, propertyName, value) 方法向嵌套对象添加响应式 property,如下所示:

var vm = new Vue({
  data: {
    obj:{a: 1}
  }
})

// `vm.a` 是响应式的

vm.obj.b = 2
// `vm.obj.b` 是非响应式的

Vue.set(vm.obj, 'b', 2)
//此时的 `vm.obj.b` 是响应式的

对于数组

Vue 不能检测以下数组的变动:

1.当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue

2.当你修改数组的长度时,例如:vm.items.length = newLength

解决方式一:类似于对象的处理,使用Vue.set(object, propertyName, value) 或者 this.$set(object, propertyName, value) 方法:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[3] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的
Vue.set(vm.items, '3', 'd')  //是响应性的

解决方式二:Vue重写了数组的push、pop、shift、unshift等方法(称为数组的变异方法),能够让你对数组进行这些操作时添加响应性,例如下例中使用push方法:

let vm = new Vue({
  data: { arr: [1, 2, 3] },
  template: `
  <div>{{arr}}
  </div>
  `,
}).$mount("#app");
vm.arr.push(4); //是响应性的

由于 Vue 不允许动态添加根级响应式 property,所以最好在初始化实例前声明所有根级响应式 property,就算只是一个空值也可以,如下:

var vm = new Vue({
    data: {
      // 声明 message 为一个空值字符串
      message: ''
    },
    template: '<div>{{ message }}</div>'
  })
  // 之后设置 `message`
vm.message = 'Hello!'