上回写到,vue里通过new Observer实例,来对数据进行劫持,重写get和set,在set里会触发订阅者的更新,从而实现数据同步更新。
补充一下数组的处理
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep(); //1 订阅者容器, 用于存放所有订阅者
this.vmCount = 0;
def(value, '__ob__', this); //通过Object.defineProperty给value定义一个__ob__属性
if (Array.isArray(value)) { //判断是否是数组
if (hasProto) { // var hasProto = '__proto__' in {};
protoAugment(value, arrayMethods); //处理原型链,
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value); //内部对数组的每一项,又调用了observe(items[i]);
} else {
this.walk(value);
}
};
protoAugment
function protoAugment (target, src) {
/* eslint-disable no-proto */
target.__proto__ = src; //target的原型指向src
/* eslint-enable no-proto */
}
上面调用中,target传入的就是当前数组,src传入了arrayMethods
- arrayMethods: Object.create(Array.prototype); 继承自Array.prototype的对象
//紧接着给 arrayMethods 重新定义了以下方法
var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
//定义处理方法并发出对应的事件
methodsToPatch.forEach(function (method) {
// cache original method
//获取原型上原本的方法 arrayProto = Array.prototype
var original = arrayProto[method];
//def方法就是调用Object.defineProperty()来给对象设置属性
def(arrayMethods, method, function mutator () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ]; //获取参数,例如:splice(start, length, item1, item2)
//执行原型链中默认的处理方法,
var result = original.apply(this, args);
var ob = this.__ob__;
var inserted;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break
case 'splice':
inserted = args.slice(2); //取要插入的新值
break
}
if (inserted) { ob.observeArray(inserted); } //对新值进行观察
// notify change
ob.dep.notify(); //触发订阅者更新操作
return result
});
});
综上,只有数组的push, pop , shift, unshift, splice, sort, reverse操作才能出发双向更新
copyAugment
//当不支持__proto__时
function copyAugment (target, src, keys) {
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
def(target, key, src[key]); //给数组定义方法,用默认的
}
}
var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
如果想要修改数组的某一项,需要调用vue的set方法,
function set (target, key, val) {
if (isUndef(target) || isPrimitive(target)
) {
warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
}
//判断target是否数组, 并且key是有效的索引值
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val); //调用splice方法,splice会触发dep.notify(),从而出发订阅者更新操作
return val
}
//已有的对象属性,且不再原型上
if (key in target && !(key in Object.prototype)) {
target[key] = val; //直接赋值,会触发set方法
return val
}
var ob = (target).__ob__;
if (target._isVue || (ob && ob.vmCount)) {
warn( //不能给vue实例添加属性
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
);
return val
}
if (!ob) { //如果target是新增的数据(说明不需要数据拦截), 直接赋值即可
target[key] = val;
return val
}
defineReactive$$1(ob.value, key, val); //对新值进行拦截处理
ob.dep.notify(); //触发更新
return val
}
//在stateMixin()中有如下赋值
//Vue.prototype.$set = set;