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!'