实现一个 MVVM 中的 Observer (step 0)

假设存在如下需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let app1 = new Observer({
name: 'youngwind',
age: 25
});

let app2 = new Observer({
university: 'bupt',
major: 'computer'
});

// 要实现的结果如下:
app1.data.name // 你访问了 name
app1.data.age = 100; // 你设置了 age,新的值为100
app2.data.university // 你访问了 university
app2.data.major = 'science' // 你设置了 major,新的值为 science

这个功能的实现, 要用到 Objectsettergetter 的劫持。
结合着看过的对 Vue 的源码分析和自己理解的部分, 写一段简单的代码,功能并不完善。

一共分为 5 步来实现, 这里只实现第 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
class Observer {

constructor (data) {
this.data = data;
this.bind(data);
}

bind (data) {
// 这里通过私有属性构成一个内部才能访问的闭包
let storage = new DataStorage();

Object.keys(data).forEach(key => {
// 1. 在初始化的时候, 把数据存进对象自己的 DataStorage 实例中
// 2. 然后通过 setter 和 getter 可以对 DataStorage 进行操作
storage.addProp(data, key); // 存入数据
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
set: val => {
if (val === storage.getProp(key)) {
console.log('你设置了 ' + key + ', 但是没有对值进行更改。');
} else {
storage.setProp(key, val);
console.log('你设置了 ' + key + ', 新的值为 ' + val);
}
},
get: () => {
console.log('你访问了 ' + key + ', 值为 ' + storage.getProp(key));
}
});
});
}
}

// 用于对每个类生成实例, 分别存储每个类的数据
class DataStorage {
constructor (obj) {
this.data = {};
}

addProp (data, key) {
this.data[key] = data[key];
}

getProp (key) {
return this.data[key];
}

setProp (key, val) {
this.data[key] = val;
}
}

用题目要求的数据进行测试, 测试结果如下:

1
2
3
4
你访问了 name, 值为 youngwind
你设置了 age, 新的值为 100
你访问了 university, 值为 bupt
你设置了 major, 新的值为 science

示例可以点击这里进行查看。

程序很简单, 比较有意思的是作用域这一部分, 猜猜看, bind() 原型方法中会不会形成闭包?

答案: 会,因为 bind() 在执行的时候, 内部变量 Object.defineProperty 中定义的两个匿名函数被全局变量 app1gettersetter 给分别引用了。