Implementing Vue Reactivity Using Javascript

Implementing Vue Reactivity Using Javascript

Falling_Sakura HaHa

代码思路:

先将每个变量注入到 Vue 实例中,使我们不必通过 data 访问,可以直接通过 Vue 实例访问。

然后为每一个键(变量)创建一个 Observer。

最后解析整个页面模板,为所有的引用变量的地方创建一个 Watcher,然后把它加到对应的 Dep 中,一个变量对应一个 Dep 但是一个变量可以被多次引用,每一个引用都是一个 Watcher,这样在一个 Watcher 改变时,它的 setter 就会触发它所在的 Dep 的更新。

中间对于数据的替换和更新用到了一系列的字符串操作和正则表达式。

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;

// target 在使用时不需要显式声明
Dep.target = this;
this.oldValue = vm[key];
// 激活 getter,将这个值的 Watcher 添加到它所在对象的 Sub
Dep.target = null;
// 重置 target
}
update() {
const newValue = this.vm[this.key];
if (newValue === this.oldValue) return;
this.callback(newValue);
this.oldValue = newValue;
}
}
class Compile {
constructor(vm) {
this.vm = vm;
this.el = vm.$el;
this.compile(this.el);
}
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach((node) => {
if (node.nodeType === 3) { // text
this.compileText(node);
} else if (node.nodeType === 1) { // element
this.compileElement(node);
}
// 还有子节点
if (node.childNodes && node.childNodes.length) {
this.compile(node);
}
});
}
compileElement(node) {
const reg = /^v-([\w-]+)$/;
const regx = /:([\w-]+)="([^"]+)"/;
Array.from(node.attributes).forEach((attr) => {
let match = regx.exec(`${attr.name}="${attr.value}"`);
if (match) {
const name = match[1];
const value = match[2];
new Watcher(this.vm, value, (newValue) => {
node.setAttribute(name, newValue);
});
} else if ((match = reg.exec(attr.name))) {
const opName = match[1];
const value = attr.value; // string
// ( _ ) 捕获从 1 下标开始
switch (opName) {
case "model":
node.value = this.vm[value];
new Watcher(this.vm, value, (newValue) => {
node.value = newValue;
});
node.addEventListener("input", (event) => {
this.vm[value] = event.target.value;
});
break;
case "if":
break;
case "on":
break;
case "else":
break;
case "else-if":
break;
case "show":
break;
case "for":
break;
}
}
});
}
compileText(node) {
const reg = /\{\{(.+?)\}\}/g;
// const value = node.textContent.replace(/\s/g, '');
const value = node.textContent;
const tokens = [];
let result,
index,
lastIndex = 0;

while ((result = reg.exec(value))) {
index = result.index;
if (index > lastIndex) {
tokens.push(value.slice(lastIndex, index));
}
const key = result[1].trim();
tokens.push(this.vm[key]);
lastIndex = index + result[0].length;

const pos = tokens.length - 1;
// update

new Watcher(this.vm, key, (newValue) => {
tokens[pos] = newValue;
node.textContent = tokens.join("");
});
}
if (lastIndex < value.length) {
tokens.push(value.slice(lastIndex));
}

if (tokens.length) {
node.textContent = tokens.join("");
}
}
}
class Dep {
constructor() {
this.subs = []; // watchers
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.forEach((sub) => sub.update());
}
}
class Observer {
constructor(data) {
this.data = data;
this.walk(data);
}
walk(data) {
Object.keys(data).forEach((key) => defineProperty(data, key, data[key]));
}
}
// 为每个属性值添加 getter 和 setter,这是响应式的基础
// 同时为每个对象添加一个 dep
function defineProperty(data, key, value) {
const dep = new Dep();
// is a object
if (typeof value === Object && value !== null) {
return new Observer(value);
}
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get() {
Dep.target && dep.addSub(Dep.target); // Watcher initualize
return value;
},
set(newValue) {
if (value === newValue) return;
value = newValue;
if (typeof value === Object && value !== null) {
return new Observer(value);
// 如果更改的值是一个对象,也要对每个对象的值进行观察初始化
}
dep.notify();
// 触发每一个 Sub 的更新。
},
});
}
class Vue {
constructor(options) {
this.$options = options || {};
this.$data = options.data || {};
const el = options.el;
this.$el = typeof el === "string" ? document.querySelector(el) : el;

// 注入
proxy(this, this.$data);
// vm.$data.prop -> vm.prop

// 观察
new Observer(this.$data);

// 模板分析
new Compile(this);
// 找到一开始需要响应式的位置
}
}
function proxy(target, data) {
Object.keys(data).forEach((key) => {
Object.defineProperty(target, key, {
enumerable: true,
configurable: true,
get() {
return data[key];
},
set(newValue) {
data[key] = newValue;
},
});
});
}

  • Title: Implementing Vue Reactivity Using Javascript
  • Author: Falling_Sakura
  • Created at : 2024-08-06 15:38:25
  • Updated at : 2024-11-21 10:44:39
  • Link: https://vercel.fallingsakura.top/57511a32.html
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
Implementing Vue Reactivity Using Javascript