Vue构造函数内部会进行数据初始化:获取用户传入的data,执行观察observe
function initData(vm) {
let data = vm.$options.data
data = vm._data = typeof data === 'function' ? data.call(vm) : data || {};
observe(data);
}
observe:判断数据是一个对象,且不是null时,创建observer实例
function observe(data) {
if (typeof data !== 'object' || data === null){
return
}
return new Observer(data)
}
Observer构造函数:不同数据类型的劫持方法不同
class Observer{
constructor(data){
Object.defineProperty(data, '__ob__', { // 在数据上添加__ob__属性,是为了在重写数据原型方法时,便于新增的数组项进行观察
configurable: false,
enumerable: false,
value: this,
})
if (Array.isArray(data)) {
data.__propto__ = arrayMethods;
this.observeArray(data);
} else {
this.observeObj(data);
}
}
}
对象:遍历对象,通过Object.defineProperty对每个属性进行劫持;如果数据的属性值是对象,或者设置的属性值是对象,会进行深度劫持
observeObj(data){
let keys = Object.keys(data);
for (let i = 0;i<keys.length;i++){
let key = keys[i];
let value = data[key];
observe(value); // 深度劫持
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue){
if (value === newValue) return
observe(newValue); // 劫持设置的值
console.log('设置data')
value = newValue
}
})
}
}
数组:重写原有方法,且观察数组项是对象的项,不劫持索引
- 重写数组方法'push', 'shift', 'unshift', 'pop', 'reverse', 'sort', 'splice':通过Object.create创建新的数组原型arrayMethods,在arrayMethods上重写以上方法;使用时,通过__proto__指向arrayMethods(只要vm实例的数据才需要劫持,所有创建新的数组原型)
- 新增的数组项,需要被观察;可能新增数组项的方法如 'push', 'unshift', 'splice'
let oldArrayProtoMethods = Array.prototype;
export let arrayMethods = Object.create(oldArrayProtoMethods);
const methods = ['push', 'shift', 'unshift', 'pop', 'reverse', 'sort', 'splice']
methods.forEach(method => {
arrayMethods[method] = function(...args) {
let r = oldArrayProtoMethods[method].apply(this, args);
let inserted;
let ob = this.__ob__;
switch (method) {
case 'push':
case 'unshift':
inserted = args;
break;
case 'splice':
inserted = args.slice[2];
break;
default:
break
}
if (inserted) ob.observerArray(inserted) // 观察新增的内容
console.log('调用数组的新方法')
return r
}
})
observerArray (value){
for(let i = 0;i<value.length;i++){
observe(value[i]);
}
}