vue中的最独特的性质之一是:非侵入性响应系统。vue中的数据模型仅仅是一个普通的js对象,但是更改它的时候,视图也会响应变化。这就涉及到了vue的响应系统。
如何追踪变化
vue是如何追踪数据模型的变化呢?当我们把一个js对象传入vue的实例中做为data的选项,vue会遍历此对象的所有属性并使用Object.defineProperty使对象的属性全部转为getter/setter。(由于Object.defineProperty是es5中的方法,并且无法模拟的方法,所以vue不兼容不支持es5的浏览器==>ie8以下的浏览器都不支持)
这些getter/setter对用户来说是不可见的,但是能让vue依赖追踪,在属性被访问和修改的时候能通知变更。
每个组件实例都有一个watcher实例,在组件渲染的过程中,会把接触到的数据记录成为“依赖”,当依赖项的setter触发的时候,就会通知watcher从而使与它关联的组件重新渲染。
检测变化的注意事项
受到现代js的限制(以及Object.observe也被废弃),vue无法检测到对象属性的添加或者删除。vue会在初始化时候对属性执行getter/setter转化,所以属性必须在data对象上才能将它转为响应的。
对于已经创建的实例,vue不允许动态创建根级别的响应式属性。但是可以用Vue.set(object, propertyName, value)向嵌套对象添加响应式属性。
Vue.set(vm.someObject, propertyName, value)
或者:
vue.$set(this.someObject, propertyName, value)
当需要一次性添加多个属性的时候,可以使用Object.assign方法或者_.extend方法。但是这样设置的属性不是响应式的,这时候需要将原对象以及混合进去的新对象一起创建一个新对象。
this.someObject = Object.assign({}, this.someObject, {a: 1, b: 2})
声明响应式属性
由于vue不允许动态添加响应式属性,所以必须在data中(初始化响应之前)声明所有的根级响应式属性,哪怕是一个空值。
var vm = new Vue({
data: {
// 声明 message 为一个空值字符串
message: ''
},
template: '<div>{{ message }}</div>'
})
// 之后设置 `message`
vm.message = 'Hello!'
假如未在data中声明message,那么vue将会警告你渲染函数正在访问不存在的属性。
这样的限制在背后是有技术原因的:
- 消除了依赖追踪中的一类边界情况??(未知什么边界情况,请大神指导)
- 使vue能够更好的配合类型检查系统工作
- 代码的可维护性更好。提前声明所有响应式属性可以让组件代码在未来修改或者给其他开发者阅读更便于理解。
异步更新队列
vue在更新DOM时是异步执行的。只要侦听到数据变化,vue就开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watch被多次触发,那么只会被推进队列一次。这种在循环时候去除重复数据对于不必要的计算和dom操作是十分必要的。然后在下一个循环tick中,vue刷新队列并执行实际工作。vue在内部对异步队列会尝试Promise.then(),MutationObserver,setImmediate等原生的方法,如果不支持的话只会执行setTimeout(fn, 0)。
例如,当你设置vm.someProperty = newValue的时候,组件是不会立刻重新渲染的(异步队列)。当刷新队列的时候,组件会在下一个事件循环“tick”中更新。多数情况下我们并不需要去关心这个过程。但是假如我们需要基于更新只之后的DOM状态来做些什么,这就有些棘手来。vue鼓励开发者用数据驱动的思维思考,避免直接操作DOM。但是有些业务要求我们需要对DOM进行操作。为了在数据变化之后等待vue完成更新DOM,可以在数据变化之后立即使用Vue.nextTick(callback)。这样回调函数将在DOM更新完成之后被调用。
<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example',
data: {
message: '111'
}
})
vm.message = '222'
console.log(vm.$el.textContent === '222')//false
Vue.nextTick(function(){
console.log(vm.$el.textContent === '222')//true
})
组件内使用vm.$nextTick()非常方便, 因为它不需要全局Vue,并且回调函数的this绑定到当前的vue实例上。
this.$nextTick(function(){
console.log(this.$el.textContent === '222';//true
});
因为$nextTick返回的是一个promise对象,所以可以用es6的async/await完成相同的工作
updateMessage: async function(){
this.message = '222';
console.log(this.$el.textContent === '222');//false
await this.$nextTick();
console.log(this.$el.textContent === '222');//true
}
网友评论