当前位置: 首页 > wzjs >正文

网站有收录但是没排名免费推广的网站

网站有收录但是没排名,免费推广的网站,山东网站建设网站,自己做的网站打不开是什么原因前言 响应式系统(Reactivity System) 是现代前端框架的核心基础架构,实现数据变更到视图更新的自动化映射。本文将采用TDD(测试驱动开发)方式,完整构建一个简化的Vue3响应式系统,核心技术点包括…

前言

响应式系统(Reactivity System) 是现代前端框架的核心基础架构,实现数据变更到视图更新的自动化映射。本文将采用TDD(测试驱动开发)方式,完整构建一个简化的Vue3响应式系统,核心技术点包括:

  • 依赖追踪机制 :建立数据与视图的关联关系
  • 分支更新策略 :优化条件渲染场景的性能
  • 嵌套函数管理 :支持组件化的副作用层级

1. 环境准备

技术栈

  • 测试框架 : Vitest
  • DOM环境 : jsdom
  • Mock工具 :Vitest内置vi工具集

基础HTML结构

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Vue3响应系统实现</title>
</head>
<body></body>
<!-- 核心响应式系统实现 -->
<script src="reactivity.js"></script>
</html>

2. 基础响应式实现

2.1 需求

通过测试用例定义预期行为:

// reactivity.spec.mjs
import { JSDOM } from 'jsdom'  
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'  let dom  // 使用 describe 函数创建一个测试套件,用于组织一组相关的测试  
describe('视图响应式更新', () => {  // beforeEach 钩子会在每个测试用例运行前执行  beforeEach(async () => {  // 启用虚拟定时器(模拟setTimeout、setInterval等)  // 这样可以在测试中控制时间流转,避免真实等待vi.useFakeTimers()  // 加载HTML文件并创建JSDOM实例  dom = await JSDOM.fromFile('./index.html', {  resources: 'usable', // 允许加载外部资源(CSS、图片等)  runScripts: 'dangerously', // 允许执行HTML中的脚本(可能有安全风险,但对测试必要)  })  // 等待所有资源加载完成  await new Promise((resolve) => (dom.window.onload = resolve))  })  // afterEach钩子会在每个测试用例运行后执行  afterEach(() => {  // 重置所有模拟对象和函数  // 确保测试之间完全隔离,不会互相影响vi.restoreAllMocks()  // 关闭JSDOM创建的window对象  dom.window.close()  })  // 定义单个测试用例,验证视图的响应式更新行为  test('应正确渲染和更新内容', async () => {  // 验证初始渲染  expect(dom.window.document.body.textContent?.trim()).toBe('hello world')  // 使用 vi.advanceTimersByTime 推进虚拟时间  vi.advanceTimersByTime(1000)  // 验证更新后内容  expect(dom.window.document.body.textContent?.trim()).toBe('hello vue3')  }, 5000) // 设置超时时间5秒  
})

2.2 实现

首先执行测试:npm vitest run,错误如下。

在这里插入图片描述

核心原理

响应系统通过Proxy拦截器实现数据访问追踪,结合副作用函数(effect function) 注册机制实现视图更新。当读取数据时收集依赖,修改数据时触发视图更新。

  • Proxy代理机制​​:Proxy是ES2015的特性,可以拦截对象的​​基本操作(intercept operations)​​包括 get/set 两种操作。
  • 依赖收集原理​​:读取数据(get)时收集副作用函数,修改数据(set)时触发收集的函数

由此我们可以实现一个最简单的版本:将下面内容保存到 reactivity.js 中。

// 存储副作用函数
const bucket = new Set()// 原始数据
const data = { text: 'hello world' }// 原始数据代理
const obj = new Proxy(data, {get(target, key) {bucket.add(effect) // 读取数据时收集依赖该数据的副作用函数return target[key]},set(target, key, value) {target[key] = valuebucket.forEach((fn) => fn()) // 设置数据时调用所有绑定的副作用函数return true},
})function effect() {  document.body.textContent = obj.text
}effect()
setTimeout(() => {obj.text = 'hello vue3'
}, 1000)

技术细节

  1. 响应式绑定过程
    • 首次执行 effect() 函数触发 obj.text 的get操作
    • get操作将 effect 函数添加到 bucket 集合
    • 数据变化时触发set操作,遍历执行 bucket 中所有函数
  2. 代理拦截流程
    Client Proxy Target obj.text = "hello vue3" set拦截 设置属性值 遍历bucket调用副作用函数 返回true Client Proxy Target

再次运行测试:

在这里插入图片描述

测试验证:初始渲染"hello world",1秒后变为"hello vue3"

View Proxy Data bucket 读取obj.text 获取真实值 收集effect() 返回初始值 渲染"hello world" 修改obj.text 遍历执行effect() 更新为"hello vue3" View Proxy Data bucket

2.3 重构

先来看看之前的基础实现缺陷

  • 硬编码的 effect 函数不够灵活
  • 全局只能注册一个副作用函数

由此我们进行重构:
在这里插入图片描述
在这里插入图片描述
完整代码如下:

// 存储副作用函数  
const bucket = new Set()  let activeEffect  // 原始数据  
const data = { text: 'hello world' }  // 原始数据代理  
const obj = new Proxy(data, {  get(target, key) {  // 读取数据时收集依赖该数据的副作用函数  if (activeEffect) {  bucket.add(activeEffect)  }  return target[key]  },  set(target, key, value) {  target[key] = value  bucket.forEach((fn) => fn()) // 设置数据时调用所有绑定的副作用函数  return true  },  
})  // 注册副作用函数并执行  
function effect(fn) {  activeEffect = fn  fn()  
}  effect(() => {  document.body.textContent = obj.text  
})  setTimeout(() => {  obj.text = 'hello vue3'  
}, 1000)

技术优化点​​

  • ​​解耦设计​​:通过 activeEffect 动态引用当前副作用函数
  • 函数式编程​​:将副作用函数作为参数传入,支持任意函数的响应式绑定
  • 首次执行机制​​:effect() 内立即执行函数,确保初始依赖收集

重构后再次运行测试,保证功能无异常。

在这里插入图片描述

为了方便后续测试代码的编写,我们还需要重构下之前的测试:

注意 我们还需要把 obj 暴露到 window 对象上,方便测试的编写。

// 定义单个测试用例,验证视图的响应式更新行为  
test('应正确渲染和更新内容', async () => {  const { effect, obj, document } = dom.window  effect(() => {  document.body.textContent = obj.text  })  setTimeout(() => {  obj.text = 'hello vue3'  }, 1000)  // 验证初始渲染  expect(dom.window.document.body.textContent?.trim()).toBe('hello world')  // 使用 vi.advanceTimersByTime 推进虚拟时间  vi.advanceTimersByTime(1000)  // 验证更新后内容  expect(dom.window.document.body.textContent?.trim()).toBe('hello vue3')  
}, 5000) // 设置超时时间5秒

3. 隔离属性的响应关系

3.1 需求

早期实现将所有副作用存入同一Set,导致所有属性变更都会触发更新。

收集
触发
Proxy.get拦截
Set容器
effect1
effect2
任何属性修改

由此我们先定义新的需求:

test('应隔离不同属性的响应', async () => {  const { effect, obj, document } = dom.window  const effectFn = vi.fn(() => {  console.log('effect run')  document.body.textContent = obj.text  })  effect(effectFn)  setTimeout(() => {  obj.noExist = Date.now() // 无关属性的变更,不应该触发 effectFn 执行  }, 1000)  // 验证初始调用  expect(effectFn).toBeCalledTimes(1)  // 使用 vi.advanceTimersByTime 推进虚拟时间  vi.advanceTimersByTime(1000)  // 不应该重复触发更新  expect(effectFn).toBeCalledTimes(1)  
})

运行测试:
在这里插入图片描述

3.2 实现

使用 WeakMap(target→Map(key→Set)) 建立依赖关系树:

// 存储副作用函数  
// WeakMap 键为原始对象,值为另一个 Map(存储对象属性和副作用函数集合的映射)  
const bucket = new WeakMap()  // 存储当前正在注册的副作用函数  
let activeEffect  // 原始数据  
const data = { text: 'hello world' }  // 创建响应式代理对象  
window.obj = new Proxy(data, {  // 拦截属性读取操作  get(target, key) {  // 没有激活的副作用函数时直接返回属性值  if (!activeEffect) return target[key]  // 获取对象对应的依赖映射(Map结构,存储属性与副作用集合的关联)  let depsMap = bucket.get(target)  if (!depsMap) {  depsMap = new Map()  bucket.set(target, depsMap)  }  // 获取属性对应的副作用集合(Set结构,自动去重副作用函数)  let deps = depsMap.get(key)  if (!deps) {  deps = new Set()  depsMap.set(key, deps)  }  // 将当前激活的副作用函数加入集合  deps.add(activeEffect)  // 返回原始对象的属性值  return target[key]  },  // 拦截属性设置操作  set(target, key, value) {  // 设置原始对象属性值  target[key] = value  // 获取对象对应的依赖映射  const depsMap = bucket.get(target)  if (!depsMap) return true  // 获取属性关联的所有副作用函数  const effects = depsMap.get(key)  // 遍历并执行所有关联的副作用函数(触发更新)  effects?.forEach((fn) => fn())  return true  },  
})  // 副作用函数注册器  
function effect(fn) {  // 设置为当前激活的副作用函数  activeEffect = fn  // 首次立即执行(触发依赖收集)  fn()  
}

依赖树技术细节

  1. 三级存储结构
    • WeakMap: 键为原始对象,保证对象释放后相关依赖自动回收
    • Map: 值为对象属性与依赖关系的映射
    • Set: 存储依赖于该属性的副作用函数集合
  2. 数据结构优势
    WeakMap
    Target Object
    Map
    Property: text
    Property: count
    Set
    effect1
    effect2
  3. WeakMap内存管理
    • 使用弱引用不会阻止垃圾回收
    • 当对象不再被引用时,自动释放存储的依赖
    • 避免内存泄漏问题

隔离测试:修改无关属性不会触发更新

在这里插入图片描述

3.3 重构

提取封装 tracetrigger 函数。

// 创建响应式代理对象  
window.obj = new Proxy(data, {  // 拦截属性读取操作  get(target, key) {  // 追踪并存储依赖当前属性的副作用函数  trace(target, key)  // 返回原始对象的属性值  return target[key]  },  // 拦截属性设置操作  set(target, key, value) {  // 设置原始对象属性值  target[key] = value  // 触发依赖当前属性的副作用函数执行  trigger(target, key)  return true  },  
})  // 追踪依赖响应对象更新的副作用函数  
function trace(target, key) {  // 没有激活的副作用函数时直接返回属性值  if (!activeEffect) return  // 获取对象对应的依赖映射(Map结构,存储属性与副作用集合的关联)  let depsMap = bucket.get(target)  if (!depsMap) {  depsMap = new Map()  bucket.set(target, depsMap)  }  // 获取属性对应的副作用集合(Set结构,自动去重副作用函数)  let deps = depsMap.get(key)  if (!deps) {  deps = new Set()  depsMap.set(key, deps)  }  // 将当前激活的副作用函数加入集合  deps.add(activeEffect)  
}  // 触发响应对象的副作用函数执行  
function trigger(target, key) {  // 获取对象对应的依赖映射  const depsMap = bucket.get(target)  if (!depsMap) return  // 获取属性关联的所有副作用函数  const effects = depsMap.get(key)  // 遍历并执行所有关联的副作用函数(触发更新)  effects?.forEach((fn) => fn())  
}  

执行测试,避免危险重构。


4. 动态分支的依赖追踪

4.1 需求

分支切换场景
effect(() => {document.body.textContent = obj.ok ? obj.text : ''
})

obj.ok由true变为false,应移除对obj.text的依赖。

分支切换问题分析

  1. 当条件 obj.ok 为true时,副作用函数依赖于 obj.okobj.text
  2. obj.ok 变为false后,理论上不应再依赖 obj.text
  3. 但默认实现中, obj.text 修改仍会触发执行

编写测试:

test('不应追踪无效分支的响应', async () => {  const { effect, obj, document } = dom.window  obj.ok = true  obj.text = 'ok'  const effectFn = vi.fn(() => {  console.log('effect run')  document.body.textContent = obj.ok ? obj.text : ''  })  effect(effectFn) // 第一次执行 effectFn  expect(dom.window.document.body.textContent?.trim()).toBe('ok')  obj.ok = false // 第二次执行 effectFn  obj.text = 'not ok' // 不应该执行 effectFn  expect(dom.window.document.body.textContent?.trim()).toBe('')  expect(effectFn).toBeCalledTimes(2)  
})

运行测试,查看错误:

在这里插入图片描述

4.2 实现

解决方案:清理历史绑定 + 重新收集依赖

// 副作用函数注册器  
function effect(fn) {  const effectFn = () => {  // 清理当前副作用函数的历史绑定  cleanup(effectFn)  // 设置为当前激活的副作用函数  activeEffect = effectFn  // 首次立即执行(触发依赖收集)  fn()  }  // 存储所有与该副作用函数相关联的依赖集合  effectFn.deps = []  effectFn()  
}
function cleanup(effectFn) {  for (let effects of effectFn.deps) {  effects.delete(effectFn) // 从 Set 集合里移除当前 effectFn  }  effectFn.deps.length = 0 // 重置 effectFn 的依赖,重新收集  
}

还需要修改 tracetrigger 函数:

在这里插入图片描述
在这里插入图片描述
完整代码如下:

// 存储副作用函数  
// WeakMap 键为原始对象,值为另一个 Map(存储对象属性和副作用函数集合的映射)  
const bucket = new WeakMap()  // 存储当前正在注册的副作用函数  
let activeEffect  // 原始数据  
const data = { text: 'hello world' }  // 创建响应式代理对象  
window.obj = new Proxy(data, {  // 拦截属性读取操作  get(target, key) {  // 追踪并存储依赖当前属性的副作用函数  trace(target, key)  // 返回原始对象的属性值  return target[key]  },  // 拦截属性设置操作  set(target, key, value) {  // 设置原始对象属性值  target[key] = value  // 触发依赖当前属性的副作用函数执行  trigger(target, key)  return true  },  
})  // 追踪依赖响应对象更新的副作用函数  
function trace(target, key) {  // 没有激活的副作用函数时直接返回属性值  if (!activeEffect) return  // 获取对象对应的依赖映射(Map结构,存储属性与副作用集合的关联)  let depsMap = bucket.get(target)  if (!depsMap) {  depsMap = new Map()  bucket.set(target, depsMap)  }  // 获取属性对应的副作用集合(Set结构,自动去重副作用函数)  let deps = depsMap.get(key)  if (!deps) {  deps = new Set()  depsMap.set(key, deps)  }  // 将当前激活的副作用函数加入集合  deps.add(activeEffect)  // 将 Set 集合(引用)存储到对应的副作用函数的包装器上  activeEffect.deps.push(deps)  
}  // 触发响应对象的副作用函数执行  
function trigger(target, key) {  // 获取对象对应的依赖映射  const depsMap = bucket.get(target)  if (!depsMap) return  // 获取属性关联的所有副作用函数  const effects = depsMap.get(key)  const effectsToRun = new Set(effects)  // 遍历并执行所有关联的副作用函数(触发更新)  effectsToRun?.forEach((fn) => fn())  
}  function cleanup(effectFn) {  for (let effects of effectFn.deps) {  effects.delete(effectFn) // 从 Set 集合里移除当前 effectFn  }  effectFn.deps.length = 0 // 重置 effectFn 的依赖,重新收集  
}  // 副作用函数注册器  
function effect(fn) {  const effectFn = () => {  // 清理当前副作用函数的历史绑定  cleanup(effectFn)  // 设置为当前激活的副作用函数  activeEffect = effectFn  // 首次立即执行(触发依赖收集)  fn()  }  // 存储所有与该副作用函数相关联的依赖集合  effectFn.deps = []  effectFn()  
}

技术实现细节

  1. 双向依赖关系
    • 依赖集合(Set)存储副作用函数
    • 副作用函数存储其所属的依赖集合
    • 建立双向引用便于清理
  2. 动态依赖收集流程
    • 每次副作用执行前清除其所有依赖关系
    • 执行期间重新收集实际访问的属性
    • 自动移除不需要的依赖关系
  3. 伪代码流程
    复制开始执行副作用函数:→ 清理历史依赖关系→ 执行用户函数→ 触发get操作时:- 将副作用添加到属性的依赖集合- 将依赖集合添加到副作用的依赖数组
    结束执行
    

分支测试:切换分支后不再追踪无效属性

在这里插入图片描述

4.3 重构

可以执行一个微重构,避免每次都重建 Set 对象。

const effectsToRun = new Set()  
// 触发响应对象的副作用函数执行  
function trigger(target, key) {  // 获取对象对应的依赖映射  const depsMap = bucket.get(target)  if (!depsMap) return  // 获取属性关联的所有副作用函数  const effects = depsMap.get(key)  effectsToRun.clear()  effects?.forEach(effect => {effectsToRun.add(effect)})  // 遍历并执行所有关联的副作用函数(触发更新)  effectsToRun?.forEach((fn) => fn())  
}

5. 嵌套副作用函数的支持

5.1 需求

组件化场景需求

嵌套调用副作用函数时需正确管理依赖关系:

effect(() => {effect(innerEffect) // 内层effecttemp = obj.foo
})

编写测试:

test('应正确追踪嵌套响应', async () => {  const { effect, obj } = dom.window  obj.foo = true  obj.bar = true  let temp1, temp2  const innerEffect = vi.fn(() => {  console.log('innerEffect run')  temp2 = obj.bar  })  const outerEffect = vi.fn(() => {  console.log('outerEffect run')  effect(innerEffect)  temp1 = obj.foo  })  effect(outerEffect) // 触发 outerEffect 执行,嵌套的 innerEffect 也应该执行  expect(outerEffect).toBeCalledTimes(1)  expect(innerEffect).toBeCalledTimes(1)  obj.foo = false // 更新 obj.foo,触发 outerEffect 执行,嵌套的 innerEffect 也应该执行  expect(outerEffect).toBeCalledTimes(2)  expect(innerEffect).toBeCalledTimes(2)  obj.bar = false // 更新 obj.bar,触发 innerEffect 执行 1 次  expect(outerEffect).toBeCalledTimes(2)  expect(innerEffect).toBeCalledTimes(3)  
})

执行测试:

在这里插入图片描述

5.2 实现

可见当修改 obj.foo 时,outerEffect 没有执行,只有 innerEffect 执行了。回顾之前的代码,可以知道问题是出在 activeEffect 指向的 effectFn 在退出 innerEffect 后并没有重新指向 outerEffect,故没有收集到 outerEffect 的依赖。

由此,我们可以使用栈结构来保存与恢复现场,添加 effectStack,在执行副作用函数前后进行现场的保存与恢复。

在这里插入图片描述

再次执行测试:

在这里插入图片描述
这里为什么接着改变 obj.bar 时,innerEffect 多执行了一次呢?
因为我们实际存储在 bucket 中的是 innerEffect 的包装器,每次搜集时都会生成不同的包装器,包装器内部都引用了同一个 innerEffect 函数。故之前 effect(outerEffect) 生成了第一个 innerEffect 包装器,而 obj.foo = false 时生成了第二个 innerEffect 包装器。

由此,要避免每次都生成不同的包装器导致重复执行,我们需要将实际执行的副作用函数与其包装器进行绑定,从而避免重复注册不同的包装器。

在这里插入图片描述
最终代码:

const effectStack = []  // 副作用函数注册器  
function effect(fn) {  let effectFn = fn.__effect__  if (!effectFn) {  effectFn = () => {  // 清理当前副作用函数的历史绑定  cleanup(effectFn)  // 设置为当前激活的副作用函数  activeEffect = effectFn  // 执行 fn 之前先把 effectFn 压入栈中  effectStack.push(effectFn)  // 首次立即执行(触发依赖收集)  fn()  // 执行完 fn 再恢复上一个栈信息  effectStack.pop()  activeEffect = effectStack[effectStack.length - 1]  }  fn.__effect__ = effectFn  // 存储所有与该副作用函数相关联的依赖集合  effectFn.deps = []  }  effectFn()  
}

嵌套处理技术细节

  1. 执行栈机制
    • 使用数组作为执行栈存储上下文
    • 进入副作用时压入栈
    • 执行完成时弹出栈
  2. 上下文维护
    effectStack=[outer]
    出栈,activeEffect=outer
    执行outer
    执行inner
  3. 单例包装函数
    • 通过 fn.__effect__ 缓存包装函数
    • 避免多次包装同一函数产生不同实例
    • 确保依赖关系的稳定性

测试验证:

在这里插入图片描述


6. 避免无限递归

6.1 需求

当副作用函数内同时读写同一属性:

effect(() => obj.count++)

触发get→收集effect→set→触发effect的死循环。

编写测试:

test('应避免无限递归响应', async () => {  const { effect, obj } = dom.window  obj.count = 0  const effectFn = vi.fn(() => {  obj.count++  })  effect(effectFn)  expect(effectFn).toBeCalledTimes(1)  
})

执行测试:

在这里插入图片描述

6.2 实现

问题分析

当我们在副作用函数里执行 obj.count++ 时,首先触发了 obj.countget 方法,调用了 trace 函数,然后又触发了 obj.countset 方法,调用了 trigger 函数,而 trigger 函数又再次调用了 effectFn 函数,从而进入了无限递归,最终栈溢出。

由此,我们的解决方案就是跳过当前活跃的副作用函数

在这里插入图片描述

// 触发响应对象的副作用函数执行  
function trigger(target, key) {  // 获取对象对应的依赖映射  const depsMap = bucket.get(target)  if (!depsMap) return  // 获取属性关联的所有副作用函数  const effects = depsMap.get(key)  effectsToRun.clear()  effects?.forEach((effect) => {  if (effect !== activeEffect) {  effectsToRun.add(effect)  }  })  // 遍历并执行所有关联的副作用函数(触发更新)  effectsToRun?.forEach((fn) => fn())  
}

递归防止机制

  1. 核心原理
    • 在trigger中检查副作用函数是否是当前活跃的
    • 如果正在执行则不触发,避免循环调用
  2. Set复制优化
    • 拷贝 Set 避免遍历时修改原集合导致异常
    • 保证遍历过程的稳定性
  3. 执行流程对比
    未防止递归:执行effect → 读取count(收集依赖) → 设置count(触发依赖) → 再次执行effect → 无限循环
    防止递归后:执行effect → 读取count(收集依赖) → 设置count(触发依赖) → 跳过正在执行的effect → 正常结束
    

递归测试

在这里插入图片描述


7. 完整实现与应用

7.1 整合后的代码

// reactivity.js
// 存储副作用函数
// WeakMap 键为原始对象,值为另一个 Map(存储对象属性和副作用函数集合的映射)
const bucket = new WeakMap()// 存储当前正在注册的副作用函数
let activeEffect// 原始数据
const data = { text: 'hello world' }// 创建响应式代理对象
window.obj = new Proxy(data, {// 拦截属性读取操作get(target, key) {// 追踪并存储依赖当前属性的副作用函数trace(target, key)// 返回原始对象的属性值return target[key]},// 拦截属性设置操作set(target, key, value) {// 设置原始对象属性值target[key] = value// 触发依赖当前属性的副作用函数执行trigger(target, key)return true},
})// 追踪依赖响应对象更新的副作用函数
function trace(target, key) {// 没有激活的副作用函数时直接返回属性值if (!activeEffect) return// 获取对象对应的依赖映射(Map结构,存储属性与副作用集合的关联)let depsMap = bucket.get(target)if (!depsMap) {depsMap = new Map()bucket.set(target, depsMap)}// 获取属性对应的副作用集合(Set结构,自动去重副作用函数)let deps = depsMap.get(key)if (!deps) {deps = new Set()depsMap.set(key, deps)}// 将当前激活的副作用函数加入集合deps.add(activeEffect)// 将 Set 集合(引用)存储到对应的副作用函数的包装器上activeEffect.deps.push(deps)
}const effectsToRun = new Set()// 触发响应对象的副作用函数执行
function trigger(target, key) {// 获取对象对应的依赖映射const depsMap = bucket.get(target)if (!depsMap) return// 获取属性关联的所有副作用函数const effects = depsMap.get(key)effectsToRun.clear()effects?.forEach((effect) => {if (effect !== activeEffect) {effectsToRun.add(effect)}})// 遍历并执行所有关联的副作用函数(触发更新)effectsToRun?.forEach((fn) => fn())
}function cleanup(effectFn) {for (let effects of effectFn.deps) {effects.delete(effectFn) // 从 Set 集合里移除当前 effectFn}effectFn.deps.length = 0 // 重置 effectFn 的依赖,重新收集
}const effectStack = []// 副作用函数注册器
function effect(fn) {let effectFn = fn.__effect__if (!effectFn) {effectFn = () => {// 清理当前副作用函数的历史绑定cleanup(effectFn)// 设置为当前激活的副作用函数activeEffect = effectFn// 执行 fn 之前先把 effectFn 压入栈中effectStack.push(effectFn)// 首次立即执行(触发依赖收集)fn()// 执行完 fn 再恢复上一个栈信息effectStack.pop()activeEffect = effectStack[effectStack.length - 1]}fn.__effect__ = effectFn// 存储所有与该副作用函数相关联的依赖集合effectFn.deps = []}effectFn()
}

7.2 系统架构全景图

Proxy代理
Proxy代理
响应式对象
get拦截
set拦截
track收集依赖
trigger触发更新
WeakMap存储依赖
Map属性映射
Set副作用集合
副作用函数
effect注册器
执行栈管理
依赖清理
嵌套支持
动态依赖追踪
7.3 真实场景应用
// app.js
const { effect, obj } = window// 创建响应式UI
effect(() => {const app = document.getElementById('app')app.innerHTML = `<div>状态管理示例:</div><div>文本内容: ${obj.text}</div><div>计数: ${obj.count}</div><div>状态: ${obj.ok ? '启用' : '禁用'}</div><button onclick="obj.count++">增加计数</button><button onclick="obj.ok=!obj.ok">切换状态</button><input type="text" value="${obj.text}" oninput="obj.text = this.value">`
})

8. 总结与延伸

核心收获
  1. 响应式原理:通过Proxy拦截操作,结合WeakMap建立依赖关系树
  2. 依赖管理:动态绑定/解绑副作用函数,提升性能
  3. 执行控制:利用栈结构实现嵌套effect的正确执行
扩展学习
  • WeakMap优化内存:无需手动清除依赖项,GC自动回收
  • Vue3响应系统对比
    基于
    封装
    关联
    类似
    Vue3源码
    ReactiveEffect
    Dep Set
    Dep
    WeakMap
    本文实现
    简易版targetMap
  • 下期预告
    • 调度器:实现 effect 的灵活调度
    • 计算属性:基于effect的延迟计算
    • Watch API:调度器控制执行时机

延伸阅读:https://vuejs.org/guide/essentials/reactivity-fundamentals.html

http://www.dtcms.com/wzjs/78367.html

相关文章:

  • 福州专业做网站的公司哪家好来宾seo
  • 网站建设html实训心得301313龙虎榜
  • 南京哪公司建设网站网站优化公司哪家好
  • 做商城网站设计爱站网注册人查询
  • 万能网址大全seo是什么的简称
  • 宣讲家网站做四讲四有模范重庆网络推广
  • 电子商务网站源码下载免费推广软件
  • 目前个人网站做地最好是哪几家zac seo博客
  • 网站模板图广州百度seo优化排名
  • 盐城市城乡建设局网站教育培训栏目长沙靠谱seo优化价格
  • 衢州 网站建设数据分析网
  • wordpress单页面主题刷排名seo软件
  • 外贸网络推广员站长工具 seo综合查询
  • 为什么做电影网站没有流量教育培训机构管理系统
  • 现在都是用什么做网站免费入驻的跨境电商平台
  • 高密做网站哪家强代理上海b2b网络推广外包
  • 做网站放太多视频seo关键词外包公司
  • 辽宁大学网站怎么做网页设计html代码大全
  • 泰州建设局网站搜索引擎网站排名
  • 中国人做代购的网站百度指数有什么参考意义
  • 如何建设游戏网站爱站网影院
  • 深圳网站seo建设企业seo网络推广
  • 做有色金属哪个网站好我要学电脑哪里有短期培训班
  • 做网站需要具备哪些条件北京seo排名优化网站
  • 网站首页图怎么做神马站长平台
  • 小零件加工在家做网站优化外包公司
  • 营销型网站建设原则百度网站站长工具
  • 天津市住房和城乡建设委员会官方网站培训机构连锁加盟
  • zzcms网站开发东莞网络优化公司
  • 网站布局分析金融网站推广圳seo公司