响应系统
响应式数据与副作用函数组成
响应式数据和副作用函数
副作用函数: 当函数执行时,直接或间接影响其他函数的执行,此时我们可以说该函数生成副作用。
响应式数据: 副作用函数中使用某数据(不在该函数作用域),在副作用函数之外的地方去修改数据时,副作用函数自动执行,那么这个数据就是响应式数据。
响应式数据基本实现
Proxy: 给目标对象定义一个关联的代理对象,为开发者提供拦截基础操作并向基本操作嵌入额外的行为。
Reflect: 是JavaScript提供的一组对象操作集。
目录结构
- reactive
index.js - index.html
reactive/index.js
/**
* @description 存放副作用函数的桶
* @type {WeakMap<Object,Map<string,Set<Function>>>}
*/
const bucket = new WeakMap()
/**
* @description 当前激活的副作用函数
* @type {Function | undefined}
*/
let activeEffect;
/**
* @description 副作用函数栈 (解决当嵌套副作用函数时activeEffect永远指向最内层副作用函数)
* @type {Array<Function>}
*/
let effectsStack = []
/**
* @description 副作用函数任务队列
* @type {Set<Function>}
*/
let jobQueue = new Set();
/**
* @description 代表是否正在刷新队列
* @type {boolean}
*/
let isFlushing = false;
/**
* @description 在一个周期内只会执行一次
*/
const flushJob = () => {
if (isFlushing) {
return
}
isFlushing = true
const p = Promise.resolve()
p.then(() => {
jobQueue.forEach(fn => fn())
}).finally(() => {
isFlushing = false
})
}
/**
* @description 收集副作用函数
* @param {Object} target
* @param {string | symbol} key
*/
const track = (target, key) => {
if (activeEffect === undefined) {
return
}
let depMaps = bucket.get(target);
if (depMaps === undefined) {
depMaps = new Map()
bucket.set(target, depMaps)
}
let depSets = depMaps.get(key);
if (depSets === undefined) {
depSets = new Set()
depMaps.set(key, depSets)
}
depSets.add(activeEffect)
activeEffect.depSets.push(depSets)
}
/**
* @description 触发副作用函数
* @param {Object} target
* @param {string | symbol} key
*/
const trigger = (target, key) => {
const depMaps = bucket.get(target);
if (depMaps) {
const depSets = depMaps.get(key)
if (depSets) {
const newDepSets = new Set()
depSets.forEach(fn => {
// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行 (解决同时在副作用函数中读取和设置响应式数据而造成的栈溢出)
if (fn !== activeEffect) {
newDepSets.add(fn)
}
})
newDepSets.forEach(fn => {
// 连续多次修改只触发一次副作用函数
jobQueue.add(fn)
flushJob()
})
}
}
}
/**
* @description 清除与副作用关联的依赖性(解决在当前条件下响应式数据不用在关联的副作用函数)
* @param {Function} effectFn
*/
const cleanup = (effectFn) => {
for (let index = 0; index < effectFn.depSets.length; index++) {
const depSets = effectFn.depSets[index];
depSets.delete(effectFn)
}
effectFn.depSets.length = 0;
}
/**
* @description 响应式数据
* @param {Object} obj
*/
const reactive = (obj) => {
return new Proxy(obj, {
set(target, key, value, receiver) {
const flag = Reflect.set(target, key, value, receiver)
trigger(target, key)
return flag
},
get(target, key, receiver) {
track(target, key)
return Reflect.get(target, key, receiver)
}
})
}
/**
* @description 副作用函数
* @param {Function} fn
*/
const effect = (fn) => {
const effectFn = () => {
cleanup(effectFn);
activeEffect = effectFn;
effectsStack.push(effectFn)
fn();
effectsStack.pop()
activeEffect = effectsStack[effectsStack.length - 1]
}
effectFn.depSets = []
effectFn();
}
export { reactive, effect }
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>toy</title>
</head>
<body></body>
<script type="module">
import { reactive, effect } from "./reactive/index.js";
const data = reactive({ foo: 1, bar: 4 });
effect(() => {
console.log(data.foo);
});
data.foo++;
data.foo++;
console.log("结束了");
</script>
</html>