vue 双向绑定初探
起源
用vue有一段时间了,在使用的过程中发现了两个有意思问题:
1、对数组的限制,检测不到下面两个行为
- 利用索引直接设置一个项
- 修改数组的长度
var vm = new Vue({
data: {
items: ['1', '2', '3']
}
})
vm.items[1] = 'a' // 不是响应性的
vm.items.length = 5 // 不是响应性的
2、对对象属性的限制,不能检测对象属性的添加或者删除
var vm = new Vue({
data: {
person: {
name: 'jon'
}
}
})
// `vm.person.name` 现在是响应式的
vm.person.age = '18'
// `vm.person.age` 不是响应式的
那么为了弄清楚这两个问题,首先从双向绑定的原理出发。
入双向绑定
- Object.defineProperty vue的双向绑定与angularjs的不同,angularjs是使用脏检查来实现的,而vue则是基于Object.defineProperty这个方法来实现数据劫持,从而达到绑定的。 Object.defineProperty():
var obj = {}
Object.defineProperty(obj, 'name', {
get: function() {
console.log('get被调用了')
},
set: function() {
console.log('set被调用了')
}
})
obj.name = '11' // set被调用了
console.log(obj.name) // get被调用了
知识补充 Object.defineProperty是ES5新加的特性,而且无法被shim(指把一个库引入一个旧的浏览器, 然后用旧的API, 实现一些新的API的功能),因此vue不支持IE8以下版本的浏览器。
- 一个极简双向绑定的例子
<input type="text" id="js-input">
<span id="js-span"></span>
<script>
var obj = {}
Object.defineProperty(obj,'name',{
set: function(newValue){
document.getElementById('js-span').innerHTML = newValue
}
})
document.getElementById('js-input').addEventListener('keyup',function(e){
obj.name = e.target.value
})
</script>
当在js-input框里输入值的时候,js-span的值也会一起被修改,那么一个最简单的双向绑定就实现了,当然vue的双向绑定,肯定不是这么简单,这个demo是为了说明数据劫持的原理~
- Object.defineProperty的限制 现在来看下这个vue是怎么利用Object.defineProperty的
var vm = new Vue({
el: '#demo',
data: {
count: 1 //基础类型数据
}
})
//实际上在构造函数的时候就完成了数据的绑定。如果你在创建实例后又为该对象属性重置了一次,则会根据自己定义的方法,也就是set属性来执行,所以当执行vm.count=2时候,会执行console.log('change')
Object.defineProperty(vm, 'count', {
set: function(newvalue) {
console.log('change');
//此处的newvalue就是新的值,可以做视图更新操作,前提在于第二个参数vm.count是已知的属性
this.value = newvalue;
},
get: function() {
return this.value
}
})
把一个普通 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter 由此可以看出,因为动态添加的属性,没有被转为getter/setter,就引发了最开始的问题2。 现在来研究下数组的问题:
var a = [0, 1, 2]
a.length = 10
// 只是显示的给length赋值,索引3-9的对应的value也会赋值undefined
// 但是索引3-9的key都是没有值的
// 我们可以用for-in打印,只会打印0,1,2
for (var key in a) {
console.log(key) // 0,1,2
}
由于只能循环出前面三个,所以只将前三个值转化为了getter/setter,数组的下标和长度都没有被转化为 getter/setter,因此引发了第一个问题,但是vue重写了数组的push, pop, shift, unshift, splice, sort, reverse方法,使其可以双向绑定。 一个小尝试如果我们这么做
var arr = [1, 2, 3]
Object.defineProperty(arr, 'length', {
set: function() {
console.log('被set了')
}
})
那么会出现 “ Cannot redefine property: length”的错误,length是个无法被转为getter/setter的属性
解决方法
针对这两个情况,vue 给出了相关的解决方案
问题1的方案:
- Vue.set(vm.items, indexOfItem, newValue)
- vm.items.splice(indexOfItem, 1, newValue)
- vm.items.splice(newLength)
问题2的方案:
- Vue.set(vm.someObject, 'b', 2)
- this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })