vue响应式系统实现

vue响应式系统实现

源码实现

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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
const bucket = new WeakMap();
// 用一个全局变量存储当前激活的effect函数
let activeEffect;
// effect栈
const effectStack = [];

function effect(fn, options = []) {
const effectFn = () => {
// 调用cleanup函数完成清除工作
cleanup(effectFn);
// 当调用effect注册副作用函数时,将副作用函数赋值给activeEffect
activeEffect = effectFn;
// 调用副作用函数之前将当前副作用函数压入栈顶
effectStack.push(effectFn);
// 把fn的执行结果存储在res中
const res = fn();
// 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并将activeEffect还原为之前的值
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
return res;
};
// 将options挂载到effectFn上
effectFn.options = options;
// 用来存储所有与该副作用函数相关的依赖集合
effectFn.deps = [];
// 只有非lazy的时候,才执行
if (!options.lazy) {
effectFn();
}
return effectFn;
}

// computed
function computed(getter) {
// 用来缓存上一次计算的值
let value;
// dirty标志,用来识别是否需要重新计算值,为true时则意味着“脏”,需要计算
let dirty = true;
const effectFn = effect(getter, {
lazy: true,
scheduler() {
if (!dirty) {
dirty = true;
// 当计算属性依赖的响应式数据变化时,手动调用trigger函数触发响应
trigger(obj, "value");
}
},
});

const obj = {
get value() {
if (dirty) {
value = effectFn();
dirty = false;
}
// 当读取value时,手动调用track函数进行追踪
track(obj, "value");
return value;
},
};

return obj;
}

// watch
function watch(source, cb, options = {}) {
let getter;
// 如果是函数,说明用户传递的不再是一个响应式数据,而是一个getter函数,直接把source赋值给getter
if (typeof source === "function") {
getter = source;
} else {
// 否则调用traverse递归读取响应式数据属性
getter = () => traverse(source);
}

//定义旧值和新值
let oldValue, newValue;
// cleanup用来存储用户注册的国旗回调
let cleanup;
// 定义onInvalidate函数
function onInvalidate(fn) {
// 将过期回调存储到cleanup中
cleanup = fn;
}

// 提取调度器scheduler为一个独立的job函数
const job = () => {
// 在scheduler中重新执行副作用函数,得到的是新值
newValue = effectFn();
// 在调用糊掉函数cb之前,先调用过期回调
if (cleanup) {
cleanup();
}
// 将旧值和新值作为回调函数的参数
cb(newValue, oldValue);
// 更新旧值,不然下次会得到错误的旧值
oldValue = newValue;
};
// 使用effect注册副作用函数时,开启lazy选项,并把返回值存储到effectFn中一边后续手动调用
const effectFn = effect(() => getter(), {
lazy: true,
scheduler: job,
});

if (options.immediate) {
// 当immediate为true时立即执行job,从而触发回调执行
job();
} else {
// 手动调用副作用函数,拿到的值就是旧值
oldValue = effectFn();
}
}

function traverse(value, seen = new Set()) {
// 如果要读取的数据是原始值,或者已经被读取过了,那么什么都不做
if (typeof value !== "object" || value === null || seen.has(value)) return;
// 将数据添加到seen中,代表便利地读取过了,避免循环引用引起的死循环
seen.add(value);
// 暂时不考虑数组等其他结构
// 假设value是一个对象,使用for...in读取对新啊个的每一个值,并递归调用traverse进行处理
for (const k in value) {
traverse(value[k], seen);
}

return value;
}

function cleanup(effectFn) {
// 遍历effectFn.deps数组
for (let i = 0; i < effectFn.deps.length; i++) {
// deps是依赖集合
const deps = effectFn.deps[i];
// 将effectFn从依赖集合中移除
deps.delete(effectFn);
}

// 最后需要重置effectFn.deps数组
effectFn.deps.length = 0;
}

// 在get拦截函数内调用track函数追踪变化
function track(target, key) {
// 没有activeEffect,直接return
if (!activeEffect) return target[key];
// 根据target从“桶”中取得depsMap,也是一个Map类型:key --> effects
let depsMap = bucket.get(target);
// 如果不存在depsMap,新建一个Map并与target关联
if (!depsMap) {
bucket.set(target, (depsMap = new Map()));
}
// 根据key从depsMap中取得deps,它是一个Set类型,
// 里面存储着所有与当前key相关联的副作用函数:effects
let deps = depsMap.get(key);

if (!deps) {
depsMap.set(key, (deps = new Set()));
}

// 最后将当前激活的副作用函数添加到“桶”中
deps.add(activeEffect);
// deps就是一个与当前副作用函数存在联系的依赖集合
// 将其添加到activeEffect.deps数组中
activeEffect.deps.push(deps);
}

// 在set函数拦截内调用trigger函数出发变化
function trigger(target, key) {
// 根据target从桶中取出depsMap,它是 key --> effects
const depsMap = bucket.get(target);
if (!depsMap) return;
// 根据key取得所有副作用函数effects
const effects = depsMap.get(key);
// 在调用forEach遍历Set集合时,如果一个值已经被访问过了,
// 但该值被删除并重新添加集合,如果此时forEach遍历还没结束
// 该值会重新被访问,就会导致无限循环执行。
// 解决方法:构造另一个Set集合遍历它
const effectsToRun = new Set();
// 执行副作用函数
effects &&
effects.forEach((effectFn) => {
// 如果trigger触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn);
}
});

effectsToRun.forEach((effectFn) => {
// 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn);
} else {
// 否则直接执行副作用函数
effectFn();
}
});
}

const data = { text: "hello world", ok: false, foo: 1, bar: 2 };

const obj = new Proxy(data, {
// 拦截读取操作
get(target, key) {
// 将副作用函数activeEffect添加到存储副作用函数的桶中
track(target, key);
// 返回属性值
return target[key];
},
// 拦截设置操作
set(target, key, newVal) {
// 设置属性值
target[key] = newVal;
// 把副作用函数从桶中取出并执行
trigger(target, key);
},
});

let temp1, temp2;
// watch
watch(
() => obj.foo,
(newValue, oldValue) => {
console.log("数据变化了", newValue, oldValue);
},
{
immediate: true,
}
);
obj.foo++;

// computed
// const sum = computed(() => obj.foo + obj.bar);
// effect(() => {
// console.log(sum.value);
// });

// obj.foo++;

// lazy
// const effectFn = effect(
// () => {
// console.log(obj.text);
// },
// {
// lazy: true,
// }
// );
// effectFn();

// 嵌套的effect与effect栈
// effect(function effectFn1() {
// console.log("effectFn1 执行");

// effect(function effectFn2() {
// console.log("effectFn2 执行");
// temp2 = obj.ok;
// });

// temp1 = obj.text;
// obj.text += "1";
// });

// 分支切换与cleanup
// effect(() => {
// console.log('effect run');
// document.body.innerText = obj.ok ? obj.text : "not";

// obj.text = 'foolishmax'
// });

// 不存在的属性不添加响应式
// setTimeout(() => {
// obj.text = "hello vue3";
// // obj.notExist = 'hello vue3'
// }, 3000);

评论