计算属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>计算属性</title>
</head>
<body>
<script>
// 用一个全局变量存储被注册的副作用函数
let activeEffect;
// effect 栈(避免嵌套副作用函数发生错乱)
const effectStack = [];
// 存储副作用函数的桶
const bucket = new WeakMap();
const data = {
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);
},
});
//定义一个任务队列
const jobQueue = new Set()
// 使用 Promise.resolve()创建一个 promise 实例,我们用它将一个任务添加到微任务队列
const p = Promise.resolve() //一个标志代表是否正在刷新队列
let isFlushing = false
function flushJob() { // 如果队列正在刷新, 则什么都不做
if (isFlushing) return // 设置为 true, 代表正在刷新
isFlushing = true
// 在微任务队列中刷新 jobQueue 队列
p.then(() => {
jobQueue.forEach(job => job())
}).finally(() => { //结束后重置 isFlushing
isFlushing = false
})
}
// effect(() => {
// obj.foo + obj.bar
// }, {
// scheduler(fn) {
// /// 每次调度时, 将副作用函数添加到 jobQueue 队列中
// jobQueue.add(fn)
// // 调用 flushJob 刷新队列
// flushJob()
// }
// })
//在get 拦截函数内调用 track 函数追踪变化
function track(target, key) {
// 没有 activeEffect,直接 return
if (!activeEffect) return;
let depsMap = bucket.get(target);
if (!depsMap) {
bucket.set(target, (depsMap = new Map()));
}
let deps = depsMap.get(key);
if (!deps) {
depsMap.set(key, (deps = new Set()));
}
// 把当前激活的副作用函数添加到依赖集合deps中
deps.add(activeEffect);
// 将其添加到activeEffect.deps数组中
activeEffect.deps.push(deps);
}
//在set 拦截函数内调用 trigger 函数触发变化
function trigger(target, key) {
const depsMap = bucket.get(target);
if (!depsMap) return;
const effects = depsMap.get(key);
const effectsToRun = new Set();
effects && effects.forEach(effect => {
// 如果trigger触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行(避免无限递归)
if (effect !== activeEffect) {
effectsToRun.add(effect);
}
});
effectsToRun && effectsToRun.forEach((effectFn) => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn); //新增
} else {
effectFn()
}
});
}
function effect(fn, options = {}) {
const effectFn = () => {
// debugger
// 调用cleanup 函数清除依赖
cleanup(effectFn);
// 当 effectFn 执行时,将其设置为当前激活的副作用函数
activeEffect = effectFn;
// 在调用副作用函数之前,将当前副作用函数压入 effectStack 栈中
effectStack.push(effectFn);
const res = fn();
// 当 effectFn 执行完毕,将其从 effectStack 中移除
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
return res
};
// 将options挂载到effectFn上
effectFn.options = options;
// activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合 effectFn.deps[]
effectFn.deps = [];
// 只有非lazy的时候执行副作用函数
if (!options.lazy) {
effectFn();
}
return effectFn
}
// 函数清除依赖
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; }
}
// 计算属性
function computed(getter) {
let value
let dirty = true
// debugger
// 把getter作为副作用函数,创建一个lazy的effect
const effectFn = effect(getter, {
lazy: true,
scheduler: () => {
dirty = true
// 当计算属性依赖发生变化时,手动调用trigger函数触发响应
trigger(obj, 'value')
}
})
const obj = {
// 当读取value时才执行effectFn
get value() {
if (dirty) {
value = effectFn();
dirty = false
}
// 当读取value时,手动调用 track 函数进行追踪
track(obj, 'value')
return value
}
}
return obj
}
const sumRes = computed(() => obj.foo + obj.bar);
effect(() => {
console.log(sumRes.value);
})
obj.foo++
// console.log(sumRes.value);
</script>
</body>
</html>
Last updated