Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的js对象,当我们对其进行修改时,视图会进行更新。接下来我们来了解其中的工作原理。
什么是响应式?
如果一个物体能对外界的刺激做出反应,它就是响应式的。
Vue的数据(data)是响应式的
const vm = new Vue({data:{n:0}})
如果我们修改vm.n(vm是data对象的代理),那么UI中的n就会响应我,Vue2通过Object.defineProperty来实现数据响应式。
如何追踪变化
当你把一个普通的js对象传入Vue实例作为data
选项,Vue将遍历此对象的所有的property,并且使用Object.defineProperty
把这些property全部转为getter/setter
。
Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些getter/setter对用户来说是不可见的,但是内部他们让Vue能够追踪依赖,在property被访问和修改时通知变更。
每个组件实例都对应一个watcher实例,他会在组件渲染过程中把接触的数据property记录为依赖。之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染。
检测变化的注意事项
对于对象
Vue对于对象的property的添加或移除无法检测。由于Vue会在初始化实例对property执行getter/setter转化,所以property必须在data对象上存在才能让Vue将它转换成响应式的。比如:
var vm = new Vue({
data:{
a:1
}
})
vm.a=2
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
可以使用Vue.set(object,propertyName,value)
/vm.$set(object,propertyName,value)
来添加响应式property。
有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign()
或 _.extend()
。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
对于数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
举个例子:
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.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)
可以用的vueArray的APi有:
push()//在数组后面添加元素
pop()//在数组后面删除元素
shift()//在数组前面删除元素
unshift()//在数组前面添加元素
splice()//删除或替换数组元素
sort()//默认把数组从小到大排序
reverse()//把数组顺序调换
参考:深入响应式原理