前端开发系列115-进阶篇之对象和数组的读写劫持
本文讨论如何监听对象中所有属性的读和写操作,以及对于数组的劫持特殊处理,本文将从侧面来介绍 Vue2.X版本中响应式数据监听的原理。本文将用到 [Object.defineProperty]()方法,该方法以及[getter 和 setter] 方法的具体使用方式,可以参考另一篇博客文章。
对象劫持
function isObject(o) {
return typeof o === "object" && o != null;
}
/*核心函数:通过Object.defineProperty方法实现劫持*/
function defineReactive(data, key, value) {
/* 递归调用:解决value也是对象的情况 */
observe(value);
Object.defineProperty(data, key, {
get() {
console.log(`${key}--读`)
return value;
},
set(newValue) {
console.log(`${key}--写`)
/* 如果数据的值没有改变那么就直接返回 */
if (value === newValue) return;
/* 如果设置的新数据是对象,那么也应该进行监听 */
observe(newValue);
value = newValue;
}
})
}
/* Observer 类(构造函数) */
class Observer {
constructor(val) {
this.walk(val)
}
walk(data) {
/* 获取当前对象所有可枚举的 key */
let keys = Object.keys(data);
/* 遍历所有的 keys,通过 Object.defineProperty 给所有的 key 都添加 getter和 setter */
keys.forEach(key => defineReactive(data, key, data[key]));
}
}
function observe(o) {
if (!isObject(o)) return; /* 排除对象的情况 */
return new Observer(o); /* 获取Observer的实例 */
}
/* 测试数据 */
let data = {
person: {
name: "zs",
age: 18
}
};
observe(data);
上面代码同的核心方法是defineReactive函数
,在该函数的内部我们通过Object.defineProperty
方法实现了对对象中属性的读(get
)和写(set
)操作的监听。Observer
类用于构建 observe实例对象,该实例的walk
方法通过遍历的方式为对象中所有的属性都实现了getter 和 setter 方法
。
接下来,我们给出一组测试数据并贴出对应的显示结果。
![](http://img.wendingding.vip/blog/objtest.png)
通过对代码的研究和对数据的测试,我们验证了上面代码基本上能够完成对对象数据读写操作的监听,但仍然存在一些不足。
① 如果是新增加属性,那么则无法监听。
② 如果是数组的结构,那么也无法监听。
数组劫持
function isObject(o) {
return typeof o === "object" && o != null;
}
function defineReactive(data, key, value) {
/* 递归调用:解决value也是对象的情况 */
observe(value);
Object.defineProperty(data, key, {
get() {
console.log(`${key}--读`)
return value;
},
set(newValue) {
console.log(`${key}--写`)
/* 如果数据的值没有改变那么就直接返回 */
if (value === newValue) return;
/* 如果设置的新数据是对象,那么也应该进行监听 */
observe(newValue);
value = newValue;
}
})
}
/* 获取数组原型的方法 */
let oldArrayMethods = Array.prototype;
/* 把oldArrayMethods作为原型对象创建一个新的空的对象 */
let newArrayMethods = Object.create(oldArrayMethods);
/* 整理数组中需要重写的方法 */
let methods = ["pop", "push", "shift", "unshift", "sort", "reverse", "splice"];
methods.forEach(method => {
newArrayMethods[method] = function(...args) {
let __ob__ = this.__ob__;
let result = oldArrayMethods[method].apply(this, args);
console.log(`监听到${method}方法`, this, args);
/* 注意:新添加的数据可能是对象也需要监听读写操作 */
/* 1.先获取新添加的数据参数 */
let insetData;
switch (method) {
case "push":
case "unshift":
insetData = args;
break;
case "splice":
insetData = args.slice(2);
default:
break;
}
console.log("insetData", insetData);
/* 2.对新添加的数据进行监听 */
if (insetData) __ob__.observerArr(insetData);
return result;
}
})
/* Observer 类(构造函数) */
class Observer {
constructor(val) {
/* 区分对象和数组的情况 */
if (Array.isArray(val)) {
/* 给当前的对象定义__ob__属性,该属性指向的是自己 */
Object.defineProperty(val, "__ob__", {
configurable: false,
enumerable: false,
value: this
})
/* 重写数组的原型方法,在这些重写的方法内部进行监听 */
Reflect.setPrototypeOf(val, newArrayMethods);
this.observerArr(val);
} else {
this.walk(val)
}
}
walk(data) {
/* 获取当前对象所有可枚举的 key */
let keys = Object.keys(data);
/* 遍历所有的 keys,通过 Object.defineProperty 给所有的 key 都添加 getter和 setter */
keys.forEach(key => defineReactive(data, key, data[key]));
}
observerArr(arr) {
arr.forEach(item => observe(item));
}
}
function observe(o) {
if (!isObject(o)) return; /* 排除对象的情况 */
return new Observer(o); /* 获取Observer的实例 */
}
/* 测试数据 */
let data = {
person: {
name: "zs",
age: 18
},
friends: [{
name: "佩琪",
age: 3
}, {
name: "巧虎",
age: 5
}]
};
observe(data);
根据测试数据贴出对应的显示结果如下所示。
![](http://img.wendingding.vip/blog/objtest2.png)
上述代码完成了对数组数据的读写监听(劫持),但仍然存在一些无法处理的情况,下面简单列出。
问题1:如果我们通过数组的下标来访问和修改数据,那么无法监听。
问题2:如果我们通过数组的 length 属性来操作(删除)数组,那么也无法监听。
上面的这些问题在 Vue 2.X 版本中主要通过 Vue.set() 或者是 vm.$set() 来进行实现。