小红书一面:长达一个小时的拷打😭
前言
兄弟也是好起来了,又又有大厂面试了。
面试过程:
一、自我介绍
这个自我介绍我之前在面试文章中提到过,大家可以翻翻查看。
二、实习经历
面试官看到我目前在一家公司实习,于是让我聊了聊我的业务内容。
三、项目方面
1. 你为什么选用 Tailwind CSS?能说说有什么好处吗?
- 原子化设计:Tailwind CSS 是一种原子化 CSS 框架,将样式拆分为最小的功能单元,每个类只负责一个特定的样式属性。
- 开发效率高:像写内联类一样快速编写样式,无需额外创建 CSS 文件。
-
响应式友好:支持大量响应式类,例如
md:w-1/2
、lg:w-1/4
、lg:flex-row
等。 - 样式隔离性强:在 Vue 单文件组件中使用 Tailwind 类,避免传统 CSS 中的样式冲突问题,比如 TabBar、ShowProducts 等组件各自维护自己的样式。
- 技术广度体现:其实当时用这个是想展示一下对现代前端工具链的理解。
-
缺点:
- 学习曲线较陡,需要记忆大量类名和约定;
- 熟练后开发效率更高。
-
拓展思考:
- 便于 AI 辅助开发:原子类不依赖嵌套或继承,减少 AI 理解上下文的压力;
- 降低样式覆盖风险,减少了 CSS 的“层叠”问题和选择器冲突。
2. 你的项目用到了组件懒加载,讲讲好处?
- 在路由懒加载中使用了组件懒加载,实现 按需加载,只有当用户导航到特定路由时,才会加载相应的组件。
- 如果不使用懒加载,打包时会把所有页面打包成一个文件,首页一次性加载全部资源,导致加载速度慢,用户体验差。
- 使用路由懒加载后,首页资源被拆分为多个 chunk 文件(如
app.js
,home.js
),CSS 同样被拆分。 - 文章参考链接:前端性能优化
面试官追问:你知道为什么会这样吗?
我当时没回答上来,但后来查资料得知:
-
import()
的调用处被视为代码分割点,被请求模块及其子模块会被分离为独立的 chunk。 - Webpack 等构建工具识别
import()
,并将动态导入的模块单独打包,从而减小初始加载体积。 - 总体积不变,但首屏加载资源减少,提升用户体验。
3. 聊聊你项目中的动态组件
- 在实现一个礼物推荐助手时,我需要展示用户提问与 AI 回答。
- 为此我封装了两个组件:一个是用户消息组件,一个是 AI 回复组件。
- 每次对话内容存储在一个数组中,根据标志属性判断渲染哪个组件。
- 最终通过 Vue 内置的
<component>
标签结合:is
属性实现了动态组件切换。
4. 你实现 keep-alive 的目的,以及和 v-if / v-show 的区别?应用场景?
-
keep-alive 目的:
- 缓存组件状态(如表单输入、滚动位置);
- 避免组件频繁销毁重建;
- 减少 API 请求,提高性能和用户体验。
-
缓存控制:
- 使用
include="cachedComponents"
属性,只缓存设置了meta.cache = true
的组件。
- 使用
与 v-if
和 v-show
的区别:
对比项 | keep-alive | v-if | v-show |
---|---|---|---|
是否保持状态 | ✅ 是 | ❌ 否 | ✅ 是 |
渲染机制 | 组件缓存 | 条件为 false 不渲染 | 切换 display 属性 |
性能 | 切换成本低,适合频繁切换 | 初始化开销小 | 切换快,初始渲染全量 |
适用场景 | 多 tab 切换、表单缓存 | 不常切换、复杂组件 | 高频切换简单元素 |
5. 自定义图片懒加载怎么实现的?
流程如下:
scrollTop + offsetTop
=> getBoundingClientRect()
=> IntersectionObserver
从手动计算逐步过渡到现代浏览器 API,性能越来越好。
又问:你了解 HTML 中原生的 lazy 吗?能否讲讲?
- 原生 HTML 支持懒加载:
<img loading="lazy">
和<iframe loading="lazy">
- 优点:简单易用,无需 JS,现代浏览器原生支持;
- 缺点:兼容性一般,IE 不支持,功能有限;
- 定制性不强,更高级的需求建议使用
IntersectionObserver
自定义实现。
6.响应式布局这方面,你是怎么做的?
- 我的项目中,有一个商品展示的功能,使用的是
wc-waterfall
,动态的绑定gap和cols两个属性,通过生命周期挂载,添加事件监听,根据屏幕的大小,调整相应的值,来实现响应式布局。 - 通过@media声明在不同尺寸下微调样式细节
- 商城项目,经常用得到商品的展示,所以我会将它封装成一个组件,方便复用
四、场景题
1.setimeout
这个是一个面试经常问到的题目,但是他问的很细
for(var i=0;i<4;i++){
setTimeout(function(){
console.log(i);
},1000)
}
首先问你输出什么?
由于 var i
的声明具有函数作用域(在这里指全局作用域),所有的 setTimeout
回调函数实际上引用的是同一个 i
变量。当定时器触发时(即循环已经结束),i
的值已经是 4
,因此所有回调打印的结果都是 4
。在整个循环过程中只有一个 i
,最后连着输出四个4.
又问大概在什么时候输出: 因为定时器不一定准,所以是大概的时间,可能就会回答在4秒后了,实际上,执行同步代码的循环后,定时器四个任务,相继执行,因为间隔时间很短,所以就很像四个定时器并发了一样,实际上还是一个又一个执行的。在大概一秒后。
如何输出 0 1 2 3呢?
var => let
-
块级作用域:在每次
for
循环迭代中,都会创建一个新的i
实例。这些i
变量被限制在循环体的块级作用域内。 -
延迟执行的回调函数:每个
setTimeout
回调函数捕获的是它对应的那个特定的i
实例。因此,当定时器触发并执行回调函数时,它们能够访问到正确的i
值,而不是所有回调都指向同一个i
。
那我想要每隔大概一秒输出一个数字呢?
- 当时我想到了是:
for (let i = 0,time=1000; i < 4; i++,time+=1000) {
setTimeout(function () {
console.log(new Date())
console.log(i);
}, time);
}
2025-05-18T14:14:54.808Z
0
2025-05-18T14:14:55.806Z
1
2025-05-18T14:14:56.814Z
2
2025-05-18T14:14:57.800Z
3
- 还有就是使用闭包加立即执行函数了:
for (var i = 0; i < 4; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
}, 1000*i);
})(i);
}
- 进阶:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
(async function () {
for (var i = 0; i < 4; i++) {
await delay(1000);
console.log(i);
}
})();
2. 数组求和
const nestedObj = [1, [2, [3, [4, 5]]], 6, 7], 求和。前几天刚好看到了数组和对象的扁平化,刚好就能用上了,不过面试官好像想让我用更简单的方法,我没想出来。
let sum=0
function flattenObject(obj ) {
for (const item of obj) {
if (Array.isArray(item)) {
flattenObject(item);
} else {
sum=sum+item;
}
}
return sum;
}
console.log(flattenObject(nestedObj));// [ 1, 2, 3, 4, 5, 6, 7]
学习点其他简单的方法:
- 递归
function sum(arr) {
return arr.reduce((total, item) => {
return total + (Array.isArray(item) ? sum(item) : item);
}, 0);
}
const nestedObj = [1, [2, [3, [4, 5]]], 6, 7];
console.log(sum(nestedObj)); // 输出: 28
结语
面了几家大厂后,也有一些心得:
大厂面试一定是穷追猛打,问到你不会为止。所以有些难题回答不出来也没关系。
而面试其实就是一场表演,大家可以在项目中准备几个亮点,自己演练几遍,在面试时流畅表达出来,体现出自己的深度和思考能力,这才是关键!