实现数组的响应式原理
首先我们在index.html中定义一个数组,并且打印实例
const vm = new MVue({data() {return {name: "zhangsan",age: "16",hobby:['zhangsan','lisi']}}
})
console.log(vm);
我们会发现定义的数组每一项都有get和set方法虽然数组是被劫持了但是这样做性能非常差。如果说数组元素有1000个或者10000个能?那么我们要循环劫持1000次或者10000次吗?这显然是不合理的,所以我们对数组要单独做数据劫持。
数组一般用户都是push,pop这所方法去操作数组元素所以,我们要重写这些方法。
首先我们先判断是否为数组,obseve/index.js文件下Observer类修改如下代码
constructor(data) {/* 这里注意,Object.defineProperty只能劫持已经存在的属性,新增或删除的无法劫持这也是为什么vue2中新增了,$set和$delete方法这类方法的原因*/if(Array.isArray(data)){// 重写原型对象上的方法data.__proto__=new_array_proto// 劫持数组this.observe_array(data)}else{// 劫持对象this.walk(data)}}
walk(data) {// 循环对象keys依次劫持,重新定义属性Object.keys(data).forEach(key => {define_reactive(data, key, data[key])})
}
observe_array(data){// 如果数组中包含对象,则走对象劫持data.forEach((item)=>observe(item))
}
在observe/index.js文件下创建array.js文件用来重写数组方法。并且导出new_array_proto
// 获取原数组原型对象
const old_arr_proto=Array.prototype;// 拷贝新原型对象
export let new_array_proto=Object.create(old_arr_proto);// 定义所以变异方法
let methods=['push','pop','shift','unshift','reverse','sort','splice'];methods.forEach((item)=>{ new_array_proto[item]=function(...args){console.log(123);// 调用原原型上的方法let res=old_arr_proto[item].call(this,...args);return res}
})
然后我们在index.html调用push方法看看控制台是否打印123。如果打印就说明重写成功
下面我们来对我们的变异方法做进一步处理。因为新增的属性并没有被劫持
// 获取原数组原型对象
const old_arr_proto=Array.prototype;// 拷贝新原型对象
export let new_array_proto=Object.create(old_arr_proto);// 定义所以变异方法
let methods=['push','pop','shift','unshift','reverse','sort','splice'];methods.forEach((item)=>{ new_array_proto[item]=function(...args){// 调用原原型上的方法let res=old_arr_proto[item].call(this,...args);//对新增的属性进行观测let val;switch(item){case "push":case "unshift":val=args;break;case "splice":val=args.slice(2);break;}// this为当前变异方法的调用者。_ob_为观测类的实例if(val){this._ob_.observe_array(val) // 可能到这里你会好奇这个_ob_是哪里来的。别急下面我将解释}return res}
})
observe/index.js文件下中构造函数增加如下代码。添加_ob_一方面是为了给已经观测的数据打上标识,另一方便是为了调用数组的变异方法时如果新增的是对象那么,通过_ob_获取到观测类实例对这个新增对象进行观测
constructor(data) {/* 这里注意,Object.defineProperty只能劫持已经存在的属性,新增或删除的无法劫持这也是为什么vue2中新增了,$set和$delete方法这类方法的原因*/// 对即将观测的数据添加_ob_属性。添加被观测标识Object.defineProperty(data, '_ob_', {value:this,enumerable:false // 禁止枚举,防止死循环})if(Array.isArray(data)){// 重写原型对象上的方法data.__proto__=new_array_proto// 劫持数组this.observe_array(data)}else{// 劫持对象this.walk(data)}}
既然已经被观测的数据打上了表示,那么为了防止重复观测我们就可以在observe方法中添加如下代码
// 对对象劫持if (typeof data !== 'object' || data === null) {return;}// 如果已经被检测过直接返回if(data._ob_ instanceof Observer){return data._ob_}// 如果一个对象被劫持过就不需要再次劫持,我们可以通过一个实例判断是否被劫持过return new Observer(data)
下面我们在index.html文件下push一个对象就可以看到这个对象也被观测到了