前端面试题(vue)

目录

  • 一、基础必答(所有厂都考,正确率必须100%)
    • [1. Vue 核心基础](#1. Vue 核心基础)
      • [1. Vue2 和 Vue3 响应式原理的区别?各自的优缺点?](#1. Vue2 和 Vue3 响应式原理的区别?各自的优缺点?)
      • [2. v-if 和 v-show 的区别及使用场景?](#2. v-if 和 v-show 的区别及使用场景?)
      • [3. computed 和 watch 的区别?watchEffect 对比 watch 有什么不同?](#3. computed 和 watch 的区别?watchEffect 对比 watch 有什么不同?)
      • [4. 组件间通信有哪些方式?(至少说出6种,重点说跨层级方案)](#4. 组件间通信有哪些方式?(至少说出6种,重点说跨层级方案))
      • [5. v-for 为什么要加 key?为什么不能用 index 作为 key?](#5. v-for 为什么要加 key?为什么不能用 index 作为 key?)
      • [6. Vue 的生命周期钩子(Vue2/Vue3)?setup 执行时机?](#6. Vue 的生命周期钩子(Vue2/Vue3)?setup 执行时机?)
      • [7. 双向绑定 v-model 的原理?Vue3 中 v-model 与 Vue2 的差异?](#7. 双向绑定 v-model 的原理?Vue3 中 v-model 与 Vue2 的差异?)
      • [8. 自定义指令的使用场景和实现方式?](#8. 自定义指令的使用场景和实现方式?)
    • [2. Vue 生态基础](#2. Vue 生态基础)
      • [1. VueRouter 路由守卫有哪些?分别用在什么场景?](#1. VueRouter 路由守卫有哪些?分别用在什么场景?)
      • [2. 路由懒加载的实现方式?为什么要做懒加载?](#2. 路由懒加载的实现方式?为什么要做懒加载?)
      • [3. Vuex/Pinia 的核心概念?Pinia 对比 Vuex 有什么优势?](#3. Vuex/Pinia 的核心概念?Pinia 对比 Vuex 有什么优势?)
      • [4. VueRouter 的 hash 模式和 history 模式区别?history 模式部署需要后端做什么配置?](#4. VueRouter 的 hash 模式和 history 模式区别?history 模式部署需要后端做什么配置?)
  • 二、进阶加分(大厂/中厂重点,外包偶尔问)
    • [1. Vue 原理进阶](#1. Vue 原理进阶)
      • [1. Vue3 中 Proxy 为什么能解决 Vue2 响应式的缺陷?](#1. Vue3 中 Proxy 为什么能解决 Vue2 响应式的缺陷?)
      • [2. Vue 的虚拟 DOM 和 Diff 算法原理?Vue3 的 Diff 做了哪些优化?](#2. Vue 的虚拟 DOM 和 Diff 算法原理?Vue3 的 Diff 做了哪些优化?)
      • [3. 组件的异步组件怎么实现?Suspense 组件的使用场景?](#3. 组件的异步组件怎么实现?Suspense 组件的使用场景?)
      • [4. Vue3 的 Composition API 对比 Options API 的优势?解决了什么问题?](#4. Vue3 的 Composition API 对比 Options API 的优势?解决了什么问题?)
      • [5. Vue 的 mixin 有什么优缺点?为什么推荐用组合式 API 替代 mixin?](#5. Vue 的 mixin 有什么优缺点?为什么推荐用组合式 API 替代 mixin?)
      • [6. 谈谈 Vue 的依赖收集和触发更新过程?](#6. 谈谈 Vue 的依赖收集和触发更新过程?)
      • [7. nextTick 的原理和使用场景?](#7. nextTick 的原理和使用场景?)
    • [2. 工程化 & 性能优化](#2. 工程化 & 性能优化)
      • [1. Vue 项目中如何做性能优化?(至少5点,结合渲染、打包、运行时)](#1. Vue 项目中如何做性能优化?(至少5点,结合渲染、打包、运行时))
      • [2. Webpack 打包 Vue 项目时,如何做体积优化?](#2. Webpack 打包 Vue 项目时,如何做体积优化?)
      • [3. Vite 为什么比 Webpack 快?Vite 构建 Vue 项目的流程?](#3. Vite 为什么比 Webpack 快?Vite 构建 Vue 项目的流程?)
      • [4. 大型 Vue 项目中,如何做状态管理的拆分?](#4. 大型 Vue 项目中,如何做状态管理的拆分?)
      • [5. Vue 项目中如何实现权限控制?(路由权限、按钮权限)](#5. Vue 项目中如何实现权限控制?(路由权限、按钮权限))
      • [6. 虚拟滚动在 Vue 中的实现方式?](#6. 虚拟滚动在 Vue 中的实现方式?)
  • 三、架构拔高(大厂必考,中厂选考,外包基本不考)
      • [1. 如何设计一个可复用的 Vue 组件库?](#1. 如何设计一个可复用的 Vue 组件库?)
      • [2. Vue 项目中如何实现微前端?(qiankun 接入流程、样式隔离、通信)](#2. Vue 项目中如何实现微前端?(qiankun 接入流程、样式隔离、通信))
      • [3. Vue 服务端渲染(SSR)的原理?Nuxt.js 的核心流程?SSR 对比 CSR 的优劣势?](#3. Vue 服务端渲染(SSR)的原理?Nuxt.js 的核心流程?SSR 对比 CSR 的优劣势?)
      • [4. 大型 Vue 项目的目录结构如何设计?](#4. 大型 Vue 项目的目录结构如何设计?)
      • [5. Vue3 + TypeScript 开发时,如何做类型约束?](#5. Vue3 + TypeScript 开发时,如何做类型约束?)
      • [6. **如何做 Vue 项目的错误监控和性能监控?**](#6. 如何做 Vue 项目的错误监控和性能监控?)
      • [7. Vue 项目跨端方案(uni-app/Taro)的选型和踩坑点?](#7. Vue 项目跨端方案(uni-app/Taro)的选型和踩坑点?)
  • 四、手写代码(所有厂都可能现场考,优先掌握)
      • [1. 手写 Vue3 自定义 hook(如:useDebounce、useLocalStorage)](#1. 手写 Vue3 自定义 hook(如:useDebounce、useLocalStorage))
      • [2. 手写简易版 Vue 响应式(Vue3 Proxy 版本)](#2. 手写简易版 Vue 响应式(Vue3 Proxy 版本))
      • [3. 手写一个通用的 Vue 分页组件(支持自定义配置、事件回调)](#3. 手写一个通用的 Vue 分页组件(支持自定义配置、事件回调))
      • [4. 实现 Vue 中的防抖/节流指令(v-debounce/v-throttle)](#4. 实现 Vue 中的防抖/节流指令(v-debounce/v-throttle))
      • [5. 手写 Pinia 持久化插件(基于 localStorage)](#5. 手写 Pinia 持久化插件(基于 localStorage))
  • 五、场景题(大厂/中厂重点,考察落地能力)
      • [1. 场景1:Vue 项目首屏加载慢,你如何定位问题并优化?](#1. 场景1:Vue 项目首屏加载慢,你如何定位问题并优化?)
      • [2. 场景2:多人协作开发 Vue 项目,如何保证代码规范和质量?](#2. 场景2:多人协作开发 Vue 项目,如何保证代码规范和质量?)
      • [3. 场景3:Vue 项目中遇到内存泄漏,你如何排查和解决?](#3. 场景3:Vue 项目中遇到内存泄漏,你如何排查和解决?)
      • [4. 场景4:移动端 Vue 项目适配方案?](#4. 场景4:移动端 Vue 项目适配方案?)
  • 总结

一、基础必答(所有厂都考,正确率必须100%)

1. Vue 核心基础

1. Vue2 和 Vue3 响应式原理的区别?各自的优缺点?

  • Vue2 :基于 Object.defineProperty 劫持对象属性的 get/set,遍历对象的每个属性实现响应式。
    • 优点:兼容性好(支持 IE9+),逻辑简单易懂;
    • 缺点:
      ① 无法监听数组下标修改、数组长度变化;
      ② 无法监听对象新增/删除属性;
      ③ 需递归遍历对象,性能随对象层级加深下降。
  • Vue3 :基于 Proxy 代理整个对象,而非单个属性。
    • 优点:
      ① 天然支持数组/对象的所有操作(新增/删除/下标修改);
      ② 非侵入式(无需修改原对象);
      ③ 懒代理(访问属性时才递归子对象,性能更优);
    • 缺点:兼容性差(不支持 IE),需通过编译降级兼容。

2. v-if 和 v-show 的区别及使用场景?

  • 核心区别
    • v-if:「条件渲染」,不满足条件时组件不会渲染(DOM 不存在),切换时会触发组件生命周期(创建/销毁);
    • v-show:「样式隐藏」,组件始终渲染(DOM 存在),仅通过 display: none 控制显示/隐藏,切换仅修改样式。
  • 使用场景
    • v-if:条件少变(如权限控制、首次加载根据接口数据渲染),适合节省初始渲染开销;
    • v-show:条件频繁切换(如 tab 切换、弹窗显隐),适合减少频繁的 DOM 操作开销。

3. computed 和 watch 的区别?watchEffect 对比 watch 有什么不同?

  • computed vs watch

    维度 computed watch
    本质 计算属性,依赖缓存 监听属性,无缓存
    触发时机 依赖变化时自动计算 监听值变化时执行回调
    返回值 必须返回一个值 无返回值(仅执行逻辑)
    使用场景 衍生值计算(如拼接字符串、过滤列表) 异步操作、复杂逻辑(如监听输入框变化请求接口)
  • watchEffect vs watch

    • watch:需显式指定监听的数据源(如 watch(() => state.count, () => {})),可获取新旧值;
    • watchEffect:隐式监听回调内用到的所有响应式数据,无需指定数据源,无法获取新旧值,初始化时会立即执行一次。

4. 组件间通信有哪些方式?(至少说出6种,重点说跨层级方案)

  • Props / Emits:父子通信(父传子用 props,子传父用 emits),最基础;
  • v-model:父子双向绑定(语法糖,本质是 props + emits);
  • ref / parent / children:父通过 ref 获取子组件实例,子通过 $parent 获取父实例(不推荐,耦合度高);
  • provide / inject:跨层级通信(祖组件 provide 提供数据,后代组件 inject 注入),适合深层嵌套组件;
  • Pinia/Vuex:全局状态管理,适合任意组件间通信(尤其是跨页面、跨层级);
  • 事件总线(mitt):通过发布/订阅模式通信,适合中小型项目的非核心数据通信;
  • attrs / listeners:透传属性/事件(父传孙,无需中间组件转发);
  • VueRouter 路由参数:跨页面通信(query/params)。

5. v-for 为什么要加 key?为什么不能用 index 作为 key?

  • key 的作用:作为虚拟 DOM 的唯一标识,Vue Diff 算法通过 key 识别节点的「复用性」,避免不必要的 DOM 重建,提升渲染性能。
  • 不能用 index 作为 key 的原因
    当列表数据重新排序/增删时,index 会随位置变化(如删除第1项,原第2项的 index 变为0),导致 Vue 误判「节点已变化」,触发不必要的 DOM 卸载/重建,甚至引发数据与 DOM 不匹配的 bug(如输入框值错乱)。
  • 推荐:用数据的唯一标识(如 id、uuid)作为 key。

6. Vue 的生命周期钩子(Vue2/Vue3)?setup 执行时机?

  • Vue2 生命周期
    创建阶段:beforeCreate → created(可访问数据,DOM 未生成);
    挂载阶段:beforeMount → mounted(DOM 渲染完成,可操作 DOM);
    更新阶段:beforeUpdate → updated(数据更新,DOM 重新渲染);
    销毁阶段:beforeDestroy → destroyed(组件销毁,清除定时器/事件)。
  • Vue3 生命周期(组合式 API)
    替换:beforeCreate/created → setup;
    其余:onMounted、onUpdated、onUnmounted(对应 mounted/updated/destroyed);
    新增:onRenderTracked(追踪渲染依赖)、onRenderTriggered(触发渲染时)。
  • setup 执行时机:在 beforeCreate 之前执行(此时 this 为 undefined),仅执行一次,用于初始化组合式 API 的数据和方法。

7. 双向绑定 v-model 的原理?Vue3 中 v-model 与 Vue2 的差异?

  • 原理 :语法糖,Vue2 中 v-model="value" 等价于 :value="value" @input="value = $event.target.value"
  • Vue2 vs Vue3
    • Vue2:一个组件只能有一个 v-model,默认绑定 value 属性 + input 事件;
    • Vue3:支持多个 v-model,可自定义绑定的属性和事件(如 v-model:name 绑定 name 属性 + update:name 事件),取消 .sync 修饰符(统一用 v-model 替代)。

8. 自定义指令的使用场景和实现方式?

  • 使用场景:操作 DOM 相关的逻辑(如防抖/节流、输入框自动聚焦、图片懒加载、权限按钮隐藏);

  • 实现方式(Vue3)

    html 复制代码
    <template>
      <input v-focus />
    </template>
    <script setup>
    // 全局指令
    import { app } from './main'
    app.directive('focus', {
      mounted(el) { el.focus() } // 指令钩子:mounted 时执行
    })
    // 局部指令
    const vFocus = {
      mounted(el) { el.focus() }
    }
    </script>
  • 核心钩子:created(元素创建)、mounted(元素挂载)、updated(元素更新)、unmounted(元素卸载)。

2. Vue 生态基础

1. VueRouter 路由守卫有哪些?分别用在什么场景?

  • 按范围分三类:
    全局守卫
    • router.beforeEach:路由跳转前(如登录验证、权限控制);
    • router.afterEach:路由跳转后(如页面埋点、修改标题);
      路由独享守卫beforeEnter(定义在路由配置中,仅当前路由生效,如详情页权限);
      组件内守卫
    • beforeRouteEnter:进入组件前(无法访问 this,需通过回调);
    • beforeRouteUpdate:路由参数变化(如 /user/1 → /user/2,组件复用);
    • beforeRouteLeave:离开组件前(如提示未保存表单)。

2. 路由懒加载的实现方式?为什么要做懒加载?

  • 目的:拆分代码包,减少首屏加载的 JS 体积,提升首屏速度;

  • 实现方式(Vue3)

    js 复制代码
    // 路由配置中使用 import 动态导入
    const routes = [
      {
        path: '/home',
        component: () => import('@/views/Home.vue') // 懒加载
      }
    ]
    // 按需分组(打包到同一个 chunk)
    const routes = [
      {
        path: '/user',
        component: () => import(/* webpackChunkName: "user" */ '@/views/User.vue')
      }
    ]

3. Vuex/Pinia 的核心概念?Pinia 对比 Vuex 有什么优势?

  • Vuex 核心:State(状态)、Getter(计算状态)、Mutation(同步修改)、Action(异步修改)、Module(模块化);
  • Pinia 核心:State(状态)、Getter(计算状态)、Action(同步/异步修改)(无 Mutation、Module);
  • Pinia 优势
    ① 简化 API(无需 Mutation,Action 支持同步/异步);
    ② 天然支持 TypeScript,类型提示更友好;
    ③ 无需嵌套模块化,通过多个 store 实现模块化;
    ④ 体积更小,性能更优;
    ⑤ 兼容 Vue2/Vue3。

4. VueRouter 的 hash 模式和 history 模式区别?history 模式部署需要后端做什么配置?

  • 核心区别

    维度 hash 模式 history 模式
    URL 表现 带 #(如 /#/home) 无 #(如 /home)
    底层原理 监听 hashchange 事件 基于 H5 History API
    刷新页面 不会发送到服务器 会发送到服务器
    兼容性 支持 IE8+ 支持 IE10+
  • history 模式部署要求
    后端需配置「兜底路由」(如 Nginx),将所有非静态资源的请求转发到 index.html,避免刷新 404:

    nginx 复制代码
    location / {
      try_files $uri $uri/ /index.html;
    }

二、进阶加分(大厂/中厂重点,外包偶尔问)

1. Vue 原理进阶

1. Vue3 中 Proxy 为什么能解决 Vue2 响应式的缺陷?

  • Vue2 缺陷:
    ① 数组:Object.defineProperty 无法监听 arr[0] = 1arr.length = 0 等操作,需重写数组方法(push/pop 等);
    ② 对象:无法监听新增/删除属性,需手动调用 Vue.set/Vue.delete
  • Proxy 优势:
    ① 代理整个对象,而非单个属性,天然支持数组下标/长度修改;
    ② 能捕获 deleteProperty(删除属性)、set(新增/修改属性)等操作,无需手动干预;
    ③ 支持更多操作(如 has 监听 in 操作、ownKeys 监听 Object.keys)。

2. Vue 的虚拟 DOM 和 Diff 算法原理?Vue3 的 Diff 做了哪些优化?

  • 虚拟 DOM :用 JS 对象描述 DOM 结构(如 { tag: 'div', props: { class: 'box' }, children: [] }),避免直接操作真实 DOM,通过对比新旧虚拟 DOM 差异,只更新需要变化的 DOM。
  • Diff 算法核心
    ① 同级比较(不跨层级);
    ② 先判断 key 是否相同,相同则复用节点,仅更新 props;
    ③ 不同则销毁旧节点,创建新节点;
  • Vue3 Diff 优化
    静态提升 :静态节点(如 <div>文本</div>)只创建一次,复用;
    PatchFlags :标记动态节点(如仅 props 变化、仅文本变化),Diff 时只遍历标记的节点;
    最长递增子序列:优化列表 Diff,减少 DOM 移动次数(如列表排序时,仅移动必要节点)。

3. 组件的异步组件怎么实现?Suspense 组件的使用场景?

  • 异步组件实现(Vue3)

    js 复制代码
    <script setup>
    // 基础用法
    const AsyncComponent = defineAsyncComponent(() => import('@/components/Async.vue'))
    // 高级用法(加载中/错误处理)
    const AsyncComponent = defineAsyncComponent({
      loader: () => import('@/components/Async.vue'),
      loadingComponent: () => import('@/components/Loading.vue'), // 加载中组件
      errorComponent: () => import('@/components/Error.vue'), // 加载失败组件
      delay: 200, // 延迟显示加载组件(避免闪屏)
      timeout: 3000 // 超时时间
    })
    </script>
  • Suspense 场景 :包裹异步组件,统一处理「加载中」和「加载完成」状态,无需每个异步组件单独写 loading 逻辑:

    html 复制代码
    <template>
      <Suspense>
        <template #default>
          <AsyncComponent />
        </template>
        <template #fallback>
          <div>加载中...</div>
        </template>
      </Suspense>
    </template>

4. Vue3 的 Composition API 对比 Options API 的优势?解决了什么问题?

  • Options API 问题
    ① 逻辑分散(数据在 data、方法在 methods、监听在 watch),复杂组件难以维护;
    ② 逻辑复用困难(mixin 命名冲突、来源不清晰);
  • Composition API 优势
    ① 逻辑聚合(相关逻辑写在 setup 中,如「表单验证」的 data + methods + watch 放在一起);
    ② 逻辑复用灵活(自定义 hook,如 useForm、useList,无命名冲突);
    ③ 更好的 TypeScript 支持;
    ④ 按需引入,体积更小。

5. Vue 的 mixin 有什么优缺点?为什么推荐用组合式 API 替代 mixin?

  • mixin 优点:实现逻辑复用(如多个组件的表单验证逻辑);
  • mixin 缺点
    ① 命名冲突(mixin 和组件的 data/methods 重名时,组件覆盖 mixin);
    ② 来源不清晰(组件中无法直观看到 mixin 的逻辑);
    ③ 逻辑耦合(mixin 之间可能相互依赖);
  • 组合式 API 替代原因:自定义 hook 可明确传入/传出参数,逻辑边界清晰,无命名冲突,可读性更高。

6. 谈谈 Vue 的依赖收集和触发更新过程?

  • 依赖收集
    ① 组件渲染时,执行 render 函数,访问响应式数据,触发 get 拦截;
    get 拦截器中将当前组件的「副作用函数(更新函数)」收集到「依赖集合」中;
  • 触发更新
    ① 响应式数据修改,触发 set 拦截;
    set 拦截器中遍历「依赖集合」,执行所有副作用函数,更新组件。
  • 核心:每个响应式数据对应一个依赖集合,收集使用该数据的组件,数据变化时通知组件更新。

7. nextTick 的原理和使用场景?

  • 原理 :Vue 异步更新 DOM(数据变化后,不会立即更新 DOM,而是将更新任务放入队列),nextTick 用于在 DOM 更新完成后执行回调;

  • 实现:优先使用微任务(Promise.then),降级使用宏任务(setTimeout);

  • 使用场景
    ① 数据修改后,立即操作更新后的 DOM(如获取输入框焦点、计算 DOM 尺寸);
    ② 批量修改数据,避免多次 DOM 更新(如循环修改数据,最后调用 nextTick 统一处理)。

    js 复制代码
    <script setup>
    const count = ref(0)
    const handleClick = () => {
      count.value = 1
      console.log(document.querySelector('.count').innerText) // 旧值 0
      nextTick(() => {
        console.log(document.querySelector('.count').innerText) // 新值 1
      })
    }
    </script>

2. 工程化 & 性能优化

1. Vue 项目中如何做性能优化?(至少5点,结合渲染、打包、运行时)

  • 渲染优化
    ① 减少响应式数据(非响应式数据用 markRaw 标记,避免 Proxy 代理);
    ② 避免不必要的渲染(用 computed 缓存、v-once 标记静态节点);
    ③ 列表优化(v-for 加 key、虚拟滚动处理长列表);
  • 打包优化
    ① 路由懒加载、组件懒加载;
    ② 按需引入第三方库(如 Element Plus 按需导入);
    ③ 压缩代码(Terser 压缩 JS、css-minimizer 压缩 CSS);
    ④ 分包(splitChunks 拆分公共代码、CDN 引入大库如 Vue/Element Plus);
  • 运行时优化
    ① 防抖节流(如搜索框输入、按钮点击);
    ② 图片优化(懒加载、webp 格式、雪碧图);
    ③ 减少 DOM 操作(用虚拟 DOM、批量修改数据);
    ④ 缓存接口数据(localStorage/Pinia,避免重复请求)。

2. Webpack 打包 Vue 项目时,如何做体积优化?

  • Tree Shaking :开启 mode: production(默认开启),删除未使用的代码;

  • 按需引入:第三方库(如 Element Plus、VueUse)按需导入,而非全量引入;

  • 代码分割splitChunks 拆分公共代码、第三方库:

    js 复制代码
    // webpack.config.js
    module.exports = {
      optimization: {
        splitChunks: {
          chunks: 'all', // 拆分所有 chunk
          cacheGroups: {
            vendor: { // 拆分第三方库
              test: /[\\/]node_modules[\\/]/,
              name: 'vendors',
              chunks: 'all'
            }
          }
        }
      }
    }
  • CDN 引入:将 Vue、VueRouter、Pinia 等大库通过 CDN 引入,排除在打包之外;

  • 压缩资源 :使用 terser-webpack-plugin 压缩 JS,css-minimizer-webpack-plugin 压缩 CSS;

  • 分析体积 :用 webpack-bundle-analyzer 分析打包体积,定位大文件。

3. Vite 为什么比 Webpack 快?Vite 构建 Vue 项目的流程?

  • Vite 快的原因
    开发阶段 :无需打包,基于 ES Module 直接按需编译(浏览器请求时才编译文件);
    预构建 :用 esbuild(Go 编写)预构建第三方库(将 CommonJS 转为 ESM),比 Webpack 的 babel 快 10-100 倍;
    HMR 优化 :仅更新修改的模块,无需重新打包;
    生产构建:用 Rollup 打包(比 Webpack 更适合库打包,体积更小);
  • Vite 构建流程
    ① 开发阶段:启动开发服务器 → 预构建第三方库 → 浏览器请求文件 → esbuild 编译文件 → 返回给浏览器;
    ② 生产阶段:执行 vite build → 预构建 → Rollup 打包 → 生成静态资源。

4. 大型 Vue 项目中,如何做状态管理的拆分?

  • 按业务模块拆分 store:如用户模块(userStore)、商品模块(goodsStore)、订单模块(orderStore);
  • 按功能拆分
    • 全局状态(如用户信息、权限):放在根 store;
    • 页面级状态:放在页面组件的 setup 中(无需放入全局 store);
    • 跨页面状态:放在对应业务 store;
  • 命名空间/前缀:Pinia 无需嵌套,直接通过 store 名称区分(如 useUserStore、useGoodsStore);
  • 持久化:核心状态(如用户 token)通过 Pinia 插件(pinia-plugin-persistedstate)持久化到 localStorage;
  • 避免过度封装:简单状态(如单个页面的表单)用组件内的 ref/reactive 即可,无需放入全局 store。

5. Vue 项目中如何实现权限控制?(路由权限、按钮权限)

  • 路由权限
    ① 路由配置中添加 meta: { roles: ['admin', 'editor'] }
    ② 全局路由守卫 beforeEach 中判断用户角色,无权限则跳转到 403/登录页:

    js 复制代码
    router.beforeEach((to, from, next) => {
      const userRoles = localStorage.getItem('roles')?.split(',') || []
      if (to.meta.roles && !to.meta.roles.some(role => userRoles.includes(role))) {
        next('/403')
      } else {
        next()
      }
    })
  • 按钮权限
    ① 自定义指令 v-permission

    js 复制代码
    app.directive('permission', {
      mounted(el, binding) {
        const userRoles = localStorage.getItem('roles')?.split(',') || []
        if (!userRoles.includes(binding.value)) {
          el.style.display = 'none'
        }
      }
    })

    ② 使用:<button v-permission="'admin'">删除</button>

  • 接口权限:请求拦截器中携带 token,后端验证权限,前端处理 403 响应。

6. 虚拟滚动在 Vue 中的实现方式?

  • 场景:长列表(如 10000 条数据),仅渲染可视区域的 DOM,提升性能;

  • 实现方式
    ① 第三方库:vue-virtual-scroller(Vue2)、@vueuse/coreuseVirtualList(Vue3);
    ② 手写核心逻辑(Vue3):

    html 复制代码
    <template>
      <div class="list-container" ref="container" @scroll="handleScroll">
        <div class="list-content" :style="{ transform: `translateY(${top}px)` }">
          <div v-for="item in visibleList" :key="item.id" class="list-item">
            {{ item.name }}
          </div>
        </div>
      </div>
    </template>
    <script setup>
    import { ref, computed, onMounted } from 'vue'
    const list = ref([]) // 所有数据
    const container = ref(null)
    const top = ref(0)
    const itemHeight = 50 // 每条数据高度
    const visibleCount = ref(20) // 可视区域显示条数
    // 可视区域数据
    const visibleList = computed(() => {
      const start = Math.floor(container.value.scrollTop / itemHeight)
      const end = start + visibleCount.value
      return list.value.slice(start, end)
    })
    // 滚动事件
    const handleScroll = () => {
      const start = Math.floor(container.value.scrollTop / itemHeight)
      top.value = start * itemHeight
    }
    // 模拟数据
    onMounted(() => {
      list.value = Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` }))
    })
    </script>
    <style>
    .list-container { height: 500px; overflow: auto; }
    .list-content { height: 10000 * 50px; position: relative; }
    .list-item { height: 50px; line-height: 50px; }
    </style>

三、架构拔高(大厂必考,中厂选考,外包基本不考)

1. 如何设计一个可复用的 Vue 组件库?

  • 封装原则

    • 单一职责(一个组件只做一件事);
    • 可配置化(通过 props 暴露配置项,如按钮的 size/type/disabled);
    • 低耦合(组件内部逻辑独立,不依赖外部状态);
  • 目录结构

    复制代码
    packages/
    ├── button/        // 按钮组件
    │   ├── src/
    │   │   ├── button.vue
    │   │   └── index.ts
    │   └── package.json
    ├── input/         // 输入框组件
    ├── utils/         // 工具函数
    └── index.ts       // 入口文件(导出所有组件)
  • 按需引入 :用 unplugin-vue-components 实现自动按需导入;

  • 类型提示:用 TypeScript 编写,定义 props 类型,提供 d.ts 文件;

  • 样式处理:支持主题定制(如 CSS 变量、scss 变量),样式隔离(CSS Modules);

  • 文档&测试:用 VitePress 写文档,Vitest 写单元测试;

  • 发布:打包为 ES Module/CJS/UMD 格式,发布到 npm。

2. Vue 项目中如何实现微前端?(qiankun 接入流程、样式隔离、通信)

  • 核心库:qiankun(基于 single-spa);

  • 接入流程
    ① 主应用配置:

    js 复制代码
    // 主应用 main.js
    import { registerMicroApps, start } from 'qiankun'
    // 注册子应用
    registerMicroApps([
      {
        name: 'vue-app', // 子应用名称
        entry: '//localhost:8081', // 子应用地址
        container: '#micro-container', // 挂载容器
        activeRule: '/vue-app', // 激活路由
      }
    ])
    // 启动 qiankun
    start()

    ② 子应用配置:

    js 复制代码
    // 子应用 main.js
    let instance = null
    export async function bootstrap() {}
    export async function mount(props) {
      instance = createApp(App)
      instance.use(router).mount(props.container.querySelector('#app'))
    }
    export async function unmount() {
      instance.unmount()
    }
  • 样式隔离
    ① qiankun 自带样式隔离(沙箱);
    ② 子应用样式加前缀(如 vue-app-),避免冲突;

  • 通信
    ① 主应用通过 props 传递数据给子应用;
    ② 全局 EventBus(mitt);
    ③ 共享 store(如 Pinia)。

3. Vue 服务端渲染(SSR)的原理?Nuxt.js 的核心流程?SSR 对比 CSR 的优劣势?

  • SSR 原理
    ① 服务端:接收请求 → 创建 Vue 实例 → 渲染虚拟 DOM 为 HTML → 返回 HTML 给客户端;
    ② 客户端:接收 HTML(首屏内容)→ 激活(hydrate)Vue 实例 → 变为可交互的单页应用;

  • Nuxt.js 核心流程
    ① 开发阶段:基于 Vue 构建,自动配置 SSR;
    ② 构建阶段:生成服务端 bundle 和客户端 bundle;
    ③ 运行阶段:请求 → 中间件 → 页面组件 → 渲染 HTML → 返回客户端;

  • SSR vs CSR

    维度 SSR CSR
    首屏速度 快(服务端返回完整 HTML) 慢(需下载 JS 后渲染)
    SEO 友好(搜索引擎可抓取内容) 差(仅抓取空 HTML)
    服务器压力 大(需渲染 HTML) 小(仅返回静态资源)
    开发复杂度 高(需处理服务端/客户端差异)

4. 大型 Vue 项目的目录结构如何设计?

  • 核心原则:分层、模块化、业务与基础分离;

    src/
    ├── api/ // 接口请求(按模块拆分)
    │ ├── user/ // 用户模块接口
    │ ├── goods/ // 商品模块接口
    │ └── index.ts // 接口入口
    ├── assets/ // 静态资源(图片、样式)
    ├── components/ // 组件
    │ ├── base/ // 基础组件(按钮、输入框)
    │ ├── business/ // 业务组件(订单列表、商品卡片)
    │ └── layout/ // 布局组件(头部、侧边栏)
    ├── composables/ // 自定义 hook(useForm、useList)
    ├── directives/ // 自定义指令
    ├── router/ // 路由(按模块拆分)
    ├── store/ // 状态管理(按模块拆分)
    ├── styles/ // 全局样式(变量、重置样式)
    ├── utils/ // 工具函数(请求、格式化、常量)
    ├── views/ // 页面组件(按业务模块拆分)
    │ ├── user/ // 用户页面
    │ ├── goods/ // 商品页面
    │ └── order/ // 订单页面
    ├── App.vue
    └── main.ts

5. Vue3 + TypeScript 开发时,如何做类型约束?

  • Props 类型约束

    js 复制代码
    <script setup lang="ts">
    interface Props {
      name: string
      age?: number
      list: Array<{ id: number; text: string }>
    }
    const props = defineProps<Props>()
    // 带默认值
    const props = withDefaults(defineProps<Props>(), {
      age: 18,
      list: () => []
    })
    </script>
  • 响应式数据类型约束

    ts 复制代码
    import { ref, reactive } from 'vue'
    interface User {
      name: string
      age: number
    }
    const user = ref<User>({ name: '张三', age: 18 })
    const list = reactive<Array<User>>([])
  • 函数返回值约束

    ts 复制代码
    const getUser = (): Promise<User> => {
      return axios.get('/api/user')
    }
  • 全局类型 :在 src/types/index.ts 中定义全局类型,在 tsconfig.json 中配置 typeRoots

6. 如何做 Vue 项目的错误监控和性能监控?

  • 错误监控
    ① 全局错误捕获:

    js 复制代码
    // main.js
    app.config.errorHandler = (err, instance, info) => {
      // 上报错误(如接口、错误信息、组件信息)
      axios.post('/api/error', {
        message: err.message,
        stack: err.stack,
        info, // 错误位置(如 render、watch)
        url: window.location.href
      })
    }

    ② 接口错误捕获:请求拦截器中处理 4xx/5xx 响应;
    ③ 白屏监控:定时检查 #app 内是否有内容,无则上报;

  • 性能监控
    ① 使用 Performance API 监控首屏时间、LCP(最大内容绘制)、FCP(首次内容绘制):

    js 复制代码
    const observer = new PerformanceObserver((list) => {
      const lcp = list.getEntries()[0]
      // 上报 LCP 时间
      axios.post('/api/performance', { lcp: lcp.startTime })
    })
    observer.observe({ type: 'largest-contentful-paint', buffered: true })

    ② 监控组件渲染时间:在 setup 中记录开始/结束时间;
    ③ 第三方工具:接入 Sentry、Fundebug 等成熟监控平台。

7. Vue 项目跨端方案(uni-app/Taro)的选型和踩坑点?

  • 选型

    方案 优势 劣势
    uni-app 生态完善,支持多端(微信/支付宝小程序、H5、App) 部分 API 与原生小程序有差异
    Taro 更好的 TypeScript 支持,React/Vue 双框架 生态不如 uni-app 完善
  • 踩坑点
    ① 样式兼容:小程序不支持 :hoverposition: fixed 有坑;
    ② API 兼容:不同端的 API 差异(如微信小程序的 wx.request vs H5 的 fetch);
    ③ 路由差异:小程序路由与 H5 路由不同,需适配;
    ④ 性能问题:小程序包体积限制(2M),需分包;
    ⑤ 生命周期:跨端生命周期有差异(如小程序的 onShow/onHide)。

四、手写代码(所有厂都可能现场考,优先掌握)

1. 手写 Vue3 自定义 hook(如:useDebounce、useLocalStorage)

  • useDebounce(防抖 hook)

    ts 复制代码
    // composables/useDebounce.ts
    import { ref, watch, unref } from 'vue'
    export function useDebounce<T>(value: T, delay = 300) {
      const debouncedValue = ref<T>(unref(value))
      let timer: number
      watch(
        () => unref(value),
        (val) => {
          clearTimeout(timer)
          timer = setTimeout(() => {
            debouncedValue.value = val
          }, delay)
        },
        { immediate: false }
      )
      return debouncedValue
    }
    // 使用
    <script setup>
    import { useDebounce } from '@/composables/useDebounce'
    const inputValue = ref('')
    const debouncedValue = useDebounce(inputValue, 500)
    watch(debouncedValue, (val) => {
      console.log('搜索:', val) // 500ms 内未输入则执行
    })
    </script>
  • useLocalStorage(本地存储 hook)

    ts 复制代码
    // composables/useLocalStorage.ts
    import { ref, watch } from 'vue'
    export function useLocalStorage<T>(key: string, defaultValue: T) {
      // 初始化
      const value = ref<T>(() => {
        const stored = localStorage.getItem(key)
        return stored ? JSON.parse(stored) : defaultValue
      }())
      // 监听变化,同步到 localStorage
      watch(
        value,
        (val) => {
          localStorage.setItem(key, JSON.stringify(val))
        },
        { deep: true }
      )
      // 删除
      const remove = () => {
        localStorage.removeItem(key)
        value.value = defaultValue
      }
      return { value, remove }
    }
    // 使用
    <script setup>
    import { useLocalStorage } from '@/composables/useLocalStorage'
    const { value: userInfo, remove: removeUserInfo } = useLocalStorage('userInfo', { name: '', age: 0 })
    // 修改
    userInfo.value = { name: '张三', age: 18 }
    // 删除
    removeUserInfo()
    </script>

2. 手写简易版 Vue 响应式(Vue3 Proxy 版本)

ts 复制代码
// 依赖收集容器
const targetMap = new WeakMap()
let activeEffect: Function | null = null
// 收集依赖
function track(target: object, key: string | symbol) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  dep.add(activeEffect)
}
// 触发更新
function trigger(target: object, key: string | symbol) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}
// 响应式核心
function reactive<T extends object>(target: T): T {
  return new Proxy(target, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      track(target, key) // 收集依赖
      return res
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver)
      trigger(target, key) // 触发更新
      return res
    },
    deleteProperty(target, key) {
      const res = Reflect.deleteProperty(target, key)
      trigger(target, key) // 触发更新
      return res
    }
  })
}
// 副作用函数
function effect(fn: Function) {
  activeEffect = fn
  fn() // 执行一次,触发 get 收集依赖
  activeEffect = null
}
// 测试
const state = reactive({ count: 0 })
effect(() => {
  console.log('count:', state.count) // 初始化执行,count: 0
})
state.count = 1 // 触发更新,count: 1
delete state.count // 触发更新,count: undefined

3. 手写一个通用的 Vue 分页组件(支持自定义配置、事件回调)

html 复制代码
<template>
  <div class="pagination" v-if="total > 0">
    <button 
      class="page-btn" 
      :disabled="currentPage === 1"
      @click="handlePageChange(1)"
    >
      首页
    </button>
    <button 
      class="page-btn" 
      :disabled="currentPage === 1"
      @click="handlePageChange(currentPage - 1)"
    >
      上一页
    </button>
    <button 
      v-for="page in pageList" 
      :key="page"
      class="page-btn"
      :class="{ active: page === currentPage }"
      @click="handlePageChange(page)"
    >
      {{ page }}
    </button>
    <button 
      class="page-btn" 
      :disabled="currentPage === totalPage"
      @click="handlePageChange(currentPage + 1)"
    >
      下一页
    </button>
    <button 
      class="page-btn" 
      :disabled="currentPage === totalPage"
      @click="handlePageChange(totalPage)"
    >
      尾页
    </button>
    <span class="page-info">
      共 {{ total }} 条,每页 {{ pageSize }} 条,第 {{ currentPage }}/{{ totalPage }} 页
    </span>
  </div>
</template>
<script setup lang="ts">
import { computed, defineProps, defineEmits } from 'vue'
// Props
const props = defineProps<{
  currentPage: number // 当前页
  pageSize: number // 每页条数
  total: number // 总条数
  showSize?: number // 显示的页码数(默认5)
}>()
// Emits
const emits = defineEmits<{
  (e: 'page-change', page: number): void
}>()
// 计算总页数
const totalPage = computed(() => Math.ceil(props.total / props.pageSize))
// 计算显示的页码列表
const pageList = computed(() => {
  const showSize = props.showSize || 5
  const total = totalPage.value
  const current = props.currentPage
  const list: number[] = []
  // 边界处理
  if (total <= showSize) {
    for (let i = 1; i <= total; i++) list.push(i)
  } else {
    const half = Math.floor(showSize / 2)
    let start = current - half
    let end = current + half
    if (start < 1) {
      start = 1
      end = showSize
    }
    if (end > total) {
      end = total
      start = total - showSize + 1
    }
    for (let i = start; i <= end; i++) list.push(i)
  }
  return list
})
// 页码变化
const handlePageChange = (page: number) => {
  if (page < 1 || page > totalPage.value || page === props.currentPage) return
  emits('page-change', page)
}
</script>
<style scoped>
.pagination {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 20px 0;
}
.page-btn {
  padding: 4px 12px;
  border: 1px solid #e5e7eb;
  border-radius: 4px;
  background: #fff;
  cursor: pointer;
}
.page-btn:disabled {
  cursor: not-allowed;
  color: #9ca3af;
  background: #f9fafb;
}
.page-btn.active {
  background: #3b82f6;
  color: #fff;
  border-color: #3b82f6;
}
.page-info {
  margin-left: 12px;
  color: #6b7280;
}
</style>

4. 实现 Vue 中的防抖/节流指令(v-debounce/v-throttle)

  • v-debounce(防抖指令)

    ts 复制代码
    // directives/debounce.ts
    import type { Directive } from 'vue'
    interface ElType extends HTMLElement {
      __handleClick__: Function
    }
    const debounce: Directive = {
      mounted(el: ElType, binding) {
        const { value } = binding
        if (typeof value !== 'function') {
          throw new Error('v-debounce 必须传入函数')
        }
        // 防抖参数:delay(默认300ms)
        const delay = binding.arg ? Number(binding.arg) : 300
        let timer: number
        el.__handleClick__ = function () {
          clearTimeout(timer)
          timer = setTimeout(() => {
            value()
          }, delay)
        }
        el.addEventListener('click', el.__handleClick__)
      },
      unmounted(el: ElType) {
        el.removeEventListener('click', el.__handleClick__)
      }
    }
    export default debounce
  • v-throttle(节流指令)

    ts 复制代码
    // directives/throttle.ts
    import type { Directive } from 'vue'
    interface ElType extends HTMLElement {
      __handleClick__: Function
      __isClick__: boolean
    }
    const throttle: Directive = {
      mounted(el: ElType, binding) {
        const { value } = binding
        if (typeof value !== 'function') {
          throw new Error('v-throttle 必须传入函数')
        }
        // 节流参数:interval(默认1000ms)
        const interval = binding.arg ? Number(binding.arg) : 1000
        el.__isClick__ = true
        el.__handleClick__ = function () {
          if (!el.__isClick__) return
          el.__isClick__ = false
          value()
          setTimeout(() => {
            el.__isClick__ = true
          }, interval)
        }
        el.addEventListener('click', el.__handleClick__)
      },
      unmounted(el: ElType) {
        el.removeEventListener('click', el.__handleClick__)
      }
    }
    export default throttle
  • 注册和使用

    js 复制代码
    // main.ts
    import { createApp } from 'vue'
    import debounce from './directives/debounce'
    import throttle from './directives/throttle'
    const app = createApp(App)
    app.directive('debounce', debounce)
    app.directive('throttle', throttle)
    html 复制代码
    <template>
      <button v-debounce:500="handleSearch">防抖搜索</button>
      <button v-throttle:1000="handleSubmit">节流提交</button>
    </template>

5. 手写 Pinia 持久化插件(基于 localStorage)

ts 复制代码
// plugins/piniaPersist.ts
import type { PiniaPluginContext } from 'pinia'
// 持久化插件
export function piniaPersistPlugin(context: PiniaPluginContext) {
  const { store } = context
  // 从 localStorage 加载数据
  const persistKey = store.$id + '_persist'
  const storedState = localStorage.getItem(persistKey)
  if (storedState) {
    store.$patch(JSON.parse(storedState))
  }
  // 监听 state 变化,同步到 localStorage
  store.$subscribe((mutation, state) => {
    localStorage.setItem(persistKey, JSON.stringify(state))
  }, { deep: true })
  // 提供删除方法
  store.$resetPersist = () => {
    localStorage.removeItem(persistKey)
    store.$reset()
  }
}
// 使用
// store/index.ts
import { createPinia } from 'pinia'
import { piniaPersistPlugin } from '@/plugins/piniaPersist'
const pinia = createPinia()
pinia.use(piniaPersistPlugin)
export default pinia

五、场景题(大厂/中厂重点,考察落地能力)

1. 场景1:Vue 项目首屏加载慢,你如何定位问题并优化?

  • 定位问题
    ① 网络层面:Chrome DevTools → Network 面板,查看资源加载时间(如 JS/CSS/图片);
    ② 打包层面:webpack-bundle-analyzer/vite-bundle-analyzer 分析打包体积,定位大文件;
    ③ 渲染层面:Chrome DevTools → Performance 面板,录制加载过程,查看瓶颈(如 JS 执行时间、DOM 渲染时间);
  • 优化方案
    ① 网络优化:
    • 路由/组件懒加载;
    • CDN 引入大库(Vue、Element Plus);
    • 开启 Gzip/Brotli 压缩(后端配置);
    • 静态资源缓存(强缓存/协商缓存);
      ② 打包优化:
    • 按需引入第三方库;
    • 拆分公共代码(splitChunks);
    • 压缩代码/图片;
      ③ 渲染优化:
    • 首屏骨架屏;
    • 预加载关键资源(<link rel="preload">);
    • 减少首屏渲染的组件数量;
      ④ 接口优化:
    • 接口合并(减少请求数);
    • 接口缓存(localStorage 缓存非实时数据);
    • 服务端渲染/静态生成(SSR/SSG)。

2. 场景2:多人协作开发 Vue 项目,如何保证代码规范和质量?

  • 代码规范
    • 配置 ESLint + Prettier(统一代码风格,如缩进、分号、引号);
    • 配置 eslint-plugin-vue(Vue 代码规范,如组件命名、props 定义);
    • 配置 TypeScript(类型约束,减少类型错误);
  • 提交规范
    • husky + lint-staged(提交前执行 ESLint 检查,不通过则禁止提交);
    • commitlint(规范 commit 信息,如 feat: 新增功能、fix: 修复 bug);
  • 代码评审
    • 采用 Git Flow 工作流(master/dev/feature 分支);
    • 合并代码前需提交 MR/PR,至少 1 人评审通过;
  • 自动化测试
    • 单元测试(Vitest/Jest)测试核心组件/工具函数;
    • E2E 测试(Cypress/Playwright)测试核心业务流程;
  • 文档规范
    • 组件文档(Storybook/VitePress);
    • 接口文档(Swagger/Postman);
    • 项目文档(README 说明环境搭建、启动命令)。

3. 场景3:Vue 项目中遇到内存泄漏,你如何排查和解决?

  • 常见内存泄漏场景
    ① 未清除的定时器/计时器(setTimeout/setInterval);
    ② 未移除的事件监听(addEventListener);
    ③ 未取消的订阅(如 Pinia 订阅、EventBus);
    ④ 闭包引用(如组件销毁后,闭包仍引用组件数据);
    ⑤ 大型数据未释放(如长列表数据未清空);

  • 排查方法
    ① Chrome DevTools → Memory 面板,录制堆快照,对比组件销毁前后的内存变化;
    ② 查找 Detached DOM(分离的 DOM 节点),定位未释放的 DOM 引用;

  • 解决方法
    ① 定时器:组件卸载时(onUnmounted)清除:

    vue 复制代码
    <script setup>
    import { onUnmounted } from 'vue'
    const timer = setInterval(() => {}, 1000)
    onUnmounted(() => {
      clearInterval(timer)
    })
    </script>

    ② 事件监听:组件卸载时移除:

    js 复制代码
    <script setup>
    import { onMounted, onUnmounted } from 'vue'
    const handleResize = () => {}
    onMounted(() => {
      window.addEventListener('resize', handleResize)
    })
    onUnmounted(() => {
      window.removeEventListener('resize', handleResize)
    })
    </script>

    ③ 订阅/总线:组件卸载时取消:

    js 复制代码
    <script setup>
    import { onUnmounted } from 'vue'
    import { useEventBus } from '@vueuse/core'
    const bus = useEventBus('test')
    const unsubscribe = bus.on('test', () => {})
    onUnmounted(() => {
      unsubscribe()
    })
    </script>

    ④ 大型数据:组件卸载时清空:

    js 复制代码
    <script setup>
    import { ref, onUnmounted } from 'vue'
    const list = ref([])
    onUnmounted(() => {
      list.value = [] // 清空数据
    })
    </script>

4. 场景4:移动端 Vue 项目适配方案?

  • 核心方案:vw/vh(推荐) + postcss-px-to-viewport;

  • 实现步骤
    ① 安装依赖:

    bash 复制代码
    npm install postcss-px-to-viewport -D

    ② 配置 postcss.config.js:

    js 复制代码
    module.exports = {
      plugins: {
        'postcss-px-to-viewport': {
          viewportWidth: 375, // 设计稿宽度(如 375px)
          viewportHeight: 667, // 设计稿高度
          unitPrecision: 5, // 精度
          viewportUnit: 'vw', // 转换单位
          selectorBlackList: ['ignore'], // 忽略的选择器
          minPixelValue: 1, // 最小转换像素
          mediaQuery: false // 不转换媒体查询中的 px
        }
      }
    }

    ③ 特殊适配:

    • 字体:使用 rem(结合 rootFontSize),避免 vw 导致字体过大/过小;
    • 横屏适配:监听 orientationchange 事件,调整样式;
    • 兼容低版本浏览器:引入 viewport-units-buggyfill
  • 备选方案
    ① rem:设置根元素 font-size(如 1rem = 100px),结合 postcss-px-to-rem;
    ② flex 布局:弹性布局适配不同屏幕;

  • 注意事项
    ① 设计稿标注为 px,自动转为 vw,无需手动计算;
    ② 固定尺寸(如 1px 边框)需忽略转换;
    ③ 测试不同设备(如 iPhone 6/7/8/11/12),调整适配参数。

总结

  1. 基础核心:Vue2/Vue3 响应式原理、生命周期、组件通信、路由/状态管理基础是所有面试的必过项,需熟练掌握;
  2. 进阶重点:虚拟 DOM/Diff 算法、性能优化(打包/渲染/运行时)、工程化(Webpack/Vite)是区分中高级前端的关键;
  3. 架构能力:组件库设计、微前端、SSR、大型项目目录/状态拆分是大厂面试的核心,需结合实战理解落地逻辑;
  4. 手写代码:防抖/节流、响应式、自定义 hook、分页组件是高频考点,需手写熟练,理解核心逻辑而非死记;
  5. 场景题:首屏优化、内存泄漏、多人协作规范是考察落地能力的关键,需形成「定位问题→分析原因→给出方案」的答题思路。
相关推荐
To_OC1 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC1 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic2 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy2 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志2 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble3 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长4 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯4 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜5 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司5 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能