百度
核心问题与回答
Q1:请简单介绍一下自己?
- 我是百度文库的前端开发工程师,主要负责APP内嵌H5页面的开发,技术栈主要是Vue2和Vue3,也有少量项目使用React
- 自我介绍:23年毕业于xxx理工学院,有3年前端开发经验,目前在xxx科技公司负责智慧停车业务线的前端开发工作,搭建了包含C端H5小程序和B端管理系统的智慧停车管理平台
- 同时是uniapp开源组件库Wot UI的团队成员,参与开发了高拓展性的瀑布流组件和通用型脚手架
复盘后的标准回答:
- 面试官你好,我叫XX,23年毕业于XX理工学院软件工程专业,有3年前端开发经验
- 目前就职于XX科技公司,负责智慧停车业务线的前端开发,技术栈以Vue2/Vue3为主,也有React的实践经验
- 工作期间搭建了覆盖C端H5小程序和B端管理系统的智慧停车管理平台,包含停车缴费、欠费追缴、设备运维等核心模块
- 同时是uniapp开源组件库Wot UI的核心贡献者,主导开发了高拓展性的瀑布流组件和通用型脚手架,对组件库设计和工程化建设有较深理解
Q2:请说一下盒子模型?
- 盒子模型主要有BFC、IFC和FFC布局
- BFC会创建一个独立的空间,不受外部环境影响。触发BFC的条件包括:overflow:hidden/auto、绝对定位等
- HTML的body标签也是一个BFC
复盘后的标准回答:
- 盒子模型分为标准盒模型(content-box)和IE盒模型(border-box)
- 标准盒模型:width/height 只包含 content,不包含 padding 和 border
- IE盒模型:width/height 包含 content、padding、border
- 通过
box-sizing属性切换,项目中常用border-box更方便布局计算 - 关于BFC(块级格式化上下文):它是一个独立的渲染区域,内部元素不影响外部
- 触发BFC的条件:
overflow: hidden/auto、浮动、绝对定位、display: inline-block/flex/grid等 - 应用场景:清除浮动、防止margin合并、自适应两栏布局
Q3:请解释一下弹性布局?
- 弹性布局(Flex布局)是现在常用的布局方式,取代了传统的浮动布局
- Flex布局有主轴和侧轴,主轴属性有justify-content(居中、space-between等),侧轴属性有align-items(start、center、end等)
- 其他属性包括flex-grow、flex-shrink、flex-basis用于设置伸缩基准,order用于子元素排序,gap用于设置间距
复盘后的标准回答:
- Flex布局是一维布局模型,适用于主轴方向的排列,是最常用的现代布局方案
- 容器属性:
flex-direction(主轴方向)、justify-content(主轴对齐)、align-items(侧轴对齐)、flex-wrap(换行)、gap(间距) - 项目属性:
flex是flex-grow、flex-shrink、flex-basis的简写 flex: 1等价于flex-grow: 1; flex-shrink: 1; flex-basis: 0%,表示等分剩余空间order属性可以改变项目排列顺序,默认值为0- 相比浮动布局,Flex布局无需清除浮动,语义更清晰,控制更灵活
Q4:如何实现一个父元素内两个子元素水平垂直居中,其中左边子元素固定宽高100×100,右边子元素宽度自适应,高度100?
- 父元素开启flex布局,设置justify-content:center和align-items:center
- 左边子元素设置固定宽高100×100
- 右边子元素设置flex:1,高度100
复盘后的标准回答:
- 推荐使用Flex布局实现:
- 父容器:
display: flex; align-items: center;(垂直居中) - 左子元素:
width: 100px; height: 100px; flex-shrink: 0;(固定宽高,防止被压缩) - 右子元素:
flex: 1; height: 100px;(宽度自适应)
- 父容器:
- 如果不需要水平居中,父容器可以不加
justify-content: center,因为左右子元素已经自然排列 - 注意
flex-shrink: 0防止左子元素被压缩,这在移动端窄屏场景下很重要
Q5:实现一个四宫格布局有哪些方法?
- 方法一:使用flex布局,设置每个元素宽高为1/2,开启flex-wrap实现自动换行
- 方法二:使用grid布局,设置两列两行,现代浏览器基本都支持grid布局
复盘后的标准回答:
- 方法一:Flexbox + wrap
- 父容器:
display: flex; flex-wrap: wrap; - 子项:
width: 50%;或flex: 0 0 50%; - 优点:兼容性好,简单直观
- 缺点:如果需要严格2×2排列,需要控制每行刚好两个
- 父容器:
- 方法二:CSS Grid(推荐)
- 父容器:
display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; - 无需设置子项宽度,更简洁
- 优点:二维布局天然支持,代码更少,语义更强
- 现代浏览器(Chrome、Firefox、Safari、Edge)均已支持,生产环境可放心使用
- 父容器:
- 方法三:传统float布局(了解即可,不推荐)
- 父容器清除浮动,子项
float: left; width: 50%;
- 父容器清除浮动,子项
Q6:除了px单位外,还用过哪些单位?
- 小程序常用rpx,H5可以用rem
- 字体单位有em
- 响应式布局常用vw、vh、dvh、dvw等
- 移动端还有vmin、vmax
复盘后的标准回答:
- 相对单位:
em:相对于父元素的字体大小,适合用于按钮、卡片等局部组件的间距和字体rem:相对于根元素(html)的字体大小,适合做整体适配方案%:相对于父元素的百分比,布局中常用
- 视口单位:
vw/vh:相对于视口宽高的1%,适合做全屏自适应dvh/dvw:动态视口单位,解决了移动端浏览器地址栏显示/隐藏导致的视口变化问题vmin/vmax:取 vw 和 vh 中较小/较大的值
- 移动端特有:
rpx:微信小程序单位,按750设计稿换算,1rpx = 屏幕宽度/750
- 适配方案推荐:
- H5项目:使用
vw/vh+rem组合方案 - 小程序:使用
rpx - 组件库开发:使用
em做相对缩放
- H5项目:使用
Q7:Vue中如何保证样式隔离?
- 在style标签上添加scoped属性实现样式隔离
- Vue3会在编译时生成data属性加上文件哈希值,每个组件的哈希值不同,通过属性选择器实现样式隔离
复盘后的标准回答:
- Scoped方案(最常用):
<style scoped>编译时会为组件所有元素添加一个唯一的data-v-xxx属性- CSS选择器会加上属性选择器,如
.foo[data-v-xxx],实现样式隔离 - 缺点:父组件无法穿透修改子组件样式(除非使用
:deep())
- CSS Modules:
- 通过编译生成唯一的类名,如
.button_a3b4c,类名不冲突 - 在 Vue 中通过
$style对象或useCssModule使用
- 通过编译生成唯一的类名,如
- CSS-in-JS:
- 运行时生成唯一类名,样式和组件逻辑绑定在一起
- Vue 中不常用,更多见于 React 生态
- BEM命名规范:
- 通过约定式命名(Block__Element--Modifier)避免冲突
- 适合大型项目,但需要团队规范约束
- 穿透选择器(需要修改子组件样式时):
- Vue2:
>>>或/deep/ - Vue3:
::v-deep()或简写:deep()
- Vue2:
Q8:JavaScript的数据类型有哪些?
- 基本类型:number、boolean、string、undefined、null、symbol
- 引用类型:object(包括function、array等)
复盘后的标准回答:
- 基本类型(7种):
number、string、boolean、undefined、null、symbol、bigint- 基本类型存储在栈内存中,值不可变
- 注意
typeof null === 'object'是历史遗留bug symbol用于创建唯一标识符,常用于对象私有属性bigint用于表示大于2^53的整数,后缀加n
- 引用类型:
object- 包括普通对象、
array、function、Date、RegExp、Map、Set等 - 引用类型存储在堆内存中,变量保存的是内存地址
- 赋值时是引用传递,修改会影响原对象
- 包括普通对象、
- 关键区别:
- 基本类型比较的是值,引用类型比较的是引用地址
- 基本类型使用
===直接比较,引用类型需要深比较
Q9:如何判断一个数据的类型?
- 使用typeof
- 使用instanceof
- 通过原型方法判断,检查对象的原型是否是某个类的原型
复盘后的标准回答:
typeof: 适合判断基本类型typeof 123 // 'number'- 局限性:
typeof null // 'object',typeof [] // 'object'
instanceof: 判断对象是否是某个构造函数的实例[] instanceof Array // true- 原理:检查原型链
- 局限性:不同iframe或window之间的实例会失效
Object.prototype.toString.call()(最准确):Object.prototype.toString.call([]) // '[object Array]'Object.prototype.toString.call(null) // '[object Null]'- 可以准确判断所有内置类型,推荐在工具函数中使用
Array.isArray(): 专门判断数组constructor: 通过xxx.constructor.name获取构造函数名
Q10:请解释一下闭包?
- 闭包是指函数返回另一个函数,且内部函数引用了外部函数的变量,形成闭包
- 不当使用闭包可能导致内存泄漏
- 合理使用闭包可以实现私有变量等功能
- 工作中常用于防抖节流等场景
复盘后的标准回答:
- 定义: 闭包是指一个函数引用了其外层作用域中的变量,即使外层函数已执行完毕,这些变量仍然保存在内存中
- 形成条件: 函数嵌套 + 内部函数引用外部变量
- 应用场景:
- 防抖节流: 利用闭包保存定时器ID
- 私有变量: 模拟私有属性,如
function createCounter() { let count = 0; return () => ++count; } - 柯里化: 固定部分参数,返回接受剩余参数的函数
- 单例模式: 利用闭包保存实例引用
- 循环中保存索引: 经典 var 问题,用闭包或 let 解决
- 内存管理:
- 闭包会导致外部变量无法被垃圾回收,需注意及时释放
- 在不需要时,将引用置为
null解除闭包
Q11:请手写一个深拷贝的实现?
- 首先判断是否是基本类型,是则直接返回
- 判断是否是特殊类型(函数、正则、日期等),需要特殊处理
- 处理集合类型(Map、Set)
- 处理数组和对象,递归拷贝所有属性
- 使用属性描述符拷贝自有属性,可以处理Symbol等特殊属性
复盘后的标准回答:
- 生产环境推荐:
JSON.parse(JSON.stringify(obj))- 简单快速,但无法处理函数、undefined、symbol、循环引用、特殊对象(Date、RegExp、Map、Set等)
- 手写完整版深拷贝:
javascript
function deepClone(target, map = new WeakMap()) {
// 基本类型直接返回
if (typeof target !== 'object' || target === null) return target;
// 处理循环引用
if (map.has(target)) return map.get(target);
// 处理特殊类型
const Ctor = target.constructor;
if (Ctor === Date) return new Date(target);
if (Ctor === RegExp) return new RegExp(target);
if (Ctor === Map) {
const result = new Map();
map.set(target, result);
target.forEach((val, key) => result.set(key, deepClone(val, map)));
return result;
}
if (Ctor === Set) {
const result = new Set();
map.set(target, result);
target.forEach(val => result.add(deepClone(val, map)));
return result;
}
// 处理数组和对象
const result = Array.isArray(target) ? [] : {};
map.set(target, result);
// 拷贝所有属性(包括Symbol)
Reflect.ownKeys(target).forEach(key => {
result[key] = deepClone(target[key], map);
});
return result;
}- 要点: 使用
WeakMap处理循环引用,防止内存泄漏;使用Reflect.ownKeys可遍历 Symbol 属性;针对不同内置类型分别处理
Q12:Vue3的组件通信方式有哪些?
- props/emit父子组件通信
- 通过class等属性传递
- 作用域插槽
- provide/inject跨层级通信
- 获取组件实例通信
- 使用组合式函数或状态管理库(如Pinia)共享数据
- 路由传参
复盘后的标准回答:
- 父子组件通信:
props父传子,emit子传父(最基础方式)v-model双向绑定,Vue3支持多个v-model$attrs透传属性(class、style、事件监听器等)
- 跨层级通信:
provide / inject:祖先组件提供数据,后代组件注入,适合主题、用户信息等全局状态- 注意:
provide默认不是响应式,需传入ref或reactive
- 插槽通信:
- 作用域插槽
v-slot:子组件向父组件暴露数据,父组件决定渲染内容 - 适合列表项、表格列等需要自定义渲染的场景
- 作用域插槽
- 实例通信:
$refs获取子组件实例,调用其方法或访问属性defineExpose(Vue3)显式暴露子组件的内容
- 全局状态管理:
Pinia(推荐)或Vuex:适合复杂的状态共享- 组合式函数(Composables):利用全局ref共享状态,轻量级方案
- 路由传参:
query、params和state传参
Q13:为什么选择Pinia而不是Vuex?
- Vuex使用起来较繁琐,有mutation(不支持异步)和action需要配合使用
- Pinia更简单扁平,只有state和action,且action支持异步
- Pinia对TypeScript支持更友好
复盘后的标准回答:
- API简洁度: Pinia 移除了 Vuex 的 mutation,只有 state、getter、action,action 同时支持同步和异步
- TypeScript支持: Pinia 天然支持 TS,无需额外类型定义;Vuex 的 TS 支持较为繁琐
- 模块化: Pinia 每个 store 独立定义,不需要像 Vuex 那样注册 modules,天然支持代码分割
- DevTools: Pinia 支持 Vue DevTools,提供时间线调试、状态快照等功能
- 体积: Pinia 体积约 1KB,比 Vuex 更轻量
- 总结: Pinia 是 Vue 官方推荐的状态管理方案,Vuex 将不再有新的版本更新
Q14:Vue2和Vue3的响应式有什么区别?
- Vue2使用Object.defineProperty,只能监听自有属性,需要重写数组方法
- Vue3使用Proxy实现全对象代理,可以监听新增/删除属性
- Vue3通过ref和reactive实现响应式,底层使用WeakMap建立依赖关系
复盘后的标准回答:
- 实现原理差异:
- Vue2:
Object.defineProperty递归遍历对象每个属性,设置 getter/setter - Vue3:
Proxy直接代理整个对象,拦截所有操作(增删改查)
- Vue2:
- 关键能力差异:
- Vue2 无法检测「属性新增/删除」,需使用
Vue.set/delete - Vue2 无法检测「数组索引/长度变更」,需重写数组方法(push、pop 等7种)
- Vue3 通过 Proxy 天然支持所有变更,无需额外处理
- Vue2 无法检测「属性新增/删除」,需使用
- 性能优化:
- Vue2 初始化时递归遍历所有属性,对象深度大时性能开销大
- Vue3 使用
WeakMap建立依赖关系,按需收集依赖,惰性监听 - Vue3 的
ref用于基本类型,reactive用于对象类型,分工更清晰
- 响应式API变化:
- Vue2:
data选项中定义的数据自动响应式 - Vue3:显式使用
ref()/reactive()声明,toRefs()保持解构后响应式
- Vue2:
Q15:Vue3还有哪些优化?
- 架构改为monorepo,源码体积更小
- 更好的Tree Shaking支持
- 编译优化:静态提升、预字符串化、patch flag标记静态节点
- diff算法从双端diff改为快速diff
- 组合式API更灵活
- v-if和v-show优先级与Vue2相反
复盘后的标准回答:
- 架构优化:
- 采用 monorepo 架构,将核心功能拆分为独立包(reactivity、runtime-core、compiler-core 等),按需引入
- 更好的 Tree Shaking 支持,未使用的API不会打包到最终产物中
- 编译优化:
- 静态提升(HoistStatic): 将静态节点提升到渲染函数外部,避免重复创建
- 预字符串化(Pre-stringify): 连续静态节点合并为字符串,减少VNode创建
- Patch Flag: 标记动态节点(如 text、class、props 有变化),diff时只对比有标记的节点,跳过静态内容
- 缓存事件处理函数: 避免重复创建事件回调
- Diff算法优化:
- Vue2 使用双端 diff(4个指针交叉对比)
- Vue3 使用快速 diff(基于最长递增子序列,移动DOM节点更少)
- Composition API: 逻辑组合更灵活,相比 Options API 复用性更强
- v-if / v-show 优先级: Vue2 中 v-if 优先级高于 v-for,Vue3 相反
Q16:请介绍一下你参与的wot UI组件库?
- 主要负责瀑布流组件和全局提示(toast)组件
- 瀑布流组件解决了uniapp社区现有组件在页面切换时布局异常的问题
- 采用父子解耦架构,父组件维护列高和坐标,子组件通过作用域插槽支持任意内容
- 实现了异步队列和超时熔断机制,支持并行加载串行排版
- 做了增量更新优化,避免全部重排
- toast组件实现了状态和UI分离,支持在纯JS文件中调用
复盘后的标准回答:
- 我是 Wot UI 组件库的核心贡献者,主要负责瀑布流(Waterfall)组件和 Toast 组件的开发
- 瀑布流组件设计亮点:
- 采用父子解耦架构:父组件
wot-waterfall维护列高和坐标计算,子组件wot-waterfall-item通过作用域插槽支持任意内容渲染 - 异步队列 + 超时熔断机制:支持图片等资源并行加载、串行排版,设置超时阈值防止一个失败项阻塞整体
- 增量更新优化:新增数据时只对新项进行排版,不触发全部重排,性能大幅提升
- 解决了 uniapp 社区现有组件在页面切换时布局异常(布局错乱、白屏)的痛点
- 采用父子解耦架构:父组件
- Toast 组件设计亮点:
- 状态和 UI 分离:通过函数式调用(如
Toast.show()),支持在纯 JS 文件中使用,不依赖组件上下文 - 支持队列管理,多个 Toast 依次显示
- 状态和 UI 分离:通过函数式调用(如
- 技术收获: 深入理解了组件库设计模式、虚拟列表渲染优化、跨端兼容处理等
Q17:请介绍一下社区知识问答系统项目?
- 主要负责对话的流式渲染
- 后端处理用户提问优化,通过向量数据库匹配最相似答案
- 前端负责将返回结果进行流式渲染展示
复盘后的标准回答:
- 这是一个基于 RAG(检索增强生成)的社区知识问答系统
- 整体架构:用户提问 → 后端优化问题 → 向量数据库检索最相似答案 → 大模型生成回答 → 前端流式渲染
- 我主要负责前端部分:
- 流式渲染: 使用 Fetch API 的
ReadableStream或 SSE 接收后端逐块返回的数据,实现打字机效果 - 状态管理: 管理流式输出的中间状态,支持暂停/继续/取消等操作
- Markdown 渲染: 对 AI 返回的 Markdown 格式内容进行实时渲染,支持代码块高亮、公式等
- 流式渲染: 使用 Fetch API 的
- 技术难点:流式渲染过程中如何处理 Markdown 的渐进式解析(如代码块未闭合时的展示策略)
Q18:你有什么问题想问我们?
- 询问岗位具体工作内容(百度文库相关业务)
- 询问团队规模和配置(前端团队约五六十人)
- 寻求成长性建议(建议放松心态,准备好技术基础)
复盘后的标准回答:
- 关于业务: 请问这个岗位主要负责哪些业务线?是百度文库的核心功能迭代还是新业务的探索?
- 关于团队: 团队目前的规模和分工是怎样的?前端团队的前后端协作模式和后端如何配合?
- 关于技术栈: 团队目前使用的技术栈是什么?有没有计划迁移到更新的技术?
- 关于成长: 公司对新人的培养机制是怎样的?有没有技术分享或 Code Review 机制?
- 关于期望: 对于这个岗位的候选人,最看重的三个能力是什么?
- 注意: 提问时避开面试官已透露的信息,表现出对业务和团队的真实兴趣
访谈总结
本次面试全面考察了候选人的前端技术能力,包括CSS布局、JavaScript基础、Vue框架原理等。候选人展示了扎实的技术基础,特别是在组件开发方面有深入实践,如瀑布流组件的性能优化方案。同时,候选人也表现出对新技术的学习能力,如对Vue3响应式原理的理解。面试最后,候选人就岗位内容和职业发展提出了针对性的问题,表现出良好的职业态度。
