前端面试题(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. 场景题:首屏优化、内存泄漏、多人协作规范是考察落地能力的关键,需形成「定位问题→分析原因→给出方案」的答题思路。
相关推荐
wuhen_n2 小时前
结构化Prompt——让AI说“人话”
前端·vue.js·ai编程
En^_^Joy2 小时前
JavaScript入门指南:从零到精通
开发语言·javascript
前端小趴菜052 小时前
vue3-signature实现电子签名
前端·javascript·vue.js
玉米Yvmi2 小时前
React自定义Hook实战指南:从入门到精通,让你的代码像乐高一样灵活
前端·react.js·面试
CharlieWang2 小时前
AI + Cloudflare = 你需要的全部
前端·敏捷开发·全栈
卤蛋fg62 小时前
vue表格vxe-table如何获取拖拽后的行序号
vue.js
董员外2 小时前
从零实现 AI 编程助手:LangChain.js + ReAct 循环实战
前端·javascript·后端
bluceli2 小时前
JavaScript BigInt:处理大数值的终极解决方案
前端·javascript
不懂代码的切图仔2 小时前
小程序web-view嵌入h5扫码 html5-qrcode库使用方法
前端·微信