Skip to content

Commit c4c09ef

Browse files
committed
feat: add 响应系统的作用与实现
1 parent 4a9552a commit c4c09ef

File tree

5 files changed

+522
-0
lines changed

5 files changed

+522
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Document</title>
8+
</head>
9+
10+
<body>
11+
</body>
12+
<script src="./响应式数据与副作用函数.js"></script>
13+
14+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"use strict";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
interface Options {
2+
immediate?: boolean;
3+
lazy?: boolean;
4+
flush?: string;
5+
scheduler?: (effectFn: EffectFn) => void;
6+
// 其他可能的字段...
7+
}
8+
9+
type EffectFn = (() => Object | Number | String | Boolean) & { options?: Options, deps: Set<EffectFn>[] }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
"use strict";
2+
// 用一个全局变量存储当前激活的 effect 函数
3+
let activeEffect;
4+
// effect 栈
5+
const effectStack = []; // 新增
6+
function effect(fn, options) {
7+
const effectFn = () => {
8+
cleanup(effectFn);
9+
// 当调用 effect 注册副作用函数时,将副作用函数赋值给 activeEffect
10+
activeEffect = effectFn;
11+
// 在调用副作用函数之前将当前副作用函数压栈
12+
effectStack.push(effectFn);
13+
// 将 fn 的执行结果存储到 res 中
14+
const res = fn(); // 新增
15+
// 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并把 activeEffect 还原为之前的值
16+
effectStack.pop();
17+
activeEffect = effectStack[effectStack.length - 1];
18+
return res;
19+
};
20+
// 将 options 挂载到 effectFn 上
21+
effectFn.options = options; // 新增
22+
// activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
23+
effectFn.deps = [];
24+
// 只有非lazy的时候,才执行
25+
if (options && !options.lazy) {
26+
// 执行副作用函数
27+
effectFn();
28+
}
29+
return effectFn;
30+
}
31+
function cleanup(effectFn) {
32+
// 遍历 effectFn.deps 数组
33+
for (let i = 0; i < effectFn.deps.length; i++) {
34+
// deps 是依赖集合
35+
const deps = effectFn.deps[i];
36+
// 将 effectFn 从依赖集合中移除
37+
deps.delete(effectFn);
38+
}
39+
// 最后需要重置 effectFn.deps 数组
40+
effectFn.deps.length = 0;
41+
}
42+
const bucket = new WeakMap();
43+
function track(target, key) {
44+
// 没有 activeEffect,直接 return
45+
if (!activeEffect)
46+
return;
47+
let depsMap = bucket.get(target);
48+
if (!depsMap) {
49+
bucket.set(target, (depsMap = new Map()));
50+
}
51+
let deps = depsMap.get(key);
52+
if (!deps) {
53+
depsMap.set(key, (deps = new Set()));
54+
}
55+
// 把当前激活的副作用函数添加到依赖集合 deps 中
56+
deps.add(activeEffect);
57+
// deps 就是一个与当前副作用函数存在联系的依赖集合
58+
// 将其添加到 activeEffect.deps 数组中
59+
activeEffect.deps.push(deps); // 新增
60+
}
61+
// 在 set 拦截函数内调用 trigger 函数触发变化
62+
function trigger(target, key, type) {
63+
const depsMap = bucket.get(target);
64+
if (!depsMap)
65+
return;
66+
const effects = depsMap.get(key);
67+
const effectsToRun = new Set();
68+
effects && effects.forEach((effectFn) => {
69+
if (effectFn !== activeEffect) {
70+
effectsToRun.add(effectFn);
71+
}
72+
});
73+
effectsToRun.forEach((effectFn) => {
74+
var _a;
75+
// 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
76+
if ((_a = effectFn.options) === null || _a === void 0 ? void 0 : _a.scheduler) {
77+
effectFn.options.scheduler(effectFn);
78+
}
79+
else {
80+
// 否则直接执行副作用函数(之前的默认行为)
81+
effectFn();
82+
}
83+
});
84+
if (type === 'ADD') {
85+
const iterateEffects = depsMap.get(ITERATE_KEY);
86+
iterateEffects && iterateEffects.forEach((effectFn) => {
87+
if (effectFn !== activeEffect) {
88+
effectsToRun.add(effectFn);
89+
}
90+
});
91+
effectsToRun.forEach((effectFn) => {
92+
var _a;
93+
if ((_a = effectFn === null || effectFn === void 0 ? void 0 : effectFn.options) === null || _a === void 0 ? void 0 : _a.scheduler) {
94+
effectFn.options.scheduler(effectFn);
95+
}
96+
else {
97+
effectFn();
98+
}
99+
});
100+
}
101+
}
102+
function computed(getter) {
103+
// value 用来缓存上一次计算的值
104+
let value;
105+
// dirty 标志,用来标识是否需要重新计算值
106+
let dirty = true;
107+
// 把 getter 作为副作用函数,创建一个 lazy 的 effect
108+
const effectFn = effect(getter, {
109+
lazy: true,
110+
scheduler() {
111+
if (!dirty) {
112+
dirty = true;
113+
// 当计算属性依赖的响应式数据变化时,手动调用 trigger 函数触发响应
114+
trigger(obj, 'value');
115+
}
116+
}
117+
});
118+
const obj = {
119+
get value() {
120+
if (dirty) {
121+
value = effectFn();
122+
dirty = false;
123+
}
124+
track(obj, 'value');
125+
return value;
126+
}
127+
};
128+
return obj;
129+
}
130+
function traverse(value, seen = new Set()) {
131+
// 如果要读取的数据是原始值,或者已经被读取过了,那么什么都不做
132+
if (typeof value !== 'object' || value === null || seen.has(value))
133+
return;
134+
// 将数据添加到 seen 中,代表遍历地读取过了,避免循环引用引起的死循环
135+
seen.add(value);
136+
// 暂时不考虑数组等其他结构
137+
// 假设 value 就是一个对象,使用 for...in 读取对象的每一个值,并递归地调用 traverse 进行处理
138+
for (const k in value) {
139+
traverse(value[k], seen);
140+
}
141+
return value;
142+
}
143+
function watch(source, cb, options) {
144+
let getter;
145+
if (typeof source === 'function') {
146+
getter = source;
147+
}
148+
else {
149+
getter = () => traverse(source);
150+
}
151+
let oldValue, newValue;
152+
// cleanup 用来存储用户注册的过期回调
153+
let cleanup;
154+
// 定义 onInvalidate 函数
155+
function onInvalidate(fn) {
156+
// 将过期回调存储到 cleanup 中
157+
cleanup = fn;
158+
}
159+
const job = () => {
160+
newValue = effectFn();
161+
// 在调用回调函数 cb 之前,先调用过期回调
162+
if (cleanup) {
163+
cleanup();
164+
}
165+
// 将 onInvalidate 作为回调函数的第三个参数,以便用户使用
166+
cb(newValue, oldValue, onInvalidate);
167+
oldValue = newValue;
168+
};
169+
const effectFn = effect(
170+
// 执行 getter
171+
() => getter(), {
172+
lazy: true,
173+
scheduler: () => {
174+
if (options.flush === 'post') {
175+
const p = Promise.resolve();
176+
p.then(job);
177+
}
178+
else {
179+
job();
180+
}
181+
}
182+
});
183+
if (options.immediate) {
184+
job();
185+
}
186+
else {
187+
oldValue = effectFn();
188+
}
189+
}
190+
const obj = {
191+
foo: 1,
192+
get bar() {
193+
// 现在这里的 this 为代理对象 p
194+
return this.foo;
195+
}
196+
};
197+
const ITERATE_KEY = Symbol();
198+
// 代理对象
199+
const p = new Proxy(obj, {
200+
// 拦截读取操作,接收第三个参数 receiver
201+
get(target, key, receiver) {
202+
track(target, key);
203+
// 使用 Reflect.get 返回读取到的属性值
204+
return Reflect.get(target, key, receiver);
205+
},
206+
// 拦截设置操作
207+
set(target, key, newVal, receiver) {
208+
// 如果属性不存在,则说明是在添加新属性,否则是设置已有属性
209+
const type = Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD';
210+
// 设置属性值
211+
const res = Reflect.set(target, key, newVal, receiver);
212+
// 把副作用函数从桶里取出并执行
213+
trigger(target, key, type);
214+
return res;
215+
},
216+
// 拦截 in 操作
217+
has(target, key) {
218+
track(target, key);
219+
return Reflect.has(target, key);
220+
},
221+
// 拦截 for...in 操作
222+
ownKeys(target) {
223+
// 使用ITERATE_KEY 代替 key,forin迭代操作针对对象,使用symbol作为唯一标识
224+
track(target, ITERATE_KEY);
225+
return Reflect.ownKeys(target);
226+
},
227+
// 拦截 delete 操作
228+
deleteProperty(target, key) {
229+
// 删除属性
230+
const res = Reflect.deleteProperty(target, key);
231+
// 触发删除操作
232+
trigger(target, key, 'DELETE');
233+
return res;
234+
},
235+
});
236+
effect(() => {
237+
// obj 是原始数据,不是代理对象,这样的访问不能够建立响应联系
238+
for (const key in p) {
239+
console.log(key);
240+
}
241+
});

0 commit comments

Comments
 (0)