手写 Vue 源码 === Effect 机制解析

目录

核心概念

响应式效果的实现

依赖收集的具体流程

为什么使用全局变量?

嵌套 effect 的处理

总结


Vue3 的响应式系统核心在于跟踪依赖并在数据变化时触发更新。effect.ts文件实现了这一机制的核心部分,下面我们来梳理其中的关键思路。

核心概念

// 创建一个响应式对象 effect
export function effect(fn, options: any = {}) {
  // 创建一个 effect 只要依赖的属性变化,就会重新执行
  const _effect = new ReactiveEffect(fn, () => {
    _effect.run();
  });
  // 执行
  _effect.run();
}

export let activeEffect; // 当前的 effect

响应式效果的实现

ReactiveEffect

类是整个响应式系统的核心,它负责:

  1. 存储用户传入的回调函数
  2. 提供执行机制
  3. 处理嵌套 effect 的情况
class ReactiveEffect {
  /**
   * fn 是用户传入的回调函数「如果fn中依赖了响应式数据,当数据变化后,会重新调用scheduler -->run」
   * scheduler 是调度器
   */
  public active = true; //默认是响应式的
  constructor(public fn, public scheduler) {}
  run() {
    // 如果当前状态是停止的,执行后,啥都不做
    if (!this.active) {
      return this.fn();
    }

    /**
     *     effect(() => {
        console.log(state.name);
        effect(() => {
            console.log(state.name);
        })
        console.log(state.age);
    })
    执行前,保存当前的 activeEffect 可以理解为 「栈」执行完后,恢复上一次的 activeEffect
    */
    let lastEffect = activeEffect;
    try {
      activeEffect = this; // 当前的 effect 「依赖收集」
      return this.fn(); //依赖收集 「state.name ,state.age」
    } finally {
      activeEffect = lastEffect; // 执行完毕后 恢复上一次的 activeEffect
    }
  }
}

activeEffect 是一个全局变量,它的核心作用是:标记当前正在执行的 effect。这是依赖收集的关键环节,让系统知道"当前访问的属性应该与哪个 effect 建立联系"

依赖收集的具体流程

让我们通过一个具体的例子来理解这个过程:

const state = reactive({ name: 'zhang', age: 18 });

effect(() => {
  console.log(state.name); // 访问 name 属性
});

当这段代码执行时,发生了以下步骤: 

  1. 调用 effect 函数,创建一个 ReactiveEffect 实例
  2. 调用 _effect.run()
  3. 在 run 方法中,将 activeEffect 设置为当前 effect 实例
  4. 执行用户传入的回调函数 fn()
  5. 在回调函数中访问 state.name
  6. 访问属性触发 Proxy 的 get 捕获器
  7. get 捕获器调用 track 函数
get(target: any, key: any, receiver: any) {
  // ...
  track(target, key);
  return Reflect.get(target, key, receiver);
}

track 函数检查 activeEffect 是否存在,如果存在,就建立依赖关系

export function track(target, key) {
  if (activeEffect) {
    console.log(activeEffect, key);
    // 这里应该建立 target[key] 和 activeEffect 的映射关系
  }
}

为什么使用全局变量?

使用全局变量activeEffect的好处是:

  1. 简化 API 设计,用户不需要手动指定依赖关系
  2. 自动追踪运行时的依赖,只收集真正访问的属性
  3. 支持动态依赖收集,依赖可以根据条件变化

嵌套 effect 的处理

Vue3 巧妙地处理了嵌套 effect 的情况,通过维护当前活跃的 effect 和保存上一个 effect 的方式:

effect(() => {
  console.log(state.name);  // 外层 effect 依赖 state.name
  
  effect(() => {
    console.log(state.name);  // 内层 effect 依赖 state.name
  })
  
  console.log(state.age);  // 外层 effect 还依赖 state.age
})

当处理嵌套 effect 时,代码会:

  • 保存当前的 activeEffect 到 lastEffect 变量
  • 将 activeEffect 设置为当前执行的 effect
  • 执行完毕后,恢复 activeEffect 为之前保存的值
  • 这就像一个栈结构,确保每个属性访问都能正确关联到当前正在执行的 effect。

总结

Vue3 响应式系统的核心在于:

  1. 通过  effect 函数创建响应式效
  2. 果使用 ReactiveEffect类管理回调函数和执行逻辑
  3. 通过  activeEffect 全局变量跟踪当前正在执行的 effect
  4. 在属性访问时进行依赖收集
  5. 在属性设置时触发相关 effect 重新执行

Vue3 响应式系统通过activeEffect 全局变量巧妙地解决了"谁在使用这个属性"的问题,实现了自动依赖收集。当访问响应式对象的属性时,系统知道是哪个 effect 正在运行,从而建立属性与 effect 之间的映射关系,为后续的响应式更新奠定基础。

<think>嗯,用户之前已经问过React的原理和高频面试题,现在又接着问中高级前端面试的场景题。看起来他可能在准备前端面试,特别是中高级职位。需要确定用户的需求是想要了解常见的面试题目,还是具体的应对策略,或者是面试流程中的注意事项。 首先,用户的问题比较广泛,可能希望得到一个全面的覆盖。中高级前端面试通常会涉及更深入的原理、项目经验、系统设计以及算法等方面。需要分模块整理,确保每个部分都有典型问题和解答思路,帮助用户系统性地准备。 用户之前已经了解过React相关的问题,这次可能需要扩展其他前端领域的知识,比如浏览器原理、性能优化、框架对比、Web安全等。同时,中高级职位可能更注重架构设计、项目深度和解决问题的能力,因此需要包含系统设计题和开放性问题。 还要考虑用户可能的薄弱环节,比如性能优化、Webpack配置、算法等,这些都是中高级面试常见的考点。另外,项目经验部分需要用户结合自己的实际经历,但可以提供如何结构化回答的建议。 需要确保回答结构清晰,每个大类下都有具体的题目和解析,帮助用户逐步理解和准备。同时,提醒用户不仅要记住答案,还要理解背后的原理,并能结合实际项目经验进行阐述,这样在面试中才能更自信。</think>--- ### 中高级前端面试高频场景题(附解析思路) 以下问题覆盖核心原理、性能优化、框架设计等深度方向,建议结合项目经验灵活应对。 --- #### **一、原理与源码类** 1. **浏览器渲染机制与优化** - **问题**:从输入URL到页面显示,详细描述浏览器的关键步骤。 - **参考答案**: - DNS解析 → TCP连接 → HTTP请求 → 服务器响应 → 解析HTML构建DOM树 → 构建CSSOM树 → 生成渲染树(Render Tree) → 布局(Layout) → 绘制(Paint)。 - **优化点**:减少重排(如使用`transform`替代`top/left`)、预加载关键资源、CDN加速。 2. **Vue/React框架对比** - **问题**:Vue和React在响应式数据更新上的核心差异是什么? - **参考答案**: - **Vue**:基于`Object.defineProperty`或`Proxy`实现数据劫持,自动追踪依赖; - **React**:依赖手动`setState`触发更新,通过虚拟DOM Diff局部更新。 3. **Webpack打包优化** - **问题**:如何将构建时间从60s优化到10s以内? - **策略**: - 使用`cache-loader`或Webpack5持久化缓存; - 多线程编译(`thread-loader`); - 缩小文件搜索范围(`resolve.modules`指定路径)。 --- #### **二、性能优化类** 1. **首屏加载时间过长** - **问题**:如何将首屏加载时间从5s降到1s? - **解决方案**: - 代码分割(`React.lazy` + `Suspense`); - 图片懒加载 + WebP格式; - 服务端渲染(SSR)或静态生成(SSG)。 2. **内存泄漏排查** - **问题**:页面长时间运行后卡顿,如何定位内存泄漏? - **步骤**: 1. Chrome DevTools → Memory面板记录堆快照; 2. 对比多次快照,查找未释放的DOM节点或闭包引用; 3. 检查事件监听未解绑(如`addEventListener`)。 --- #### **三、框架设计类** 1. **实现一个简易响应式系统** - **问题**:手写代码实现Vue的`data`和`watch`功能。 - **核心代码逻辑**: ```javascript class Dep { constructor() { this.subscribers = new Set(); } depend() { if (activeEffect) this.subscribers.add(activeEffect); } notify() { this.subscribers.forEach(effect => effect()); } } function reactive(obj) { Object.keys(obj).forEach(key => { const dep = new Dep(); let value = obj[key]; Object.defineProperty(obj, key, { get() { dep.depend(); return value; }, set(newVal) { value = newVal; dep.notify(); } }); }); return obj; } ``` 2. **设计一个前端埋点SDK** - **需求**:支持PV/UV统计、错误监控、性能指标。 - **设计要点**: - 数据上报方式(`navigator.sendBeacon`防丢失); - 错误捕获(`window.onerror` + Promise异常拦截); - 性能指标(`Performance API`获取FP/FCP)。 --- #### **四、开放性问题** 1. **技术选型争议** - **问题**:为什么选择React而不是Vue? - **回答方向**: - 生态丰富性(如Ant Design、React Native); - 大型项目协作优势(TS支持更完善); - 团队技术栈统一性。 2. **项目难点与解决** - **问题**:描述一个你解决过最复杂的前端问题。 - **回答模板**: 1. 场景描述(如“百万级数据表格渲染卡顿”); 2. 分析根因(DOM节点过多、重复渲染); 3. 解决方案(虚拟滚动 + 分页加载 + Web Worker计算)。 --- #### **五、算法与工程化** 1. **手写Promise.all** - **要求**:处理异步任务并发与错误捕获。 - **代码示例**: ```javascript function promiseAll(promises) { return new Promise((resolve, reject) => { let results = [], count = 0; promises.forEach((p, i) => { p.then(res => { results[i] = res; if (++count === promises.length) resolve(results); }).catch(reject); }); }); } ``` 2. **实现一个自动化部署脚本** - **需求**:从代码提交到生成生产环境包的全流程。 - **关键步骤**: - Git Hook触发CI/CD(如GitHub Actions); - 执行单元测试 + 代码规范检查(ESLint); - 构建后自动上传至服务器(rsync/SCP)。 --- ### **面试技巧** 1. **STAR法则**:描述项目时按**情境(Situation)→ 任务(Task)→ 行动(Action)→ 结果(Result)**结构化回答。 2. **追问准备**:对简历中的技术关键词(如“微前端”、“WebGL”)深入理解,避免被问倒。 3. **反问环节**:主动询问团队技术栈、项目挑战、晋升机制,展现主动性。 掌握以上内容,可覆盖80%中高级前端面试核心考点,建议结合自身项目深度实践! 🚀
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端 贾公子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值