vue的mvvm模型,解耦了视图和数据,为前端开发提供了极大的便利,而其中最重要的是数据变化检测,vue的data检测机制有以下几个特点:
- 检测数据为对象的时候,必须先声明属性 ,这个属性才是响应式的。
- 增加不存在的属性 不能更新视图 (vm.$set)
- 修改数组索引和长度 是不会导致视图更新的
- 数组里套对象 对象是支持响应式变化的,如果是常量则没有效果
- 如果新增的数据 vue中也会帮你监控(对象类型)
Object的变化检测
在js中有两种方法可以侦测到对象的变化Object.defineProperty
和ES6的Proxy
,vue2.0的版本是采用Object.defineProperty
来检测对象数据的变化。根据vue的使用特性,我们要检测对象的所有key值,并且对新增的数据也要检测,我们可以写出下面的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30function observer(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
}
// 监听对象的每一个属性 key
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key])
})
}
function defineReactive(obj,key,value) {
// 递归对象的值,如果值为对象,也监测
observer(value);
Object.defineProperty(obj,key,{
enumerable:true,
configurable: true,
get() {
// 对于对象 我们在这里 收集依赖 watcher
return value
},
set(newValue) {
//给某个key设置值的时候 可能也是一个对象 也需要监听
observer(newValue);
//对象: 在这里触发收集的依赖
value = newValue;
console.log('视图更新');
}
})
}
对于对象数据的类型处理,我们使用递归来检测对象的每一个属性,同时,我们也可以给对象属性赋的新值也可能是个对象,由于Object.defineProperty
只能监测对象已声明的属性,对于新的对象,我们也要再次调用observer(newValue)
。同时,vue无法检测obj在定义时,没有声明的而后来新增的属性如name,或者是delete this.obj.name
这个变化,也就不会更新视图。
Array的变化侦测
我们已经知道了Object的侦测方式是通过getter/setter来实现的,但是更改数组的方法有push、pop、shift、unshift、splice、sort、reverse
这些原型方法,这种方式是 getter/setter
办不到的。
因此要检测数组的变化,我们必须自己来实现,所有vue对为数组提供了一个拦截器,对于要检测的数组都使用拦截器上的方法。拦截器的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26const arraryProto = Array.prototype;
// 数组原型上的方法
let proto = Object.create(arrayProto);
['push', 'unshift', 'splice', 'reverse', 'sort', 'shift', 'pop'].forEach(method=>{
proto[method] = function (...args) {
// 和 Object一样,我们也要处理 数组的新增数据 ,push unshift 和 splice都可以新增数据
let inserted; // 默认没有插入新的数据
switch(method) {
case 'push':
case 'unshift':
inserted = args
break;
// 数组的splice 只有传递三个参数 才是往数组增加数据
case 'splice':
inserted = args.slice(2)
break;
default:
break;
}
console.log('视图更新');
// 检测新增的数据
ArrayObserver(inserted)
// 还是调用数组的原型方法,但是我们可以在这里 发送数组的变化通知
arrayProto[method].call(this, ...args)
}
})
ArrayObserver的实现
1
2
3
4
5
6
7function ArrayObserver(obj) {
for(let i=0;i<obj.length;i++) {
let item = obj[i];
// 如果是普通值 就不监控了
observer(item); // 如果是对象会被 defineReactive
}
}
在最上面实现的observe函数中,我们还需要加上数组的侦测逻辑:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function observer(obj) {
if (typeof obj !== 'object' || obj == null) {
return obj;
}
// 数组的处理逻辑
if(Array.isArray(obj)) {
// 对于要侦测的数组要加上拦截器,数组的方法进行重写
Object.setPrototypeOf(obj,proto)
ArrayObserver(obj);
}else{
// 对象的处理
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key])
})
}
}
数组的依赖列表是挂在observer
函数上的,最有我们就可以在getter和拦截器中都可以访问到依赖列表。
小结
至此,vue中关于侦测对象Object和数组Array
的原理已基本实现,但是光有数据侦测机制,而没有依赖,是不能把数据(model)和视图(view)连接起来的。具体怎么收集和触发依赖,后面再分享。