一面
两道题:flatten & throttle
-
flat
function flat(arr, depth = 1) { return depth > 0 ? arr.reduce((pre, cur) => { return [...pre, ...(Array.isArray(cur) ? flat(cur, depth - 1) : [cur])]; }, []) : arr; }
写出了 depth = Infinity
的情况,但忽略了 depth 参数
-
throttle
function throttle(func, timer) { let lastDate = 0 return function(...args) { const currentDate = new Date() if(currentDate - lastDate >= timer) { func.apply(this, args) // 非箭头函数的 this 指向与函数的调用作用域有关 lastDate = currentDate } } }
面试中没有写出来,面试官提示了也没有写出来;虽然记得 throttle 是一段事件执行一次,但是不记得 throttle 的使用场景了,导致思考的角度是偏了的;throttle 主要是和 scroll, resize 等事件一起使用,确保回调函数隔一段时间执行一次,从而提升性能
了解 AI 吗,AI 背后的大模型?
说了写代码会运用 Copilot,平时也会用 DeepSeek 了解一个知识点的整体;AI 背后的东西说了不了解
发布订阅模式
面试过程中问了下 Vue 用到了这个嘛,面试官说是的,我说没怎么了解
发布订阅模式包含三个重要的角色:
发布者:负责发布事件并传递给事件总线,不关心谁订阅的的
订阅者:向事件总线订阅自己感兴趣的事件,并且提供回调函数
事件总线:维护事件类型与订阅者回调之间的依赖关系,在接受到发布者的通知后让订阅者函数执行
Vue 中也使用了发布订阅模式:
发布者:更新响应式变量的事件源
订阅者:使用响应式变量的函数
事件中心:targetMap + track + trigger(初次访问响应式变量时会利用 track 跟踪依赖,会维护响应式变量与订阅者函数的映射关系到 targetMap;发布者更新响应式变量时会通过 trigger 让对应的订阅者函数执行)
get 与 set 拦截器:提供了发布者与订阅者发布与订阅操作,连接 trigger 与 track
观察者模式:
主题:维护状态、观察者列表,并且提供注册、注销、状态变更后让观察者执行的方法
观察者:订阅主题变量,提供了主题变更后会执行的操作
Vue2 使用了观察者模式
代理模式
Vue3 使用 Proxy 对对象进行拦截操作,包括 get、set 等
事件循环
事件循环是浏览器主线程执行任务的一种机制,会按照 宏任务 -> 所有微任务 -> requestAnimationFrame 回调函数 -> UI 渲染 -> requestIdleCallback 回调(如果有空闲时间)顺序不停的执行任务
宏任务:script 标签的同步代码,setTimeout, setInterval, I/O 操作包括网络请求回调,事件操作回调
微任务:Promise, MutationObserver(DOM 变化监听器), MessageChannel(消息通道,跨文档通信)
什么是虚拟 DOM,为什么会有虚拟 DOM
虚拟 DOM: 表示真实 DOM 的 JavaScript 对象
为什么会有虚拟 DOM 呢?我的回答是可以做到跨平台以及批量渲染,面试官说不对,让我下去看
如何做到首屏从 55 -> 95 分的
回答得不是很好,只提到了资源优化、CodeSplitting以及 TreeShaking
新加的优化方式为:资源加载(使用新型图片、预加载 LCP 图片、图片懒加载;字体切割)、CodeSplitting、TreeShaking 优化
项目中已经存在的优化方式:按需加载第三方组件库,purgecss, 路由懒加载,代码压缩,静态资源缓存等
做过的尝试:字体预加载,CriticalCSS 等;对用到的三方库的组件进行二次封装,只引入需要用的样式,觉得维护成本太高,没有考虑
需要优化的点:使用在线字体、引入骨架屏
预渲染是怎么做的
因为是 CSR 项目,不方便集成 nuxt,vitepress 等框架,选用了 vite-ssg 插件的方案;预渲染的本质就是服务端生成 html;vite-ssg 打包会做三件事情:生成客户端代码、生成服务端代码、客户端代码激活
如何做到估点更准确
UI (拆分模块,通用组件)+ 接口对接 + 沟通
后续可以复盘,看沟通以及接口对接成本
二面
偏综合,看解决问题的思路
综合
在提 PR 之前你会做什么
如何保证老项目不被改出 bug
如何保证写的代码没有 bug
如果设计图和接口没有出怎么办
如果出现意外导致任务可能完不成怎么办
Vue 相关
computed 与 watch 的区别
**设计目的:**基于已有数据计算衍生值 VS 监听数据变化并执行副作用
**数据流向:**单项 VS 双向
**缓存机制:**缓存 VS 不缓存
**触发时机:依赖变更后自动更新 VS 显示监听数据变化
****代码结构:声明式(返回计算结果)VS 命令式(编写回调逻辑)
**适应场景:复杂表达式 VS 异步操作
自定义指令
提供了生命周期钩子,每个钩子都提供了以下参数:
el
:指令绑定的元素,可以直接操作 DOM。
binding
:一个对象,包含指令的各种信息,如value
(指令的值)、arg
(参数)、modifiers
(修饰符)等。
组件间事件通信
props + $emit
事件总线:EventBus
provide/inject
hooks/Pinia/Vuex
VueRouter: 路由间数据传递
在项目中有使用 render 函数吗
在做一些高阶组件的时候可能会用到 render 函数
高阶组件
vue 中如何做性能优化
Vue 特有的:v-once, v-memo, KeepAlive, v-if 与 v-view 的使用, 绑定 key,watch 与 computed 的使用,合理使用 shallowRef 与 shallowReactive
通用:
加载速度:DNS 预解析,http2 多路复用,图片懒加载,使用新型图片,避免引入大型文件,使用在线字体,CDN 存储静态资源,使用缓存等,按需加载三方库组件,异步组件,purgecss, CodeSplitting, TreeShaking, 代码合理分层,避免模块副作用与循环引用等,代码压缩等,
渲染:移除定时器(setInterval),div 的结构尽量浅,, css 层次尽量浅,合理使用防抖/节流,img 的父容器固定宽高,虚拟列表,使用 css3 动画
用户体验:骨架屏,渐进图片加载,合适的 loading 状态
React 特有的:memo, useMemo, useCallback
http1, http2, http3 的区别
特性
HTTP/1.x
HTTP/2
HTTP/3
底层协议
TCP
TCP
QUIC(基于 UDP)
二进制 / 文本
文本
二进制分帧
二进制分帧
多路复用
不支持(串行请求)
支持(同一连接并发请求)
支持(彻底解决队头阻塞)
头部压缩
无
HPACK 算法
QPACK 算法(改进版 HPACK)
服务器推送
不支持
支持
支持
队头阻塞
应用层和传输层都存在
仅传输层存在(TCP 丢包)
彻底消除
连接建立
1-3 RTT
1-2 RTT
0-RTT(后续连接)
安全性
可选 HTTPS(TLS 1.0+)
强制 HTTPS(TLS 1.2+)
强制 TLS 1.3
如何做状态管理
对于一些小型只需要轻量级、局部状态管理的项目可以直接使用 hooks,但 Hooks 有几个弊端:1. 状态修改没有规范,需要团队自己定义,不好管理 2. 不方便调试,需要自己实现调试工具 3. 持久化、缓存、日志等需要自己集成第三方库 4. 需要手动定义类型
而 Pinia 等状态管理库解决了这些问题:通过 actions 进行状态修改,支持时间旅行调试、状态快照,提供丰富的插件系统,比如持久化、缓存、日志等,自动类型推导等,能更方便进行状态管理
状态管理的库其实也是会转成响应式的变量,包含状态变量本身,基于状态变量的计算变量,修改状态的 actions 等
React 相关
类组件的生命周期
总体上可划分为初始化、挂载前、挂载后、更新前、更新后、卸载前、卸载后
初始化:
constructor: 组件的构造函数,用于初始化状态和绑定方法
getDerivedStateFromProps: 根据新的 props 更新 state
挂载前:
render: 描述 UI 结构,渲染组件
挂载后:
componentDidMount: 组件挂载到 DOM 后调用,常用于发起请求,添加事件监听器等副作用操作
更新前:
getDerivedStateFromProps:在接收到新的 props 时会再次被调用
shouldComponentUpdate:根据返回值决定组件是否需要更新,可用于性能优化
render:根据最新的 props 和 state 渲染组件
getSnapshotBeforeUpdate:在更新前获取最新的 DOM 信息,可用于保存滚动位置
更新后:
componentDidUpdate:组件更新完成后调用,可用于获取更新后的 DOM
卸载前:
componentWillUnmount:组件即将从 DOM 中卸载时调用
useEffect 与 useLayoutEffect 的区别
useLayoutEffect 会阻塞渲染,useEffect 不会
useLayoutEffect 可以用来做滚动位置恢复,动画等操作;useEffect 可以进行异步副作用操作,比如事件监听,发起请求,DOM 操作等
useEffect 如何做事件清理
useEffect 会返回清理函数
手写题:Promise.all