Vue最显著的特性之一即是不太引人注意的响应式系统(reactivity system)。模型层(model)只是普通JS对象,修改它则更新视图(view)。这会让状态管理变得很是简单且直观,不过理解它的工做原理以免一些常见的问题也是很重要的本文将详细介绍Vue响应式系统的底层细节浏览器
追踪变化
把一个普通JS对象传给Vue实例的data
选项,Vue将遍历此对象全部的属性,并使用Object.defineProperty把这些属性所有转为getter/setter。Object.defineProperty是仅ES5支持,且没法shim的特性,这也就是为何Vue不支持IE8浏览器的缘由异步
用户看不到getter/setter,可是在内部它们让Vue追踪依赖,在属性被访问和修改时通知变化函数
每一个组件实例都有相应的watcher实例对象,它会在组件渲染的过程当中把属性记录为依赖,以后当依赖项的setter
被调用时,会通知watcher
从新计算,从而导致它关联的组件得以更新post

变化检测
受现代JS的限制(以及废弃 Object.observe
),Vue不能检测到对象属性的添加或删除。因为Vue会在初始化实例时对属性执行 getter/setter
转化过程,因此属性必须在data
对象上存在才能让Vue转换它,这样才能让它是响应的this
var vm = new Vue({ data:{ a:1 } }) // `vm.a` 是响应的 vm.b = 2 // `vm.b` 是非响应的
Vue不容许在已经建立的实例上动态添加新的根级响应式属性(root-level reactive property)。然而它可使用 Vue.set(object, key, value)
方法将响应属性添加到嵌套的对象上spa
Vue.set(vm.someObject, 'b', 2)
也可使用 vm.$set
实例方法,这也是全局 Vue.set
方法的别名code
this.$set(this.someObject,'b',2)
有时想向已有对象上添加一些属性,例如使用Object.assign()
或 _.extend()
方法来添加属性。可是,添加到对象上的新属性不会触发更新。在这种状况下能够建立一个新的对象,让它包含原对象的属性和新的属性component
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })` this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
声明响应式属性
因为Vue不容许动态添加根级响应式属性,因此必须在初始化实例前声明根级响应式属性,哪怕只是一个空值
var vm = new Vue({ data: { // 声明 message 为一个空值字符串 message: '' }, template: '<div>{{ message }}</div>' }) // 以后设置 `message` vm.message = 'Hello!'
若是在data选项中未声明 message
,Vue将警告渲染函数在试图访问的属性不存在
这样的限制在背后是有其技术缘由的,它消除了在依赖项跟踪系统中的一类边界状况,也使Vue实例在类型检查系统的帮助下运行的更高效。并且在代码可维护性方面也有一点重要的考虑:data
对象就像组件状态的概要,提早声明全部的响应式属性,可让组件代码在之后从新阅读或其余开发人员阅读时更易于被理解
异步更新队列
Vue异步执行DOM更新。只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的全部数据改变。若是同一个watcher被屡次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免没必要要的计算和DOM操做上很是重要。而后,在下一个的事件循环“tick”中,Vue刷新队列并执行实际(已去重的)工做。Vue在内部尝试对异步队列使用原生的Promise.then
和MutationObserver
,若是执行环境不支持,会采用setTimeout(fn, 0)
代替
例如,当设置vm.someData='new value'
,该组件不会当即从新渲染。当刷新队列时,组件会在事件循环队列清空时的下一个“tick”更新。多数状况不须要关心这个过程,可是若是想在DOM状态更新后作点什么,这就可能会有些棘手。虽然Vue.js一般鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 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) // => '更新完成' }) } } })