实现数组的响应式原理
首先我们在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一个对象就可以看到这个对象也被观测到了