美团外包面试

8 阅读4分钟

一面

两道题: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