Skip to content

Commit 07de30e

Browse files
committedMar 7, 2020
feat: 数组劫持
1 parent 29feb29 commit 07de30e

File tree

5 files changed

+180
-5
lines changed

5 files changed

+180
-5
lines changed
 

‎img/2.jpg

48.1 KB
Loading

‎note/数组劫持.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
## 数组劫持的实现
2+
3+
## 1. 数组原生方法的劫持
4+
5+
前面已经完成了对`data`数据里对象的劫持,但是针对数组的变化,`Object.defineProperty` 不能很好的支持,所以在`Vue` 中,采取了对数组原生方法进行劫持的操作,来保证数据可以被正常监测到。
6+
7+
由于原来的方法不能正常对数组进行数据劫持,所以我们要对data的数据类型进行区分,所以我们需要改写`Observer`类:
8+
9+
```javascript
10+
class Observer {
11+
constructor(data) { // data === vm._data
12+
// 将用户的数据使用 Object.defineProperty重新定义
13+
if (Array.isArray(data)) {
14+
// 对数组方法进行劫持, 让数组通过链来查找我们自己改写的原型方法
15+
data.__proto__ = arrayMethods;
16+
} else {
17+
this.walk(data);
18+
}
19+
}
20+
21+
// ...
22+
}
23+
```
24+
25+
通过使用`data.__proto__`将数组上的方法换成我们改写的原型方法 `arrayMethods`,我们新建一个`array.js`来重写我们的数组方法。
26+
27+
```javascript
28+
/**
29+
* 拦截用户调用的push、shift、unshift、pop、reverse、sort、splice数组方法
30+
*/
31+
32+
// 获取老的数组方法
33+
let oldArrayProtoMethods = Array.prototype;
34+
35+
// 拷贝新的对象,用来查找老的方法, 不修改原型上的方法
36+
export let arrayMethods = Object.create(oldArrayProtoMethods);
37+
38+
let methods = [
39+
'push',
40+
'pop',
41+
'unshift',
42+
'shift',
43+
'sort',
44+
'splice'
45+
];
46+
47+
methods.forEach(method => {
48+
arrayMethods[method] = function(...args) { // 函数劫持
49+
let result = oldArrayProtoMethods[method].apply(this, args);
50+
console.log('调用数组更新方法');
51+
return result;
52+
}
53+
});
54+
```
55+
56+
此时我们去调用`vm.arr.push(1, 2, 3)`会打印*调用数组更新方法*,说明此时的`push`方法是调用的我们重写的方法。
57+
58+
59+
60+
## 2. 数组新增对象进行监测
61+
62+
如果我们调用`vm.arr.push({a: 1})`,那我们也需要对数组新增的对象属性进行监测,也就是需要添加`observe`观测,所以还需要进一步改写数组的方法:
63+
64+
```javascript
65+
/**
66+
* 对数组新增的元素进行劫持
67+
* @param {*} inserted
68+
*/
69+
+export function observerArray(inserted) {
70+
+ for (let i = 0; i < inserted.length; i++){
71+
+ observe(inserted[i]); // 还需要对数组里面的内容进行监测
72+
+ }
73+
+}
74+
75+
methods.forEach(method => {
76+
arrayMethods[method] = function(...args) { // 函数劫持
77+
let result = oldArrayProtoMethods[method].apply(this, args);
78+
console.log('调用数组更新方法');
79+
+ let inserted;
80+
+ switch (method) {
81+
+ case 'push':
82+
+ case 'unshift':
83+
+ inserted = args;
84+
+ break;
85+
+ case 'splice':
86+
+ inserted = args.slice(2); // 获取splice(start, deleteCount, [])新增的内容
87+
+ default:
88+
+ break;
89+
+ }
90+
+ if(inserted) observerArray(inserted);
91+
return result;
92+
}
93+
});
94+
```
95+
96+
97+
98+
## 3. 结果
99+
100+
此时如果我们数组添加一个对象并且修改对象的值都可以正常被监测到:
101+
102+
```javascript
103+
console.log(vm.arr.push({a: 1}));
104+
console.log(vm.arr[3].a = 100);
105+
```
106+
107+
此时浏览器会打印:
108+
109+
![image-20200307144823192](../img/2.jpg)
110+
111+
112+
113+
到这我们的数组劫持也实现了,下一部分我们去实现如何将数据通过模板的方式替换到页面并同步改变。
114+
115+
116+

‎source/vue/observe/array.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { observe } from ".";
2+
3+
/**
4+
* 拦截用户调用的push、shift、unshift、pop、reverse、sort、splice数组方法
5+
*/
6+
7+
// 获取老的数组方法
8+
let oldArrayProtoMethods = Array.prototype;
9+
10+
// 拷贝新的对象,用来查找老的方法, 不修改原型上的方法
11+
export let arrayMethods = Object.create(oldArrayProtoMethods);
12+
13+
let methods = [
14+
'push',
15+
'pop',
16+
'unshift',
17+
'shift',
18+
'sort',
19+
'splice'
20+
];
21+
22+
/**
23+
* 对数组新增的元素进行劫持
24+
* @param {*} inserted
25+
*/
26+
export function observerArray(inserted) {
27+
for (let i = 0; i < inserted.length; i++){
28+
observe(inserted[i]); // 还需要对数组里面的内容进行监测,并不对索引进行监测
29+
}
30+
}
31+
32+
methods.forEach(method => {
33+
arrayMethods[method] = function(...args) { // 函数劫持
34+
let result = oldArrayProtoMethods[method].apply(this, args);
35+
console.log('调用数组更新方法');
36+
let inserted;
37+
switch (method) {
38+
// 只对新增的属性进行再次监测,其他方法没有新增属性
39+
case 'push':
40+
case 'unshift':
41+
inserted = args;
42+
break;
43+
case 'splice':
44+
inserted = args.slice(2); // 获取splice(start, deleteCount, [])新增的内容
45+
default:
46+
break;
47+
}
48+
if(inserted) observerArray(inserted);
49+
return result;
50+
}
51+
});

‎source/vue/observe/observer.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {observe} from './index';
2+
import { arrayMethods } from './array';
23

34
/**
45
* 定义响应式的数据变化
@@ -16,6 +17,7 @@ export function defineReactive(data, key, value) {
1617
set(newValue) {
1718
console.log('设置数据');
1819
if (newValue === value) return;
20+
observe(newValue); // 如果新设置的值是一个对象, 应该添加监测
1921
value = newValue;
2022
}
2123
});
@@ -24,7 +26,12 @@ export function defineReactive(data, key, value) {
2426
class Observer {
2527
constructor(data) { // data === vm._data
2628
// 将用户的数据使用 Object.defineProperty重新定义
27-
this.walk(data);
29+
if (Array.isArray(data)) {
30+
// 对数组方法进行劫持, 让数组通过链来查找我们自己改写的原型方法
31+
data.__proto__ = arrayMethods;
32+
} else {
33+
this.walk(data);
34+
}
2835
}
2936

3037
/**

‎src/index.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ let vm = new Vue({
1818
}
1919
});
2020

21-
console.log(vm.msg);
22-
console.log(vm.msg = 'world');
23-
console.log(vm.arr.push(4));
24-
console.log(vm.arr);
21+
// console.log(vm.msg);
22+
// console.log(vm.msg = 'world');
23+
// console.log(vm.arr.push(123));
24+
console.log(vm.arr.push({a: 1}));
25+
console.log(vm.arr[3].a = 100);

0 commit comments

Comments
 (0)
Please sign in to comment.