vue的响应式原理 (官网总结)

一、如何追踪变化
一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProPerty 把这些 property 全部转为 getter/setter。
Object.defineProperty() getter/setter
Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也是Vue 不支持 IE8 以及更低版本游览器的原因。

  • shim可以将新的API引入到旧的环境中,而且仅靠就环境中已有的手段实现。
    意思就是,Object.defineProperty这个特性是无法使用低级浏览器中的方法来实现的,所以Vue不支持IE8以及更低版本的浏览器。

这些 getter/setter 对于用户来说是不可见的,但是在内部它们让 vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的时不同游览器在控制台打印数据对象时对 getter/setter 的格式化并不同。

每个组件实例对于一个 watcher 实例,它会在组件渲染的过程中 ”接触“ 过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

二、检测变化

1、对于对象

var vm = new Vue({
	data: {
		a: 1
	}
})
// 如果我们要改变a的值
vm.a = 2 // 是响应式的

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

// 对于已经创建的实例,vue不允许动态添加根级别的响应式property。可以使用 Vue.set(object, propertyName, value) 方法嵌套对象添加响应式property。如:
Vue.set(vm.someObject, 'b', 2)
// 也可以使用 vm.$set 实例方法,也是全局Vue.set 方法的别名
this.$set(this.someObject, 'b', 2)

// 如需要为已有对象赋值多个新 property,比如使用 Object.assign() 或 _.extend()。
// 但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,应该用原对象与混合进去的对象的 property 一起创建一个新的对象
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

2、对于数组
Vue 不能检测以下数组的变动:
① 当你利用索引直接设置一个数组项时:例如:vm.items[indexOfItem] = newValue
② 当你修改数组的长度时,例如:vm.item.length = newLength

例:

var vm = new Vue({
	data: {
		item: ['a', 'b', 'c']
	}
})

// 以下两种操作数组都不是响应式的
vm.item[1] = 'x' // 一类问题
vm.item.length = 2 // 二类问题

// 为解决一类问题,以下两种方式和 `vm.items[indexOfItem] = newValue` 相同的效果,同时也将在响应式系统内触发状态更新:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)

// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

// 你也可以使用 `vm.$set` 实例方法,该方法时全局方法 `Vue.set` 的一个别名:
vm.$set(vm.items, indexOfItem, newValue)

// 为了解决第二类问题,也可以使用splice
vm.items.splice(newLength)

三、声明响应式 property
由于 Vue 不允许动态添加根级响应式 property,所以你必须在初始化实例前声明所有根级响应式 property,哪怕只是一个空值:

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

如果你未在 data 选项中声明 message,Vue 将警告你渲染函数正在试图访问不存在的 property。

这样的限制在背后是有其技术原因的,它消除了在依赖项跟踪系统中的一类边界情况,也使 Vue 实例能更好地配合类型检查系统工作。但与此同时在代码可维护性方面也有一点重要的考虑:data 对象就像组件状态的结构 (schema)。提前声明所有的响应式 property,可以让组件代码在未来修改或给其他开发人员阅读时更易于理解。

四、异步更新队列
vue 在更新 DOM 是异步执行的。只要侦听到数据变化,vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。
如果同一个 watcher 被多次出发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环 “tick” 中,vue 刷新队列并执行实际(已去重的)工作。vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeOut(fn,0) 代替。

例如,当你设置 vm.someData = ‘new value’,该组件不会立即重新渲染,当刷新队列时,组件会在下一个事件循环 “tick” 中更新。
如果想要基于更新后的 DOM 状态来做点啥:
为了在数据变化之后等待 vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback)。这样回调函数在 DOM 更新完后被调用。例如:

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '未更新'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '已更新'
      console.log(this.$el.textContent) // => '未更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '已更新'
      })
    }
  }
})

因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2017 async/await 语法完成相同的事情:

methods: {
  updateMessage: async function () {
    this.message = '已更新'
    console.log(this.$el.textContent) // => '未更新'
    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值