数据响应式,在vue2.0中是通过Object.defineProperty重写get和set方法,在vue3.0中则通过proxy重写get和set;两者区别就是,过Object.defineProperty需要对数据递归实现层层代理,容易导致性能问题;proxy避免了递归,但是可能会有兼容问题。 本文主要是基于Object.defineProperty描述vue的响应式原理。
创建observe实例
在vue内部,当数据时一个对象时,会创建observe实例,对数据进行劫持
function observe(data) {
if (typeof data !== 'object' || data === null){
return
}
console.log(data)
return new Observer(data)
}
Observer构造函数
Observer用于对不同数据类型进行观测
class Observer{
constructor(data){
Object.defineProperty(data, '__ob__', {
configurable: false,
enumerable: false,
value: this,
})
if (Array.isArray(data)) { // 如果是数组 改变原型链,观察数组每一项
data.__propto__ = arrayMethods;
this.observerArray(data);
} else { // 如果是对象 遍历每一项,重新定义
this.walk(data);
}
}
observerArray (value){
for(let i = 0;i<value.length;i++){
observe(value[i]);
}
}
walk(data){
let keys = Object.keys(data);
for (let i = 0;i<keys.length;i++){
let key = keys[i];
let value = data[key];
defineReactive(data, key, value); // 定义响应式数据
}
}
}
数组劫持
对改变数组本身的七个方法push|shift|unshift|pop|reverse|sort|splice进行重写
- 只有vm实例数据中的数组才需要进行劫持,所以,基于数组原型对象复制新的对象arrayMethods,这个对象上添加需要重写的数组方法;最后通过原型链实现继承
- 如果新增了数组项,需要对新增项进行劫持
let oldArrayProtoMethods = Array.prototype;
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
}
})
对象劫持
- 通过Object.defineProperty重写get和set方法对数据的取值和设置值进行拦截
- 需要对属性值进行观察,实现层级响应式
- 需要对设置的值进行观察
function defineReactive(data, key, value){
observe(value);
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
return value
},
set(newValue){
if (value === newValue) return
observe(newValue);
console.log('设置data')
value = newValue
}
})
}