vue 前端开发面试记录

144 阅读7分钟

近期也是迫不得已参加了一波面试,从呆了四年的舒适圈跳出来还是非常地不习惯,好在平时偶尔会心血来潮地学习一阵子,所以不至于大脑一片空白。在经历了头几次面试的手足无措后,终于拿到了还算满意的 offer,以下是这波面试中的一些问题。

vue3 和 vue2 的区别

所有公司都会问,基本上自我介绍完第一个问题就是这个。大部分 vue 开发者或多或少都能说出几点,但我认为最关键的就是条理清晰且完整地讲述出来,经过几次面试总结反思后,我想起了 《vue.js 设计与实现》这本书,所以就有了如下回答。

  1. 响应式系统:vue2 使用 Object.defineProperty 和发布订阅模式来实现响应式系统,而 vue3 是采用 proxy + reflect 来实现的。
  2. 渲染器:渲染器的主要功能是将虚拟节点转换为真是 dom 节点,vue3 新增了 Fragment 节点,这样在写组件文件的时候可以有多个根节点了。其次 vue3 的 diff 算法也进行了升级,性能得到了提升(vue2 双端 diff,vue3 通过预处理 + 构建最长递增子序列进行 diff)。
  3. 组件系统:vue 组件本质是一个带有一个 render 函数的 js 对象,vue3 中在这个 js 对象加入了 setup 上下文对象和 setup 函数,因此 vue3 在兼容 vue2 选项式写法的基础上支持了组合式 API,组合式 API 的出现使得开发者能够编写出更加优雅的代码。此外,还新增了 Teleport 组件,Suspense 组件等。
  4. 编译器:编译时会生成** Block Tree** 和带有静态标记的虚拟 DOM。
    • 静态提升 (Static Hoisting) :将静态的节点和属性提升到渲染函数之外,只在首次渲染时创建一次,后续复用,避免重复创建。
    • Patch Flags (补丁标志) :在创建动态节点时,会为其打上一个标记(如 1 代表文本动态),diff 时只需检查这些有标记的动态内容,无需再全树对比,性能大幅提升。
    • Tree Shaking 友好:很多 API(如 v-model<Transition>)可以按需引入,没用到的功能不会被打包到生产环境,减小了体积。

vue3 细节

在回答了 vue3 和 vue2 的区别后,面试官有极大的概率追问其中的一些细节,尤其是响应式系统。

vue3 响应式系统实现原理?

通过 proxy 创建一个代理对象,在 proxy 对象中使用 get 拦截属性读取操作,触发 track 函数收集依赖,使用 set 拦截属性设置操作。当在副作用函数中读取某个代理对象 target 上的某个字段 key 时,会自动构建一个依赖关系(target,key,currentEffect),并且该依赖关系保存在一个全局对象 bucket 上,数据结构为 WeakMap<target, Map<key, Set>>。当 track 函数执行时会根据 bucket 找到订阅了该 target.key 的所有副作用函数并执行。

vue3 nextTick 实现原理?

  1. 响应式数据发生变化触发 trigger 函数
  2. 订阅了该响应式数据的副作用函数加入到待执行队列 queue
  3. 通过 Promise.resolve().then 创建一个微任务用于执行 queue 中所有待执行副作用函数
  4. 在执行副作用函数的过程中遇到 nextTick,nextTick 中的回调函数被添加到另一个待执行队列 pendingPostFlushCbs
  5. Queue 队列中中所有副作用函数执行完毕,开始执行 pendingPostFlushCbs 中的所有 nextTick 中回调函数

vue2 响应式系统缺陷?

  1. 无法自动检测对象属性的添加和删除,需要通过 Vue.set,Vue.delete
  2. 对数组的监听需要重写数组的方法
  3. 初始化时需要递归遍历对象的所有属性并转为 getter / setter,有性能开销

vue3 基于 proxy 的响应式系统解决了上述 3 个缺陷,对于第一点,有的面试官还会追问为什么 vue3 响应式系统就不再需要 Vue.set 了?这时候一定要回答因为 vue3 是对整个对象进行代理,而 vue2 是监听对象上的 key。

说一下 vue3 响应式系统的副作用函数?

  1. 当发生副作用函数嵌套时,使用栈结构保存后再依次 pop 执行
  2. effect 函数支持调度机制,通过回调函数决定函数的执行时机(flushJob)
  3. 为了解决条件依赖,在副作用函数内部收集依赖之前会先将旧依赖清空

HTTP 缓存

浏览器事件循环

  1. 执行同步代码,清空调用栈
  2. 依次执行微任务,清空微任务队列
  3. 渲染页面
  4. 从宏任务队列中取出第一个宏任务,将其回调函数压入调用栈执行
  5. 重复执行

可能会顺带问些 Promise 相关的问题

CommonJs / ESM

  1. 使用方式上,require,exports,import,export
  2. Commonjs 是运行时加载,ESM 是编译时加载,所以支持树摇
  3. Commonjs 是同步加载,ESM 是异步加载

webpack / vite

由于我对 webpack 不怎么熟,如果面试官先问 webpack,我就大概先背一遍八股文,entry / output / loader / plugin / env 巴拉巴拉的,然后说由于公司前端项目都是使用 vite,所以对 vite 了解的比较深,这时候面试官就会让我聊聊 vite,那么就来到我的舒适区了。

  1. 基于 ESM + ESBuild 实现 no bundle 本地服务器,极大提升服务启动速度
  2. 使用 ESBuild 进行预构建(非 esmodule 转为 esmodule + 零散模块文件处理为一个文件降低请求量),使用 http 强缓存 + 本地文件缓存(node_modules/.vite)加快下一次服务启动速度
  3. 基于 rollup 插件系统的打包工具,插件是 vite 在编译打包过程中在特定时间段暴露出来的一些钩子,使得开发人员可以插入一些自定义逻辑。Build 阶段 + output 阶段,根据这两个阶段可以将插件分为两类,build 阶段的插件针对的是模块(单文件),output 阶段的插件针对的是 chunk(多个文件打包进一个文件了)
  4. 还可以聊聊 vite HMR 的实现

随后面试官就会问你有没有自己写过 vite 插件,所以一定要自己动手写一个或者记住同事手写的插件逻辑,能说出来在某个钩子中执行了什么逻辑即可

前端性能优化

  • 降低请求量:icon 内联 / 雪碧图 / router chunks / 异步组件 / 图片懒加载 / gzip 压缩
  • 使用缓存:HTTP 缓存 / 本地缓存
  • 加快请求速度:CDN /
  • 代码优化:为 script 标签加上 defer / async

WeakMap / Map

问起 WeakMap 要么是响应式系统中顺带问的,要么就是深拷贝时需要你主动说出来的,通过 WeakMap

处理对象互相引用。

  • WeapMap 只能使用对象作为 key,Map 可以使用任意类型作为 key
  • WeakMap 不阻止垃圾回收,Map 会组织垃圾回收
  • WeakMap 不能遍历,无法获取 size,无法 clear

JS Bridge

由于一直在做 web,真没有这方面的实战经验,第一次遇到这个问题只能说 js 和 native 互相调用,下面是问了 AI 后总结的。

让 web 页面具有调用原生的功能,也让原生代码可以控制 web 页面的内容和行为

Web 调用 Native 功能

  1. 访问设备 API
  2. 与原生 UI 交互
  3. 数据持久化
  4. 提升性能

Native 调用 Web 功能

  1. 传递数据
  2. 控制页面行为
  3. 动态功能新

底层原理:WebView

JS 调用 Native:

  1. 拦截 URL Scheme

原理:WebView 可以拦截 Web 页面内发起的所有请求,javascript 可以通过创建一个自定义格式的请求来让 native 端处理

  1. 注入 API

向网页 window 对象中注入一个全局对象或方法

Native 调用 JS

原生代码直接执行一串 JavaScript 字符串

简历中的项目细节

一定要花时间过几遍简历上的项目代码,没有参与的项目写到简历上就更要多看几遍了,确保简历上的项目问题都能流畅地回答出来。

场景题

  1. 如何避免线上出现 i18n 翻译键丢失问题?
  2. 让你设计一个微前端系统,如何让多个前端应用中的 js 变量和 css 类名不发生混淆?
  3. 让你实现一个作业批阅系统,你会如何设计?
  4. 灰度发布?