这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战
Vue
在数据劫持中对于数组特殊处理的原因:
使用Object.defineProperty
并不会对数据的索引进行劫持,比如我现在有一个数组,长度是100000,如果对每个索引都进行劫持,那么就需要将劫持100000次,所以劫持数组会造成性能问题,性能代价和用户体验收益不成正比
在日常开发过程中,我们也很少会直接通过数组的索引去操作数组,这样是存在问题的,因为数组是会一直在变化的,如果数据改变,那么通过索引获取到的数据并不是期望获得的,大部分都是通过数组的原型方法psuh
、pop
等方法操作,因此将数组的7个可以改变数组本身的函数进行了代理
劫持数组方法
对于数组的处理,专门提取到一个文件src/observe/array.js
中
看一下对于数组的处理,核心代码在代码中已注释
/**
* 重写数组的一些方法(会改变数组本身的一些方法)
*/
const methods = [
"push",
"pop",
"shift",
"unshift",
"sort",
"splice",
"reverse",
];
// 获取数组原型方法
const oldArrMethods = Array.prototype;
// 根据数据原型上的方法全部拷贝
export const arrMethods = Object.create(oldArrMethods);
/**
* 遍历需要处理的7个方法
*/
methods.forEach((method) => {
// 劫持7个函数
arrMethods[method] = function (...args) {
// 获调用数据原有的方法
const res = oldArrMethods[method].apply(this, args);
// 若是调用 push、unshift和splice, 则inserted代表被加入的新的数据
let inserted;
// 获取到Observer实例
let ob = this.__ob__;
switch (method) {
case "push":
case "unshift":
inserted = args;
break;
case "splice": // splice 有新增 删除的功能,
inserted = args.slice(2); // 截取 新增的数据
break;
default:
break;
}
if (inserted) {
// 将新增属性 继续劫持
ob.observeArray(inserted);
}
return res;
};
});
上述代码核心:
- 为何只对
push
、pop
等7个方法进行特殊处理 - 为何使用
Object.create
创建一个新的数组原型方法,这样做可以不影响数组原有的原型函数 - 对于数组新增和替换的数据再次进行劫持
Observer改造
既然数组的处理已经完成,接下来便是在src/observe/index.js
引入并使用
在Observer
类的构造中需要做两件事情
- 给每一个监控过的对象都增加一个
__ob__
属性
def(data, "__ob__", this);
def
函数做的事情:将数据设置为不可枚举、不可配置
export function def(data, key, value) {
Object.defineProperty(data, key, {
enumerable: false,
configurable: false,
value,
});
}
这里设置不能直接使用
data.__ob__ = this;
存在循环调用的风险
- 将数组和对象分别处理
对象依旧调用walk
函数进行处理,数组则需要特殊处理:
改变数组的原型指向
data.__proto__ = arrMethods;
重新定义一个observeArray
函数处理数组
整体代码变为:
class Observer {
constructor(data) {
def(data, "__ob__", this);
if (Array.isArray(data)) { // 处理数组
data.__proto__ = arrMethods;
this.observeArray(data);
} else { // 处理对象
this.walk(data);
}
}
walk(data) {
// 保持不变
}
observeArray(data) {
// 监控数组中的对象
for (let i = 0; i < data.length; i++) {
observe(data[i]);
}
}
}
此时代码对于数组的处理只能处理一维数组,对于多维数组的处理,需要继续处理
function dependArray(val) {
for (let i = 0; i < val.length; i++) {
const current = val[i];
if(Array.isArray(current)) {
dependArray(current)
}
}
}
其调用时机在数据劫持get
中
get() {
// 如果数组中 还有数组, 需要将数组中的每一项再收集一下依赖
if(Array.isArray(val)) {
dependArray(val)
}
return val;
}