近期也是迫不得已参加了一波面试,从呆了四年的舒适圈跳出来还是非常地不习惯,好在平时偶尔会心血来潮地学习一阵子,所以不至于大脑一片空白。在经历了头几次面试的手足无措后,终于拿到了还算满意的 offer,以下是这波面试中的一些问题。
vue3 和 vue2 的区别
所有公司都会问,基本上自我介绍完第一个问题就是这个。大部分 vue 开发者或多或少都能说出几点,但我认为最关键的就是条理清晰且完整地讲述出来,经过几次面试总结反思后,我想起了 《vue.js 设计与实现》这本书,所以就有了如下回答。
- 响应式系统:vue2 使用 Object.defineProperty 和发布订阅模式来实现响应式系统,而 vue3 是采用 proxy + reflect 来实现的。
- 渲染器:渲染器的主要功能是将虚拟节点转换为真是 dom 节点,vue3 新增了 Fragment 节点,这样在写组件文件的时候可以有多个根节点了。其次 vue3 的 diff 算法也进行了升级,性能得到了提升(vue2 双端 diff,vue3 通过预处理 + 构建最长递增子序列进行 diff)。
- 组件系统:vue 组件本质是一个带有一个 render 函数的 js 对象,vue3 中在这个 js 对象加入了 setup 上下文对象和 setup 函数,因此 vue3 在兼容 vue2 选项式写法的基础上支持了组合式 API,组合式 API 的出现使得开发者能够编写出更加优雅的代码。此外,还新增了 Teleport 组件,Suspense 组件等。
- 编译器:编译时会生成** 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 实现原理?
- 响应式数据发生变化触发 trigger 函数
- 订阅了该响应式数据的副作用函数加入到待执行队列 queue
- 通过 Promise.resolve().then 创建一个微任务用于执行 queue 中所有待执行副作用函数
- 在执行副作用函数的过程中遇到 nextTick,nextTick 中的回调函数被添加到另一个待执行队列 pendingPostFlushCbs
- Queue 队列中中所有副作用函数执行完毕,开始执行 pendingPostFlushCbs 中的所有 nextTick 中回调函数
vue2 响应式系统缺陷?
- 无法自动检测对象属性的添加和删除,需要通过 Vue.set,Vue.delete
- 对数组的监听需要重写数组的方法
- 初始化时需要递归遍历对象的所有属性并转为 getter / setter,有性能开销
vue3 基于 proxy 的响应式系统解决了上述 3 个缺陷,对于第一点,有的面试官还会追问为什么 vue3 响应式系统就不再需要 Vue.set 了?这时候一定要回答因为 vue3 是对整个对象进行代理,而 vue2 是监听对象上的 key。
说一下 vue3 响应式系统的副作用函数?
- 当发生副作用函数嵌套时,使用栈结构保存后再依次 pop 执行
- effect 函数支持调度机制,通过回调函数决定函数的执行时机(flushJob)
- 为了解决条件依赖,在副作用函数内部收集依赖之前会先将旧依赖清空
HTTP 缓存
浏览器事件循环
- 执行同步代码,清空调用栈
- 依次执行微任务,清空微任务队列
- 渲染页面
- 从宏任务队列中取出第一个宏任务,将其回调函数压入调用栈执行
- 重复执行
可能会顺带问些 Promise 相关的问题
CommonJs / ESM
- 使用方式上,require,exports,import,export
- Commonjs 是运行时加载,ESM 是编译时加载,所以支持树摇
- Commonjs 是同步加载,ESM 是异步加载
webpack / vite
由于我对 webpack 不怎么熟,如果面试官先问 webpack,我就大概先背一遍八股文,entry / output / loader / plugin / env 巴拉巴拉的,然后说由于公司前端项目都是使用 vite,所以对 vite 了解的比较深,这时候面试官就会让我聊聊 vite,那么就来到我的舒适区了。
- 基于 ESM + ESBuild 实现 no bundle 本地服务器,极大提升服务启动速度
- 使用 ESBuild 进行预构建(非 esmodule 转为 esmodule + 零散模块文件处理为一个文件降低请求量),使用 http 强缓存 + 本地文件缓存(node_modules/.vite)加快下一次服务启动速度
- 基于 rollup 插件系统的打包工具,插件是 vite 在编译打包过程中在特定时间段暴露出来的一些钩子,使得开发人员可以插入一些自定义逻辑。Build 阶段 + output 阶段,根据这两个阶段可以将插件分为两类,build 阶段的插件针对的是模块(单文件),output 阶段的插件针对的是 chunk(多个文件打包进一个文件了)
- 还可以聊聊 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 功能
- 访问设备 API
- 与原生 UI 交互
- 数据持久化
- 提升性能
Native 调用 Web 功能
- 传递数据
- 控制页面行为
- 动态功能新
底层原理:WebView
JS 调用 Native:
- 拦截 URL Scheme
原理:WebView 可以拦截 Web 页面内发起的所有请求,javascript 可以通过创建一个自定义格式的请求来让 native 端处理
- 注入 API
向网页 window 对象中注入一个全局对象或方法
Native 调用 JS
原生代码直接执行一串 JavaScript 字符串
简历中的项目细节
一定要花时间过几遍简历上的项目代码,没有参与的项目写到简历上就更要多看几遍了,确保简历上的项目问题都能流畅地回答出来。
场景题
- 如何避免线上出现 i18n 翻译键丢失问题?
- 让你设计一个微前端系统,如何让多个前端应用中的 js 变量和 css 类名不发生混淆?
- 让你实现一个作业批阅系统,你会如何设计?
- 灰度发布?