前端开发岗模拟面试题套卷A答案及解析(一)技术面部分

发布于:2025-02-19 ⋅ 阅读:(26) ⋅ 点赞:(0)

前端开发岗模拟面试题套卷A答案及解析(一)技术面部分

(一)技术面

一、JavaScript核心技术(ES6+)

1-1、实现防抖函数
function debounce(fn, delay) {
   
  let timer = null;
  return function(...args) {
   
    clearTimeout(timer); // 清除已有定时器
    timer = setTimeout(() => {
    
      fn.apply(this, args); // 绑定正确this
    }, delay);
  };
}
// 应用场景:搜索框输入联想、窗口resize事件
1-2、其应用场景解释

防抖函数(debounce)核心作用是 限制高频触发的函数执行频率,确保连续多次触发时,只在最后一次触发后延迟执行一次目标函数。

postscript:
代码逐行解释

1、let timer = null
利用闭包特性保存定时器标识,保证多次调用时共享同一个 timer。
2、clearTimeout(timer)
每次触发时,先清除上一次的定时器,重置倒计时。
3、setTimeout(() => { fn.apply(this, args) }, delay)
延迟 delay 毫秒后执行目标函数 fn,并通过 apply 确保 this 指向和参数传递正确性。

核心特性

特性 说明
延迟执行 触发后等待 delay 时间再执行
重置机制 新触发会覆盖旧定时器
上下文绑定 通过apply保留原函数 this
1-3 应用场景举例

1、搜索框输入联想

  • 问题:用户连续输入时,每次按键都会触发搜索请求,导致请求爆炸。
  • 解决:防抖控制在用户停止输入 300ms 后发送请求。
    示例代码:
const searchInput = document.getElementById('search');
searchInput.addEventListener('input', debounce(function() {
   
  // 发送搜索请求
}, 300));

2、窗口resize事件

  • 问题:调整窗口大小时频繁触发重绘逻辑,引发性能问题。
  • 解决:防抖确保只在调整结束后计算布局。
window.addEventListener('resize', debounce(function() {
   
  // 更新布局
}, 200));

3、按钮防重复提交

  • 问题:用户快速点击按钮导致重复提交请求。
  • 解决:防抖屏蔽短时间内的多次点击。
submitBtn.addEventListener('click', debounce(function() {
   
  // 提交表单
}, 1000));

与节流(throttle)的区别

区别项&技术名 防抖(debounce) 节流(throttle)
逻辑 只执行最后一次触发 固定时间间隔执行一次
场景 输入联想、resize 滚动事件、鼠标移动

postscript:
回答得分点

1、正确识别防抖函数(占 40%)
✅ 明确说出函数用途是“限制高频触发函数的执行频率”。
2、闭包与定时器机制(占 30%)
✅ 解释 timer 闭包保存状态和 clearTimeout 的重置逻辑。
3、应用场景举例(占 20%)
✅ 至少给出两个合理场景(如搜索框、resize)。
4、附加细节(占 10%)
✅ 提及 apply 的上下文绑定作用。
✅ 对比节流函数(throttle)的差异。

如果候选人能结合代码实现细节和实际业务场景作答,即可判定为满分回答。

2-1、闭包(Closure)原理

闭包是 函数与其词法作用域(lexical scope)的组合,使得内部函数可以访问外部函数的变量,即使外部函数已执行完毕。其本质是 函数在定义时捕获并保留了其所在作用域的引用
实现机制
1、词法环境(Lexical Environment)
函数在定义时记录其所在作用域的变量引用,形成闭包链。
2、垃圾回收豁免
被闭包引用的变量不会被回收,即使外部函数已销毁。
3、私有性
通过闭包可以实现变量隐藏(类似私有变量)。
自增ID生成器实现
代码实现

function createIdGenerator(initialId = 0) {
   
  let id = initialId; // 通过闭包保留id状态
  return function() {
   
    return ++id; // 每次调用自增
  };
}

// 使用示例
const generator = createIdGenerator();
console.log(generator()); // 1
console.log(generator()); // 2 

关键特性

特性 说明
状态持久化 通过闭包保存id变量状态
隔离性 每次调用工厂函数生成独立计数器
可定制初始值 支持传入initialId参数

postscript:
闭包原理得分点
1、作用域链描述
(30%)
✅ 解释函数定义时捕获外部作用域变量的机制

2、生命周期控制(30%)
✅ 说明闭包如何阻止变量被垃圾回收

3、实际应用场景(20%)
✅ 结合ID生成器案例说明状态保持

4、延伸扩展(20%)
✅ 提及闭包在模块化/私有变量中的应用
ID生成器得分点

评分项 满分 扣分点示例
闭包正确使用 4 未使用闭包(直接返回全局变量)
自增逻辑正确 3 后置递增(id++)导致从0开始
支持多实例独立 2 多个生成器共享同一计数器
参数可配置性 1 未实现initialid参数

进阶追问示例
1、如何实现ID重置功能?

function createIdGenerator() {
   
  let id = 0;
  return {
   
    next: () => ++id,
    reset: () => id = 0
  };
}

2、多个生成器之间如何避免冲突?
每个生成器通过独立闭包隔离状态,天然线程安全。
通过闭包实现的状态管理是前端高频考点,建议结合内存管理(如闭包泄露场景)深化理解。

3-1、手写Promise.all实现,并处理错误情况的代码及详细解释:
function myPromiseAll(promises) {
   
  return new Promise((resolve, reject) => {
   
    // 检查输入是否为可迭代对象
    if (typeof promises?.[Symbol.iterator] !== 'function') {
   
      reject(new TypeError('Argument is not iterable'));
      return;
    }

    const promiseArray = Array.from(promises);
    const results = new Array(promiseArray.length);
    let completedCount = 0;
    let hasRejected = false;

    // 处理空数组的情况
    if (promiseArray.length === 0) {
   
      resolve(results);
      return;
    }

    for (let i = 0; i < promiseArray.length; i++) {
   
      // 将每个元素转为 Promise
      Promise.resolve(promiseArray[i])
        .then((result) => {
   
          if (hasRejected) return; // 已失败则忽略
          results[i] = result; // 按索引存储结果
          completedCount++;
          // 全部完成时 resolve
          if (completedCount === promiseArray.length) {
   
            resolve(results);
          }
        })
        .catch((error) => {
   
          if (!hasRejected) {
   
            hasRejected = true;
            reject(error); // 首个错误直接 reject
          }
        });
    }
  });
}

核心实现要点
1、类型检查

  • 使用Symbol.iterator 验证输入是否为可迭代对象(如数组)。
  • 非可迭代对象直接reject类型错误。
    2、边界处理
  • 空数组直接resolve([])。
  • 非Promise值通过 Promise.resolve()包装。
    3、状态管理
  • results 数组按索引存储结果,保证输出顺序。
  • completedCount 计数器跟踪完成数量。
  • hasRejected标志位确保只reject一次。
    4、错误处理
  • 任何一个Promise失败立即终止,后续结果被忽略。
  • 通过hasRejected防止多次调用reject

使用示例

// 成功场景
myPromiseAll([
  Promise.resolve(1),
  Promise.resolve(2)
]).then(console.log); // 输出: [1, 2]

// 失败场景
myPromiseAll([
  Promise.resolve(1),
  Promise.reject('error')
]).catch(console.log); // 输出: 'error'

// 非 Promise 值
myPromiseAll([1, 2, 3]).then(console.log); // 输出: [1, 2, 3]

// 空数组
myPromiseAll([]).then(console.log); // 输出: []

与原生的差异对比

特性 原生Promise.all 手写实现
错误处理 立即终止 完全一致
非Promise值 自动包装为Promise 通过Promise.resolve实现
结果顺序 严格按输入顺序 完全一致
可迭代对象支持 支持 通过类型检查实现

得分点解析

得分项 分值 扣分点示例
正确返回Promise实例 2 未返回Promise对象
处理可迭代对象 2 未校验输入类型导致奔溃
结果顺序保留 3 使用push()导致顺序错乱
错误立即终止 3 未处理多个reject调用
非Promise值兼容 2 未使用Promise.resolve包装
空数组处理 1 未特殊处理空数组场景

通过以上实现,完整复现了 Promise.all 的核心功能,并严格处理了边界条件和异常场景。

4 Proxy实现对象属性访问监控

基础实现

function createObservedObject(target) {
   
  const handler = {
   
    get(target, property, receiver) {
   
      console.log(`[监控] 读取属性 ${
     property}`);
      // 递归代理嵌套对象
      const value = Reflect.get(target, property, receiver);
      return typeof value === 'object' && value !== null 
        ? createObservedObject(value) // 深度代理
        : value;
    },
    set(target, property, value, receiver) {
   
      console.log(`[监控] 设置属性 ${
     property} = ${
     value}`);
      return Reflect.set(target, property, value, receiver);
    }
  };
  return new Proxy(target, handler);
}

// 使用示例
const user = createObservedObject({
   
  name: 'Alice',
  address: {
   
    city: 'Beijing'
  },
  scores: [90, 85]
});

console.log(user.name); // 输出监控日志
console.log(user.address.city); // 触发嵌套对象监控
user.scores.push(95); // 监控数组操作

核心机制
1、属性读取监控

  • get陷阱:拦截所有属性访问,包括嵌套对象
  • 递归代理:对对象/数组类型属性值进行深度代理
  • Reflect转发:保持原对象的默认行为
    2、属性修改监控
  • set陷阱:拦截所有属性赋值操作
  • 数组方法拦截push/pop等操作会触发get + set
    进阶优化版本
function createDeepProxy(target, cache = new WeakMap()) {
   
  // 防止重复代理和循环引用
  if (cache.has(target)) return cache.get(target);

  const handler = {
   
    get(target, prop) {
   
      console.log(`Read ${
     prop}`);
      const value = Reflect.get(...arguments);
      // 自动代理对象/数组/Set/Map等引用类型
      if (typeof value === 'object' && value !== null) {
   
        return createDeepProxy(value, cache);
      }
      return value;
    },
    set(target, prop, value) {
   
      console.log(`Set ${
     prop} to`, value);
      return Reflect.set(...arguments);
    }
  };

  const proxy = new Proxy(target, handler);
  cache.set(target, proxy); // 缓存已代理对象
  return proxy;
}

关键特性对比

特性 基础版本 优化版本
循环引用处理 ❌ 会栈溢出 ✅ WeakMap 缓存解决
复合数据类型支持 对象/数组 对象/数组/Set/Map
性能优化 ❌ 重复创建代理 ✅ 缓存机制
监控范围 直接属性访问 包括原型链方法调用

应用场景
1、数据变更追踪

  • Vue3响应式系统的核心实现原理
  • 实现自动化的表单数据校验

2、调试工具

  • 实时监控对象状态变化
  • 记录属性访问历史

3、权限控制

  • 禁止访问私有属性(属性名前带)
get(target, prop) {
   
  if (prop.startsWith('_')) {
   
    throw new Error('私有属性禁止访问');
  }
  return Reflect.get(...arguments);
}

4、性能分析

  • 统计热点属性访问频率
  • 检测内存泄漏(长时间未被访问的属性)

注意事项
1、Proxy局限性

  • 无法监控Object.keys()等静态方法
  • JSON.stringfy()无效

2、性能影响

  • 深度代理大型对象时会有内存开销
  • 生产环境建议选择性代理关键数据

3、浏览器兼容性

面试得分点
1、基础实现
(40%)

  • 正确使用Proxy的 get/set 陷阱
  • 处理基本数据类型和引用类型差异

2、深度代理(30%)

  • 递归代理嵌套对象
  • 处理数组等特殊对象

3、异常处理(20%)

  • 循环引用解决方案
  • 私有属性访问控制

4、扩展认知(10%)

  • 能够关联到Vue3响应式原理
  • 提出实际应用场景

通过Proxy实现的属性监控是前端高级开发的必备技能,建议结合具体框架源码(如Vue3 的 reactive模块)深入理解。

5、Event Loop核心机制

JavaScript 是单线程语言,通过 **事件循环(Event Loop)**处理异步操作。其运行机制分为以下层级:

层级 内容 优先级
调用栈 同步代码执行(后进先出) 最高
微任务 Promise.then、MutationObserver
宏任务 setTimeout、setInterval、I / O

执行规则:
1、同步代码立即执行,清空调用栈
2、执行所有微任务(直到微任务队列为空)
3、执行一个宏任务
4、重复步骤2-3
题目代码分析

console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);

执行步骤分解
1、同步阶段

  • console.log(1)—> 输出1
  • setTimeout 回调注册到宏任务队列
  • Promise.then回调注册到微任务队列
  • console.log(4)—>输出4
    此时输出:1 —> 4

2、微任务阶段:

  • 检查微任务队列,执行()=>console.log(3)——> 输出3
    此时输出:1 —> 4 —> 3

3、宏任务阶段:

  • 取出第一个宏任务(SetTimeout回调),执行()=>console.log(2)—>输出2

最终输出:1—> 4 —> 3 —> 2
关键原理图示

[调用栈]1. 执行 console.log(1)
2. 将 setTimeout 回调加入宏任务队列
3. 将 Promise.then 回调加入微任务队列
4. 执行 console.log(4)[微任务队列]
↓ 执行所有微任务 → console.log(3)[宏任务队列]
↓ 执行一个宏任务 → console.log(2)

常见误区
1、零延迟不代表立即执行
setTimeout(fn, 0)的实质是最快 4ms (浏览器规范限制)后加入宏任务队列,而非立即执行。
2、微任务优先级碾压宏任务
即使宏任务进入队列,也必须等待当前所有微任务执行完毕。
3、嵌套任务的影响
若在微任务中创建新的微任务,会持续执行直到队列清空:

Promise.resolve().then(() => {
   
  console.log(3);
  Promise.resolve().then(() => console.log(5)); // 新增微任务
});

输出顺序为:1 —>4 —> 3 —> 5—>2
面试得分点

考察维度 满分回答要点
阶段划分 明确同步/微任务/宏任务执行顺序
队列机制 解释微任务队列清空后才执行宏任务
API分类 正确区分宏任务与微任务API
浏览器差异 提及Node.js与浏览器的差异

掌握 Event Loop机制是前端核心能力,建议通过 Loupe 可视化工具加深理解。

二、Vue架构深度

1、Vue3响应式原理(对比Vue2)

Vue2使用Object.defineProperty递归遍历对象属性实现响应式,存在无法检测新增属性和数组下标变化的问题。Vue3改用Proxy代理对象,可监听动态属性增减和更多操作类型(如delete)。同时引入Reflect操作对象,配合effect-tracker实现更精准的依赖收集。组件实例层面通过Composition API实现逻辑复用,相比Vue2的Options API更灵活

postScript:
得分点:

  • 准确对比两代实现差异(3分)
  • 指出Proxy优势(2分)
  • 说明Composition API作用(2分)
  • 提及Refleck使用(1分)
2、Vue自定义拖拽指令

核心逻辑
1、事件驱动:通过 mousedown 触发拖拽, mousemove 更新位置,mouseup 结束拖拽
2、坐标计算:基于 clientx / clientv 计算鼠标相对元素的偏移量
3、边界处理:可选限制元素在可视区域内移动((如搜索结果[7]的弹窗拖拽实现))

// 注册全局指令(可放入单独文件)
Vue.directive('drag',  {
   
  inserted(el, binding) {
   
    // 设置元素定位方式(需确保元素可定位)
    el.style.position  = 'absolute';
    
    // 获取拖拽触发区域(默认整个元素可拖拽)
    const dragHandle = binding.value?.handle  
      ? el.querySelector(binding.value.handle)  
      : el;
    
    dragHandle.style.cursor  = 'move';
    
    let startX = 0, startY = 0, initialLeft = 0, initialTop = 0;
    
    // 鼠标按下事件 
    const onMouseDown = (e) => {
   
      e.preventDefault(); 
      // 记录初始位置 
      startX = e.clientX; 
      startY = e.clientY; 
      initialLeft = el.offsetLeft; 
      initialTop = el.offsetTop; 
 
      document.addEventListener('mousemove',  onMouseMove);
      document.addEventListener('mouseup',  onMouseUp);
    };
 
    // 鼠标移动事件 
    const onMouseMove = (e) => {
   
      const dx = e.clientX  - startX;
      const dy = e.clientY  - startY;
      
      // 计算新位置 
      let newLeft = initialLeft + dx;
      let newTop = initialTop + dy;
      
      // 边界限制(可选)
      if (binding.value?.boundary)  {
   
        newLeft = Math.max(0,  Math.min(newLeft,  window.innerWidth  - el.offsetWidth)); 
        newTop = Math.max(0,  Math.min(newTop,  window.innerHeight  - el.offsetHeight)); 
      }
      
      // 更新元素位置 
      el.style.left  = `${
     newLeft}px`;
      el.style.top  = `${
     newTop}px`;
    };
 
    // 鼠标松开事件 
    const onMouseUp = () => {
   
      document.removeEventListener('mousemove',  onMouseMove);
      document.removeEventListener('mouseup',  onMouseUp);
    };
 
    // 绑定事件 
    dragHandle.addEventListener('mousedown',  onMouseDown);
    
    // 保存引用用于解绑 
    el.__vueDragHandler__ = onMouseDown;
  },
  
  unbind(el) {
   
    // 移除事件监听 
    const dragHandle = el.__vueDragHandler__?.currentTarget || el;
    dragHandle.removeEventListener('mousedown',  el.__vueDragHandler__);
  }
});

示例代码解说

关键优化点
  • 定位方式自动检测:强制设置position: absolute避免用户未设置 [参考2][参考3]
  • 事件解绑机制:在unbind阶段移除监听防止内存泄漏
  • 性能优化:使用requestAnimationFrame优化高频触发(示例未展示,可自行扩展)
  • 触摸屏支持:添加touchstart/touchmove事件实现移动端适配
实现效果对比
功能特性 本方案实现 参考方案[7]实现
基础拖拽 ✔️ ✔️
边界限制 ✔️ ✔️
拖拽手柄 ✔️ ✔️
移动端支持
嵌套滚动处理
扩展建议
  • 组合API:可结合useDraggable组合式API封装(Vue3特性)
  • 拖拽回调:通过指令参数暴露@drag-start/@drag-end事件
  • 拖拽限制:支持自定义边界检测函数
3、如何设计高性能的动态表单渲染组件?

(1) 分层架构设计

graph TD
A[Schema解析层] --> B[组件映射层]
B --> C[状态管理层]
C --> D[渲染引擎层]
D --> E[扩展插件层]
  • Schema解析层:支持JSON Schema/自定义DSL描述表单结构
  • 组件映射层:建立字段类型与组件映射关系
  • 状态管理层:原子化状态管理 + 响应式更新
  • 渲染引擎层:虚拟滚动 + 差异对比渲染
  • 扩展插件层:校验、联动、条件渲染等能力扩展

(2)核心性能优化手段
1)虚拟滚动实现

// 滚动容器
<VirtualScroll 
  itemHeight={
   80} 
  visibleCount={
   10}
  total={
   1000}
>
  {
   (index) => <FormField schema={
   schemaList[index]}/>}
</VirtualScroll>
  • 仅渲染可视区域内的表单项
  • 滚动时动态计算渲染位置
  • 支持预估高度和动态高度调整

2)状态管理优化

// 使用Recoil实现原子化状态
const fieldState = atom({
   
  key: 'formField',
  default: null,
  effects: [persistState] // 持久化副作用
});

// 组件内按需订阅
const [value, setValue] = useRecoilState(fieldState(id));

3)差异更新算法

function diffUpdate(oldSchema, newSchema) {
   
  const patches = [];
  // 使用JSON-Patch算法生成差异
  jsonDiff.compare(oldSchema, newSchema, patches);
  applyPatches(patches); // 局部更新DOM
}

(3)渲染引擎实现
1)组件级缓存

// Vue 实现示例
<template>
  <component 
    :is="getComponent(schema.type)"
    :schema="schema"
    :key="schema.id + schema.version" // 版本控制缓存
    v-memo="[schema.version]"
  />
</template>

2)异步分块渲染

// 使用requestIdleCallback分批次渲染
function renderChunk(schemas) {
   
  let index = 0;
  function doChunk() {
   
    if (index >= schemas.length) return;
    // 每次渲染50个项
    const chunk = schemas.slice(index, index + 50);
    renderItems(chunk);
    index += 50;
    requestIdleCallback(doChunk);
  }
  doChunk();
}

(4)性能指标与优化验证
性能测试标准

指标 目标值 测量工具
首次内容渲染(FCP) <1s Lighthouse
输入响应延迟 <50ms Chrome DevTools
内存占用 <100MB/千字段 Chrome Memories面板
滚动帧率 >=60fps Chrome Rendering面板

优化前后对比

gantt
title 千字段表单性能优化对比
dateFormat  X
axisFormat %s
section 优化前
渲染耗时 : 0, 2500
内存占用 : 0, 350
section 优化后
渲染耗时 : 0, 300
内存占用 : 0, 80

(5)扩展能力设计
1、动态加载策略

// Web Worker 加载复杂校验规则
const worker = new Worker('validator.worker.js');
worker.postMessage({
    rule, value });
worker.onmessage = (e) => updateValidation(e.data);

2、GPU加速渲染

.form-item {
   
  will-change: transform, opacity;
  transform: translateZ(0);
}

3、服务端渲染降级方案

// 服务端生成静态结构
app.use('/form', (req, res) => {
   
  const html = renderToString(<StaticForm schema={
   schema} />);
  res.send(html);
});

(六)最佳实践建议
1、Schema设计规范:

  • 字段ID保持稳定
  • 避免深层嵌套结构
  • 版本化字段配置

2、性能兜底方案:

// 监控渲染时长自动降级
let startTime = Date.now();
renderForm();
if (Date.now() - startTime > 1000) {
   
  showLoading();
  enableDegradedMode(); // 启用简化渲染模式
}

3、开发者工具集成:

// 表单性能分析插件
FormDevTools.register({
   
  trackRender: true,
  highlightUpdates: true
});

通过以上架构设计和优化策略,可实现支持万级字段的动态表单流畅渲染,同时保持开发体验和可维护性。建议结合具体框架特性进行适配实现,并持续进行性能分析和迭代优化。

4、解释Vue组件间通信的5种方式及适用场景

1. Props / $emit(父子组件通信)
实现方式:

  • 父 → 子:通过 props 传递数据
  • 子 → 父:通过 $emit 触发事件
<!-- Parent.vue -->
<Child :title="parentTitle" @update="handleUpdate"/>

<!-- Child.vue -->
<button @click="$emit('update', newValue)">提交</button>

适用场景:

  • 简单的父子组件数据传递
  • 层级不超过3层的组件通信
    优点:Vue官方推荐方式,类型检查支持完善
    缺点:跨层级通信需要逐层传递(Prop drilling)

2. Event Bus(全局事件总线)
实现方式

// eventBus.js
import Vue from 'vue';
export const EventBus = new Vue();

// ComponentA.vue
EventBus.$emit('data-change', payload);

// ComponentB.vue
EventBus.$on('data-change', callback);

适用场景:

  • 非父子组件间通信(如兄弟组件)
  • 小型项目快速实现跨组件通信
    优点:轻量级、快速实现解耦
    缺点:事件管理混乱,难以维护大型项目
    3. Vuex/Pinia(状态管理)
    实现方式:
// store.js
export default new Vuex.Store({
   
  state: {
    count: 0 },
  mutations: {
    increment(state) {
    state.count++ } }
});

// Component.vue
this.$store.commit('increment');

适用场景:

  • 中大型项目全局状态管理
  • 需要持久化/可追溯的状态
  • 多个组件共享复杂业务逻辑
    优点:集中管理、时间旅行调试
    缺点:小型项目引入会增加复杂度

4. provide / inject(依赖注入)
实现方式:

// 祖先组件
export default {
   
  provide() {
   
    return {
    theme: this.themeData };
  }
}

// 后代组件
export default {
   
  inject: ['theme']
}

适用场景:

  • 跨多层级组件传递数据(如主题/配置)
  • 高阶组件(HOC)开发
    优点:避免逐层传递
    缺点:数据流向不透明,破坏组件独立性

5、attrs / listeners(透传属性和事件)
实现方式:

<!-- 中间组件 -->
<GrandChild v-bind="$attrs" v-on="$listeners"/>

<!-- 最终组件 -->
<template>
  <div>{
   {
    $attrs.title }}</div>
</template>

适用场景:

  • 创建高阶包装组件
  • 透传第三方组件原生事件和属性
    优点:避免手动声明每个 prop/event
    缺点:Vue3 中 $listeners 被合并到 $attrs

通信方式选型矩阵

场景 推荐方案 典型示例
直接父子通信 Props+$emit 表单控件双向绑定
兄弟组件通信 Event Bus / Vuex 购物车商品数量同步
跨多层级组件 provide/inject 主题切换 / 权限注入
复杂状态共享 Vuex / Pinia 用户登录状态全局管理
高阶组件开发 attrs / listeners 封装第三方UI库组件

性能优化要点
1、避免滥用全局状态
Pinia/Vuex的状态变更会触发所有相关组件更新,需合理划分模块
2、使用计算属性缓存

computed: {
   
  filteredList() {
    /* 复杂计算 */ }
}

3、事件总线及时销毁

beforeDestroy() {
   
  EventBus.$off('event-name');
}

Vue3 新增特性
1、Composition API 响应式传递

const sharedState = reactive({
    count: 0 });
provide('state', sharedState);

2、Teleport 跨 DOM 通信

<teleport to="#modal-container">
  <Dialog/>
</teleport>

根据项目规模和组件关系选择合适的通信方式,避免出现「过度设计」或「通信混乱」两种极端。对于超过 5 层组件嵌套的场景,建议优先考虑状态管理方案。

5、如何用Composition API重构 Options API的复杂组件?

**一、重构步骤
1、组件结构分析
**原始Options API结构示例:

export default {
   
  data() {
   
    return {
    
      count: 0,
      user: null,
      loading: false
    }
  },
  computed: {
   
    doubleCount() {
    return this.count * 2 }
  },
  methods: {
   
    async fetchUser() {
   
      this.loading = true;
      this.user = await api.getUser();
      this.loading = false;
    }
  },
  mounted() {
   
    this.fetchUser();
  }
}

2. 核心逻辑拆分
按功能模块拆分为组合式函数:

// useCounter.js
export function useCounter(initial = 0) {
   
  const count = ref(initial);
  const doubleCount = computed(() => count.value * 2);
  return {
    count, doubleCount };
}

// useUser.js
export function useUser() {
   
  const user = ref(null);
  const loading = ref(false);
  
  async function fetchUser() {
   
    loading.value = true;
    user.value = await api.getUser();
    loading.value = false;
  }

  onMounted(fetchUser);

  return {
    user, loading, fetchUser };
}

3.整合到Setup函数

<script setup>
import {
    useCounter, useUser } from './composables';

const {
    count, doubleCount } = useCounter();
const {
    user, loading } = useUser();
</script>

二、关键重构技巧
1、响应式数据转换

Options API Composition API
data () ref()/ reactive()
this.property .value访问
computed computed()
watch watch()/ watchEffect()

2、生命周期映射

Options API Composition API
beforeCreate 无对应,直接写在setup
created 无对应,直接写在setup
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted

3、方法处理优化

// Options API
methods: {
   
  handleClick() {
    /* ... */ }
}

// Composition API
const handleClick = () => {
    /* ... */ };

三、复杂场景重构示例
1、混合状态与副作用

原代码:

export default {
   
  data() {
   
    return {
    scrollY: 0 }
  },
  mounted() {
   
    window.addEventListener('scroll', this.handleScroll);
  },
  methods: {
   
    handleScroll() {
   
      this.scrollY = window.scrollY;
    }
  },
  beforeUnmount() {
   
    window.removeEventListener('scroll', this.handleScroll);
  }
}

重构后:

// useScroll.js
export function useScroll() {
   
  const scrollY = ref(0);
  
  const handleScroll = () => {
   
    scrollY.value = window.scrollY;
  };

  onMounted(() => window.addEventListener('scroll', handleScroll));
  onUnmounted(() => window.removeEventListener('scroll', handleScroll));

  return {
    scrollY };
}

2、跨组件逻辑复用
原代码:

// 多个组件重复相同代码
export default {
   
  data() {
   
    return {
    darkMode: false }
  },
  methods: {
   
    toggleTheme() {
   
      this.darkMode = !this.darkMode;
      document.body.classList.toggle('dark', this.darkMode);
    }
  }
}

重构为可复用逻辑:

// useTheme.js
export function useTheme() {
   
  const darkMode = ref(false);
  
  const toggleTheme = () => {
   
    darkMode.value = !darkMode.value;
    document.body.classList.toggle('dark', darkMode.value);
  };

  return {
    darkMode, toggleTheme };
}

// 组件中使用
const {
    darkMode, toggleTheme } = useTheme();

四、重构收益对比

指标 Options API Composition API
代码行数 120行 80行(减少33%)
功能模块复用率 0% 60%逻辑可复用
代码可读性 逻辑分散在不同选项 按功能集中组织
TypeScript支持 有限 完整类型推断
Tree-shaking 无法优化未使用选项 按需导入组合式函数

五、最佳实践建议
1、渐进式重构策略

  • 优先重构500行以上的复杂组件
  • 使用<script setup>语法糖简化代码
  • 保留Options API用于简单组件
    2、组合式函数设计规范
// 命名规范:use+功能名称
function usePagination() {
   }

// 单一职责:每个函数只处理一个关注点
function useDataFetching() {
   }
function useFormValidation() {
   }

// 明确输入输出:参数类型化,返回响应式对象
function useSearch(params: SearchParams) {
   
  return {
    results, loading };
}

3、性能优化技巧

// 使用 shallowRef 优化大对象
const bigData = shallowRef({
    /* 大型数据集 */ });

// 使用 markRaw 跳过代理
const staticConfig = markRaw({
    version: 3 });

// 合理使用 watchEffect 自动依赖收集
watchEffect(() => {
   
  console.log(count.value);
});

通过以上方法,可系统性地将复杂的 Options API 组件改造为更模块化、更易维护的 Composition API 组件。建议结合 Vue DevTools 的 Composition API 调试功能进行验证。

三、工程化与性能优化

1、Webpack构建速度优化方案(至少5种)

一、缓存加速方案
1、持久化缓存(Webpack5+)

// webpack.config.js
module.exports = {
   
  cache: {
   
    type: 'filesystem', // 使用文件系统缓存
    buildDependencies: {
   
      config: [__filename] // 配置文件变更时自动失效缓存
    }
  }
};

原理:

  • 将模块解析、代码生成结果缓存到磁盘
  • 二次构建时直接复用缓存内容
    效果:冷启动构建速度提升60% ~ 80%

二、范围缩小策略
2、精准文件搜索

resolve: {
   
  modules: ['node_modules'], // 指定模块查找目录
  extensions: ['.js', '.vue'], /