Vue 会在初始化实例时对property 执行getter,setter 转化,property 必须在data 对象上存在才能让 Vue 转换为响应式的。
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
如何追踪变化
当你把一个普通的JavaScript 对象传入Vue 实例作为data 选项,Vue 将会遍历此对象所有的property ,并使用Object.defineProperty
把这些property 全部转为getter/setter ,Object.defineProperty
是ES5 中无法shim 的特性,所以Vue 不支持IE8以及更低版本的浏览器。
这些getter/setter 对用户是不可见的,但是在内部他们让Vue 能够追踪依赖,在property 被访问和修改时通知变更。
每个组件实例都对应一个watcher 实例,它会在组件渲染的过程中把“接触”过的数据property 记录为依赖,之后当依赖项的setter 触发时,会通知watcher ,从而使它关联的组件重新渲染。
对象的响应式实现
由于JavaScript 的限制,VUE 不能检测数组和对象的变化,那Vue 是如何保证数组和对象的响应式呢?
使用 Vue.set(object, propertyName, value)
举例:
<div id="app">
{{obj.a}}
<button @click="click1">
click
</button>
</div>
<script>
const obj={
a:1,
b:2
};
new Vue({
el:'#app',
data:{
a:1,
obj:obj
},
methods:{
click1(){
Vue.set(this.obj, 'a', this.obj.a+1)
}
}
})
</script>
点击按钮之后会重新渲染视图。
数组的响应式实现
Vue 不能检测以下的数组的变动,例如:
vm.items[indexOfItem]=newValue
vm.items.length=newLength
为了解决这一问题,可以使用 Vue.set(vm.items, indexOfItem, newValue)
举例:
<div id="app">
{{arr[0]}}
<button @click="click1">
click
</button>
</div>
new Vue({
el:'#app',
data:{
a:1,
arr:[1,2]
},
methods:{
click1(){
Vue.set(this.arr,0,this.arr[0]+1)
}
}
})
每点击一次按钮,data选项中的arr 的第一项变化会重新渲染视图。
除此之外,Vue 包装了被观察数组的变异方法,它们也可以触发视图更新,被包装的方法有:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
举例:
<div id="app">
<button @click="click1">
{{arr}}click
</button>
</div>
new Vue({
el:'#app',
data:{
arr:[1,2]
},
methods:{
click1(){
const temp=this.arr[this.arr.length - 1] + 1;
this.arr.push(temp)
}
}
})
点击按钮时,执行push 之后会触发视图更新。
声明响应式property
所有响应式property 必须在初始化实例前声明,哪怕只是一个空值。
异步更新队列
Vue 在更新DOM 时是异步执行的。
例如: 当你设置 vm.someData =‘new Value’,该组件不会立即重新渲染,当刷新队列时,组件会在下一个时间循环tick 中更新。
所以如果你想基于更新后的DOM 状态来做点什么,可以使用 Vue.nextTick(callback),在下一个时间循环 tick 中,DOM 已经完成更新,在回到函数中执行你想做的操作。
举例:
<div id="app">
<button @click="click1">
click{{a}}
</button>
</div>
new Vue({
el:'#app',
data:{
a:1
},
methods:{
click1(){
this.a++
console.log(this.$el.textContent)// DOM 还为渲染
this.$nextTick(function(){
console.log(this.$el.textContent)// 已经渲染完成
})
}
}
})
在 $nextTick 中,DOM 渲染已经完成。
$nextTick 返回的是一个Promise 对象,所以可以使用 async 和 await 语法简化操作。
click1 这个方法简化为下面:
async click1(){
this.a++
console.log(this.$el.textContent)
await this.$nextTick()
console.log(this.$el.textContent)
}