【学习】vue响应系统

发布于:2025-09-09 ⋅ 阅读:(18) ⋅ 点赞:(0)

响应系统

响应式数据与副作用函数组成

响应式数据和副作用函数

副作用函数: 当函数执行时,直接或间接影响其他函数的执行,此时我们可以说该函数生成副作用。
响应式数据: 副作用函数中使用某数据(不在该函数作用域),在副作用函数之外的地方去修改数据时,副作用函数自动执行,那么这个数据就是响应式数据。

响应式数据基本实现

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>


网站公告

今日签到

点亮在社区的每一天
去签到