解决数据劫持时数组更新监控的问题

动手模拟MVVM系列文章点击这里, 在实践数据劫持里的遗留问题:

  • 当你修改数组的长度时,例如:arr.length = newLength
  • 当你利用索引直接设置一个项时,例如:arr[index] = newValue
  • 无法监控通过Array.prototype上的push、pop、shift、unshift、splice、sort、reverse、fill和copyWithin对数组的改变

今天写了个demo基本可以解决第三个问题,思路是通过一个对象代理数组原型上的九个方法(跟劫持数据的思路一样),当这九个方法被调用的时候,代理对象触发数据更新通知,思路实现代码如下:

代码清单1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 列出9个无法监控的方法
var methodNames = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse", "fill", "copyWithin"];
// 声明一个对象,该对象的[[proto]]指向Array.prototype,
var arrProto = Object.create(Array.prototype);

// 在对象arrProto上添加9个方法,用来覆盖Array.prototype上的同名方法
// 这样就等于做了一个代理,在内部依然调用Array.prototype上的方法,
methodNames.forEach((methodName) => {
arrProto[methodName] = function () {
let ret = Array.prototype[methodName].apply(this, [...arguments]);
console.log(`调用${methodName}方法: `, this);
return ret;
};
}
);

function dataIntercept(data) {
if (!data || typeof data !== "object") {
// console.log(`参数必须为对象`);
return false;
}

if (Array.isArray(data)) {
// 如果是数组类型, 将[[proto]]指向Array.prototype的代理,这样就实现了劫持
Object.setPrototypeOf(data, arrProto);
data.map((item) => {
dataIntercept(item);
}
);
} else {
Object.keys(data).map((item, index) => {
var initData = data[item];
Object.defineProperty(data, item, {
enumerable: true,
configurable: true,
set: function (val) {
// console.log(`修改属性${item}值为${val}`);
initData = val;
},
get: function () {
//console.log(`获取属性${item}的值${initData}`);
return initData;
}
});
if (typeof initData === "object") {
dataIntercept(initData);
}
}
);
}

}

var data = {
name: "daihaoxin",
job: "coding",
cities: [{
a: [4, 2, 3, 4]
}, "sh", "bj"]
};
dataIntercept(data);
data.cities.push("hangzhou");
console.log(data.cities);
data.cities.pop();
console.log(data.cities);
data.cities[0].a.push(999);
console.log(data.cities[0].a);
data.cities.reverse();
console.log(data.cities);

通过对Array.prototype设置代理,就实现了监控["push", "pop", "shift", "unshift", "splice", "sort", "reverse", "fill", "copyWithin"]九个方法的调用。

对于第二个问题中arr[index] = newValue的操作,可以变通的通过调用splice实现。

代码清单2

1
2
// Array.prototype.splice
arr.splice(index, 1, newValue);

第一个问题遗留。