快手用户增长部门正积极拥抱AI技术,通过“技术驱动+生态协同”的策略来推动用户规模和活跃度的提升。其核心是利用AI优化内容生态、提升用户体验、赋能创作者和商家,并积极拓展海外市场。

下面是一个主要布局方向的表格总结:
| 布局方向 | 具体应用与举措 | 预期效果/现状 |
|---|---|---|
| 📊 用户数据基本盘 | 2025年Q1平均日活跃用户(DAU)4.08亿,月活跃用户(MAU)7.12亿,DAU创历史新高。 | 用户增长虽放缓,但基本盘稳固,寻求通过AI提升存量用户价值。 |
| 🤖 核心AI引擎:可灵 | 可灵AI(视频生成大模型)是核心驱动。成立可灵AI事业部(一级部门),迭代快(发布以来超20次迭代,如2.0、2.1版本)。提供更具性价比的视频生成服务(如降价65%)。 | 全球用户超2200万,月活增长25倍。Q1收入超1.5亿元,70%收入来自海外付费用户。 |
| 🛍️ 电商场景深度融合 | AI试衣:提升购物体验和转化。AI快捷评论:基于ASR模型生成互动评论,提升直播间氛围。智能客服:问题解决率提升至80%,大幅降本增效。直播切片:AI自动生成商品讲解等切片视频,日均GMV同比增长超300%。 | 提升用户购买决策效率和购物体验,2025年Q1电商GMV同比增长15.4%至3323亿元。 |
| 📈 内容推荐与广告提效 | 生成式推荐大模型OneRec:端到端模型,更精准的内容分发。覆盖主站约25%流量,未来计划提升至50%-60%。测试中带来用户时长超1%的增长。AIGC营销素材:帮助商家低成本生成广告素材,日均广告消耗达3000万元。 | 提升用户粘性和使用时长,优化广告投放效率,2025年Q线上营销服务收入180亿元(同比增8%)。 |
| 🌍 全球化拓展 | 可灵AI海外表现强劲:70%收入来自海外用户。重点市场:巴西是核心市场之一,正拓展中东和东南亚(如印尼SnackVideo)。本地化运营:加大投入,计划扩展多语言市场。 | 2025年Q1海外收入13.2亿元(同比增32.7%),经营利润实现扭亏为盈。可灵AI成为全球化竞争的利器。 |
| ⚙️ 赋能创作者与商家 | 降低创作门槛:可灵帮助用户轻松生成高质量视频。智能经营工具:如“智能开播”工具帮助新商家生成直播话术。新商扶持:AI智能顾问提供全链路陪伴服务,新入驻商家数量同比增长超30%。 | 繁荣平台生态,吸引更多创作者和商家入驻,形成良性循环。 |
💡 底层逻辑与挑战
快手用户增长部门的AI布局,其底层逻辑是**“三重螺旋”增长范式**:
- 技术螺旋:以可灵等大模型矩阵为引擎。
- 经营螺旋:AI赋能商家全面提升经营效率。
- 生态螺旋:AI最终反哺并重塑整个平台生态,驱动业务增长。
当然,也面临一些挑战:
- 用户增长天花板:国内用户增速放缓,存量竞争激烈。
- AI贡献占比仍小:尽管增速快,但AI业务收入占总营收比例仍较低(约1%),需要时间成长为核心收入来源。
- 平衡“AI”与“人情味”:需警惕过度依赖AI可能导致内容同质化,如何保持快手独特的“老铁”社区氛围和真实感是一大考验。
- 激烈的市场竞争:不仅来自传统短视频平台,也面临其他AI大模型厂商的竞争。
✅ 总结
总的来说,快手用户增长部门正从追求用户数量的高速增长,转向依托AI技术驱动用户价值和商业价值的深度增长。通过可灵这样的AI基础设施,快手不仅在优化内部效率和用户体验,更在构建一个对B端和C端都更具吸引力和效率的生态系统,并为全球化扩张提供了新的利器。
一面
1. animation 和 transition 的区别
| 特性 | transition (过渡) | animation (动画) |
|---|---|---|
| 功能 | 用于实现状态变化的过渡效果。 | 用于创建复杂的关键帧动画。 |
| 控制 | 相对简单,需要触发(如:hover, 添加类)。 | 功能强大,可自动播放、控制循环次数、方向、暂停等。 |
| 关键帧 | 隐式定义(从A状态到B状态)。 | 通过 @keyframes 显式定义多个关键帧和中间状态。 |
| 循环 | 不能循环播放。 | 可以无限循环 (infinite)。 |
| 中间状态 | 只能定义开始和结束两个状态。 | 可以定义任意多个关键帧和中间状态。 |
| 触发 | 需要事件触发(如鼠标悬停、JS添加类)。 | 可以自动立即播放,也可由事件触发。 |
总结:transition 适合简单的、由事件触发的状态转变动画。animation 适合复杂的、自动的、需要精细控制多个关键帧的动画。
2. 手撕动画:div先下移100px,再右移100px
核心思路是使用 animation 和 @keyframes,通过关键帧控制位移顺序。
<!DOCTYPE html>
<html lang="en">
<head>
<style>
.box {
width: 100px;
height: 100px;
background-color: red;
/* 应用动画 */
animation: move 2s ease-in-out forwards;
}
@keyframes move {
0% {
transform: translate(0, 0);
}
50% {
/* 先下移 */
transform: translate(0, 100px);
}
100% {
/* 再右移(同时保持下移的状态) */
transform: translate(100px, 100px);
}
}
</style>
</head>
<body>
<div class="box"></div>
</body>
</html>
forwards属性使得动画在结束后保持在最后一帧的状态。- 通过设置
50%和100%两个关键帧,精确控制了移动的顺序。
3. 手撕:倒计时组件
这是一个基于 React Hooks 的功能完整的倒计时组件。
import React, { useState, useEffect, useRef } from 'react';
const CountdownTimer = ({ initialTime = 60, onEnd }) => {
const [timeLeft, setTimeLeft] = useState(initialTime);
const [isRunning, setIsRunning] = useState(false);
const timerRef = useRef(null);
// 效果:计时器逻辑
useEffect(() => {
if (isRunning && timeLeft > 0) {
timerRef.current = setInterval(() => {
setTimeLeft(prevTime => prevTime - 1);
}, 1000);
} else if (timeLeft === 0) {
clearInterval(timerRef.current);
onEnd?.(); // 可选的回调,计时结束时触发
}
// 清理函数:组件卸载或依赖变化时清除定时器,防止内存泄漏
return () => clearInterval(timerRef.current);
}, [isRunning, timeLeft, onEnd]);
const start = () => setIsRunning(true);
const pause = () => setIsRunning(false);
const reset = () => {
setIsRunning(false);
setTimeLeft(initialTime);
};
// 格式化时间显示 (e.g., 65 -> "01:05")
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
};
return (
<div>
<h1>{formatTime(timeLeft)}</h1>
<button onClick={start} disabled={isRunning || timeLeft === 0}>
开始
</button>
<button onClick={pause} disabled={!isRunning}>
暂停
</button>
<button onClick={reset}>重置</button>
</div>
);
};
export default CountdownTimer;
4. CDN 为什么可以加速?
CDN(内容分发网络)通过以下方式加速网站访问:
- 地理分布:将你的静态资源(JS, CSS, 图片等)缓存到全球各地的边缘节点服务器上。
- 就近访问:用户请求资源时,CDN会智能调度,将请求重定向到离用户地理距离最近、网络路径最优的边缘节点,极大减少网络传输延迟(RTT)。
- 减轻源站负载:大多数请求由边缘节点响应,大大降低了源服务器的带宽和计算压力。
- 优化传输:大型CDN提供商拥有优质的骨干网和跨网互联资源,优化了网络传输路径。
5. splitchunks 怎么配置?
SplitChunksPlugin 是 Webpack 用于代码分割的插件。其核心配置在 optimization.splitChunks 中:
// webpack.config.js
module.exports = {
// ...
optimization: {
splitChunks: {
chunks: 'all', // 对所有类型的chunk进行处理(async, initial, all)
cacheGroups: {
// 1. 分离第三方库(node_modules)
vendor: {
test: /[\\/]node_modules[\\/]/, // 匹配路径
name: 'vendors', // chunk的名称
priority: 10, // 优先级,防止同时满足多个条件时被打包到优先级低的组里
},
// 2. 分离公共代码(被多个chunk引用的模块)
common: {
name: 'common',
minChunks: 2, // 被2个及以上chunk引用才提取
minSize: 0, // 生成chunk的最小大小
priority: 5,
},
},
},
},
};
常用配置项:
chunks: 选择哪些代码块进行优化。minSize: 生成 chunk 的最小体积(字节)。minChunks: 被引用次数超过该值才会被提取。maxAsyncRequests: 按需加载时的最大并行请求数。maxInitialRequests: 入口点的最大并行请求数。cacheGroups: 定义如何对模块进行分组。
6. 前端如何进行缓存?
前端缓存主要分为以下几类:
-
HTTP 缓存(浏览器缓存):
- 强缓存:
Cache-Control(max-age),Expires。浏览器直接读取本地缓存,不发起请求。 - 协商缓存:
Last-Modified/If-Modified-Since,ETag/If-None-Match。浏览器询问服务器资源是否过期,未过期则返回304状态码,使用本地缓存。
- 强缓存:
-
Service Worker 缓存:
- 可以拦截网络请求,实现复杂的离线缓存策略(Cache First, Network First等)。
-
数据存储缓存:
localStorage/sessionStorage:存储简单的键值对数据。IndexedDB:存储大量结构化数据。Cookie:通常用于身份验证等小数据存储。
7. React 和 Vue 的区别
| 方面 | React | Vue |
|---|---|---|
| 设计理念 | 函数式编程,推崇“单向数据流”和“不可变性”。 | 响应式编程,基于可变数据的“双向绑定”(在表单等特定元素上)。 |
| 核心语法 | JSX(JavaScript XML),在JS中写类HTML结构。 | 单文件组件(SFC),将模板、逻辑、样式封装在一个.vue文件中。 |
| 状态管理 | 使用 useState, useReducer Hooks 或状态管理库(Redux)。 | 使用 data 返回响应式对象,或 Pinia/Vuex。 |
| 更新机制 | 状态更新后,需要通过 setState 或 dispatch 触发重新渲染。 | 直接修改状态即可,响应式系统自动触发相关组件更新。 |
| 生态系统 | 更灵活,社区提供了大量选择,但需要自行决策和集成(如路由、状态管理)。 | 更集成化,官方维护了路由(Vue Router)、状态管理(Pinia)等,开箱即用。 |
| 学习曲线 | 对JavaScript基础要求更高,概念相对更少但更抽象。 | 模板更易上手,API设计对初学者更友好,高级特性需要更多学习。 |
8. ESM 和 CMJ 的区别
| 特性 | ESM (ECMAScript Modules) | CMJ (CommonJS) |
|---|---|---|
| 语法 | import / export | require() / module.exports |
| 加载方式 | 静态编译时加载,支持Tree Shaking。 | 动态运行时加载。 |
| 运行环境 | 浏览器原生支持和Node.js(v12+)。 | Node.js环境。 |
| 值引用 | 动态只读引用。导入的是值的引用,模块内修改会影响外部。 | 值的拷贝(浅拷贝)。导入的是导出值的副本。 |
| 顶层位置 | 只能在模块顶层使用。 | 可以在代码任何地方使用。 |
| 异步 | 原生支持异步加载。 | 是同步加载。 |
| this指向 | 顶层this是undefined。 | 顶层this指向当前模块。 |
二面
1. i18n 原理
国际化(i18n)的核心原理是资源替换。
- 定义语言包:为每种支持的语言创建一个资源文件(通常是JSON或JS对象),包含所有需要翻译的文本键值对。
// en.json { "welcome": "Welcome", "button_text": "Submit" } // zh-CN.json { "welcome": "欢迎", "button_text": "提交" } - 建立Provider/Context:在应用顶层提供一个国际化上下文,管理当前语言和切换语言的方法。
- 翻译函数:提供一个函数(如
t('welcome')),它根据当前选择的语言,从对应的语言包中查找键(welcome)并返回对应的翻译值("Welcome"或"欢迎")。 - 组件集成:UI组件使用这个翻译函数来渲染文本,而不是硬编码字符串。
- 切换语言:当用户切换语言时,更新Context中的语言状态,触发组件重新渲染,从而加载新的语言包资源。
2. 强缓存和协商缓存
-
强缓存:浏览器不向服务器发送请求,直接使用本地缓存。通过
Cache-Control和ExpiresHTTP响应头控制。Cache-Control: max-age=3600表示资源在3600秒内有效。Expires: Wed, 21 Oct 2025 07:28:00 GMT是一个绝对时间戳(HTTP/1.0,优先级低于Cache-Control)。
-
协商缓存:浏览器向服务器发送请求,询问缓存是否依然有效。如果有效,服务器返回304 Not Modified,浏览器使用本地缓存。通过
Last-Modified/If-Modified-Since或ETag/If-None-Match控制。Last-Modified/If-Modified-Since:基于文件的修改时间。ETag/If-None-Match:基于文件内容的哈希值,更精确(例如,文件内容没变但修改时间变了,ETag不会变)。
缓存流程:浏览器发起请求 -> 检查强缓存是否过期 -> 未过期,直接用缓存 -> 已过期,则发起协商缓存请求 -> 缓存有效(304),用缓存 -> 无效(200),返回新资源。
3. 强缓存什么情况下会失效?
- 时间到期:
Cache-Control设置的max-age或s-maxage时间到期。 - 用户行为:
- 用户强制刷新(Ctrl + F5)会忽略所有缓存,直接向服务器请求。
- 用户清除了浏览器缓存。
- 缓存策略变化:服务器更新了资源,并更改了资源的URL(例如通过添加哈希值到文件名:
app.[hash].js)。这是最常用、最有效的缓存策略。
4. 兼容性的问题一般怎么看?
- MDN Web Docs:查询每个API或CSS属性的兼容性表格,清晰列出了各浏览器及其版本的支持情况。
- Can I use:权威的网站兼容性查询工具,输入特性名称即可查看详细的浏览器支持范围。
- Babel:使用Babel编译器,它可以将新的ES6+语法转换为旧版本浏览器兼容的ES5语法。
- PostCSS + Autoprefixer:处理CSS兼容性,自动添加浏览器厂商前缀(如
-webkit-,-moz-)。 - Polyfill:对于无法通过编译转换的API(如
Promise,fetch),使用core-js等库提供实现。
5. 有没有了解过为什么兼容性会造成高度塌陷的问题?
“高度塌陷”是CSS布局中的一个经典问题,通常由浮动(float) 引起,与浏览器旧版本对标准的实现差异有关。
- 原因:当一个元素内部只包含浮动元素时,该元素的高度在旧式浏览器中会计算为0,就好像里面没有内容一样,这就是“高度塌陷”。
- 原理:浮动元素会脱离正常的文档流,父容器无法感知其存在,因此无法被浮动子元素“撑开”。
- 解决方案(清除浮动):
- 额外标签法:在浮动元素末尾添加一个空标签,设置
clear: both;。 - 伪元素法(推荐):给父容器添加一个清除浮动的伪元素。这是Bootstrap等框架使用的方法。
.clearfix::after { content: ""; display: table; clear: both; } - 触发BFC:给父容器设置属性,触发其BFC(块级格式化上下文),使其可以包含浮动元素。例如
overflow: hidden;或display: flow-root;(现代、专为此设计)。
- 额外标签法:在浮动元素末尾添加一个空标签,设置
6. App 和 H5 具体怎么调试?
- Android:
- Chrome DevTools:在Chrome中打开
chrome://inspect,启用USB调试后,可以看到连接的设备和App内的WebView,可以直接调试。
- Chrome DevTools:在Chrome中打开
- iOS:
- Safari Web Inspector:在Safari偏好设置中开启“开发”菜单。用USB连接iOS设备后,在“开发”菜单中选择你的设备或App内的WebView进行调试。
- 通用方案:
- 代理调试(Charles/Fiddler):抓取H5页面发出的网络请求,分析问题。
- VConsole/Eruda:在页面中引入这些轻量级库,在手机屏幕上生成一个类似DevTools的控制台,方便查看日志、网络请求等信息。
- Weinre:远程调试工具,在项目中引入脚本,即可在PC浏览器上调试手机页面。
7. 手撕:LRU 缓存
LRU(Least Recently Used)缓存淘汰算法。我们使用 Map 数据结构来实现,因为 Map 能记住键的原始插入顺序。
class LRUCache {
constructor(capacity) {
this.capacity = capacity; // 缓存容量
this.cache = new Map(); // 使用Map来存储键值对
}
get(key) {
if (!this.cache.has(key)) return -1; // 如果key不存在,返回-1
const value = this.cache.get(key);
this.cache.delete(key); // 删除原有键
this.cache.set(key, value); // 重新设置,将其变为最新使用的
return value;
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key); // 如果key已存在,先删除
} else if (this.cache.size >= this.capacity) {
// 如果缓存已满,删除最久未使用的(即Map中的第一个键)
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
this.cache.set(key, value); // 设置新的键值对
}
}
// 测试
const lruCache = new LRUCache(2);
lruCache.put(1, 1);
lruCache.put(2, 2);
console.log(lruCache.get(1)); // 返回 1
lruCache.put(3, 3); // 该操作会使得密钥 2 作废
console.log(lruCache.get(2)); // 返回 -1 (未找到)
lruCache.put(4, 4); // 该操作会使得密钥 1 作废
console.log(lruCache.get(1)); // 返回 -1 (未找到)
console.log(lruCache.get(3)); // 返回 3
console.log(lruCache.get(4)); // 返回 4
8. 手撕:聊天框
这是一个简化的React聊天框组件,包含消息列表和发送框。
import React, { useState, useRef, useEffect } from 'react';
const ChatBox = () => {
const [messages, setMessages] = useState([]);
const [inputValue, setInputValue] = useState('');
const messagesEndRef = useRef(null);
// 效果:自动滚动到底部
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(scrollToBottom, [messages]);
// 发送消息
const handleSend = () => {
if (inputValue.trim()) {
const newMessage = {
id: Date.now(),
text: inputValue,
timestamp: new Date().toLocaleTimeString(),
isOwn: true, // 模拟是自己发送的消息
};
setMessages(prev => [...prev, newMessage]);
setInputValue('');
// 模拟收到回复 (可选)
setTimeout(() => {
const replyMessage = {
id: Date.now() + 1,
text: `Echo: ${newMessage.text}`,
timestamp: new Date().toLocaleTimeString(),
isOwn: false,
};
setMessages(prev => [...prev, replyMessage]);
}, 1000);
}
};
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
handleSend();
}
};
return (
<div className="chat-container">
<div className="messages-container">
{messages.map(msg => (
<div
key={msg.id}
className={`message-bubble ${msg.isOwn ? 'own-message' : 'other-message'}`}
>
<div className="message-text">{msg.text}</div>
<div className="message-time">{msg.timestamp}</div>
</div>
))}
<div ref={messagesEndRef} /> {/* 用于滚动定位的锚点 */}
</div>
<div className="input-area">
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="输入消息..."
/>
<button onClick={handleSend}>发送</button>
</div>
</div>
);
};
export default ChatBox;
/* 简单样式 */
.chat-container {
display: flex;
flex-direction: column;
height: 400px;
border: 1px solid #ccc;
}
.messages-container {
flex: 1;
overflow-y: auto;
padding: 10px;
}
.message-bubble {
max-width: 70%;
margin-bottom: 10px;
padding: 8px 12px;
border-radius: 18px;
word-break: break-word;
}
.own-message {
background-color: #0084ff;
color: white;
margin-left: auto;
}
.other-message {
background-color: #e4e6eb;
color: black;
}
.message-time {
font-size: 0.7em;
margin-top: 4px;
text-align: right;
opacity: 0.7;
}
.input-area {
display: flex;
padding: 10px;
border-top: 1px solid #ccc;
}
.input-area input {
flex: 1;
padding: 8px;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
三面
1. 跨域的情况下如何获取到别的页面 localStorage 里存放的数据?如何进行安全处理?
核心方法:使用 window.postMessage 和 message 事件进行跨文档消息传递。
实现原理:
- 在
a.com的页面(父页面)中通过iframe嵌入b.com的页面(子页面)。 - 父页面监听
message事件。 - 子页面在加载后,使用
postMessage将存储在b.com的localStorage中的数据发送给父页面a.com。 - 父页面在收到消息后,从中提取数据。
安全处理(至关重要):
- 验证来源:在父页面的
message事件监听器中,必须检查event.origin,确保消息来自你信任的特定域名(b.com),防止恶意网站发送消息。 - 限制目标:在子页面使用
postMessage时,最好指定精确的目标 origin(parent.postMessage(data, 'https://a.com')),避免数据发送到未知的、可能恶意的窗口。 - 数据验证:对接收到的数据格式和内容进行校验,不要直接信任和使用。
示例代码:
<!-- 父页面 (a.com) -->
<iframe src="https://b.com/child.html" id="childFrame"></iframe>
<script>
window.addEventListener('message', function(event) {
// 1. 安全检查:验证消息来源
if (event.origin !== 'https://b.com') {
return;
}
// 2. 处理数据
console.log('Received data from child:', event.data);
});
</script>
<!-- 子页面 (b.com/child.html) -->
<script>
// 从自己的localStorage中读取数据
const data = localStorage.getItem('myData');
// 发送数据给父页面,并指定目标origin
window.parent.postMessage(data, 'https://a.com');
</script>
2. 性能指标怎么看的?FCP 理想状态应该在几秒内?
- 如何看:使用工具(如 Lighthouse, Chrome DevTools Performance panel, Web Vitals library)来测量和监控这些指标。
- FCP (First Contentful Paint) - 首次内容绘制:测量页面开始加载到屏幕首次显示任何文本、图片、非白色 canvas/SVG 的时间。
- 理想状态:小于等于 1.5 秒。
- Google 的评级标准:
- Good (绿色): ≤ 1.5s
- Needs Improvement (橙色): 1.5s ~ 3.0s
- Poor (红色): > 3.0s
3. 哪些优化会对 FCP 这个指标产生影响?
优化FCP的核心是尽快呈现初始内容。
- 优化关键渲染路径:
- 消除渲染阻塞资源:延迟加载非关键CSS/JS,使用
async或defer。 - 缩小CSS:压缩CSS文件,移除未使用的CSS。
- 内联关键CSS:将首屏内容所需的关键CSS直接内联在HTML的
<head>中。
- 消除渲染阻塞资源:延迟加载非关键CSS/JS,使用
- 优化服务器响应:
- 减少TTFB(Time to First Byte):使用CDN、优化后端逻辑、使用缓存。
- 启用压缩(如Gzip/Brotli)。
- 资源优化:
- 优化图片:压缩、使用现代格式(WebP/AVIF)、响应式图片(
srcset)。 - 预加载关键资源:使用
<link rel="preload">提示浏览器尽早获取重要资源。
- 优化图片:压缩、使用现代格式(WebP/AVIF)、响应式图片(
- 缓存:合理配置HTTP缓存(强缓存),让浏览器快速从本地加载资源。
4. 性能指标好和坏的标准
Google 使用 Core Web Vitals 作为关键用户体验指标,其标准如下:
| 指标 | 定义 | 好 (Green) | 需要改进 (Orange) | 差 (Red) |
|---|---|---|---|---|
| LCP (Largest Contentful Paint) | 最大内容元素绘制时间 | ≤ 2.5s | 2.5s ~ 4.0s | > 4.0s |
| FID → INP (Interaction to Next Paint) | 响应下一次用户交互的延迟 | ≤ 100ms | 100ms ~ 200ms | > 200ms |
| CLS (Cumulative Layout Shift) | 累计布局偏移分数 | ≤ 0.1 | 0.1 ~ 0.25 | > 0.25 |
(注:FID已被INP取代,成为新的核心指标)
前端面试题目详解
1. 自我介绍
面试官您好,我叫刘警,是南京师范大学地图学与地理信息系统专业的26届硕士毕业生。我专注于前端开发领域,有扎实的前端工程化经验和全栈开发能力。
在我的硕士期间,我作为负责人参与了国家重点研发项目"城市灾害应急管理集成系统"的开发,使用Vue+SpringBoot技术栈。我还有在得物集团的实习经历,参与开发了B端低代码落地页编辑器和C端性能优化工作,使首屏秒开率从15%提升至65%。
我建立了个人技术博客,是CSDN前端优质创作者,总访问量140w+,粉丝2.5w+,持续输出前端技术内容。已主导完成5项软件著作权登记和1项发明专利。
我热爱前端技术,有较强的学习能力和解决问题的能力,期待能在前端开发岗位上贡献价值。
2. 为什么学前端,学了多久
我学习前端开发已经3年多了。最初接触前端是因为被网页的视觉交互效果所吸引,后来发现前端技术能将创意快速实现并呈现给用户,这种即时反馈的成就感让我深深着迷。
随着深入学习,我认识到前端开发不仅是UI实现,更涉及性能优化、工程化、用户体验等深层次技术,这激发了我持续探索的热情。特别是在研究生阶段参与国家级项目开发后,我更加坚定了在前端领域深耕的决心。
3. HTML相关问题
HTML学习的重点有哪些
HTML学习的重点包括:
- 语义化标签:正确使用header、nav、main、article、section等语义化标签
- 表单与输入控件:掌握各种input类型和表单验证
- 可访问性(ARIA):确保页面对所有用户都可访问
- 元数据和SEO:合理使用meta标签优化搜索引擎收录
- 多媒体元素:audio、video、canvas等标签的使用
- 性能优化:preload、prefetch等资源加载优化
HTML里如何实现飞书文档字体变化的效果
飞书文档的字体变化效果可以通过contenteditable属性结合CSS实现:
<div contenteditable="true" class="document-editor">
这里是可以编辑的文本内容
</div>
<style>
.toolbar button {
/* 工具栏按钮样式 */
}
.document-editor {
min-height: 300px;
border: 1px solid #ddd;
padding: 10px;
}
.bold {
font-weight: bold;
}
.italic {
font-style: italic;
}
.underline {
text-decoration: underline;
}
</style>
<script>
// 通过document.execCommand实现文本格式控制
function formatText(command, value = null) {
document.execCommand(command, false, value);
}
// 或者使用现代API
function toggleBold() {
document.querySelector('.document-editor').classList.toggle('bold-mode');
}
</script>
现代实现可能使用自定义编辑器或基于Canvas的渲染技术,但基本原理是通过控制CSS样式或使用文档编辑API。
4. CSS相关问题
CSS怎么学的
我通过多途径系统学习CSS:
- 基础知识:通过MDN文档和教程掌握核心概念
- 实践项目:在真实项目中应用和解决布局问题
- 案例分析:研究知名网站的CSS实现方式
- 新技术跟进:学习Grid、Flexbox等现代布局技术
- 性能优化:掌握渲染原理和优化技巧
CSS选择器优先级
CSS选择器优先级从高到低:
- !important声明
- 内联样式(style属性)
- ID选择器(#id)
- 类选择器/属性选择器/伪类(.class, [type=“text”], :hover)
- 元素选择器/伪元素(div, ::before)
- 通配符/关系选择器(*, >, +, ~)
优先级计算通常用(a,b,c,d)表示:
- a: 是否内联样式
- b: ID选择器数量
- c: 类、属性、伪类选择器数量
- d: 元素、伪元素选择器数量
选中同时有a和b类的元素
/* 只选中同时有a和b类的元素 */
.a.b {
color: red;
}
/* 忽略单独的a或b */
.a:not(.b), .b:not(.a) {
color: initial;
}
选择三个子元素的实现
.parent {
display: flex;
}
/* 选择第一个子元素 */
.parent > :first-child {
background-color: #ff9999;
}
/* 选择第二个子元素(中间) */
.parent > :nth-child(2) {
background-color: #99ff99;
}
/* 选择最后一个子元素 */
.parent > :last-child {
background-color: #9999ff;
}
垂直居中实现
多种垂直居中方法:
- Flexbox方案:
.container {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
height: 300px;
}
- Grid方案:
.container {
display: grid;
place-items: center;
height: 300px;
}
- 绝对定位方案:
.container {
position: relative;
height: 300px;
}
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
- Table-cell方案:
.container {
display: table;
width: 100%;
height: 300px;
}
.centered {
display: table-cell;
vertical-align: middle;
text-align: center;
}
Position属性详解
CSS position属性定义元素的定位方式:
static:默认值,元素处于正常文档流中relative:相对定位,相对于自身原本位置进行偏移absolute:绝对定位,相对于最近的非static定位祖先元素fixed:固定定位,相对于视口定位,不随滚动条滚动sticky:粘性定位,在滚动时保持在特定位置
5. JavaScript相关问题
JS怎么学的
我通过以下方式系统学习JavaScript:
- 语言基础:深入理解原型、作用域、闭包、异步等核心概念
- 现代特性:学习ES6+新特性如箭头函数、解构、Promise等
- 类型系统:掌握TypeScript增强开发体验
- 运行机制:研究事件循环、内存管理、V8引擎优化
- 设计模式:应用模块化、面向对象等编程范式
- 实践项目:在真实项目中应用和优化JS代码
作用域和闭包
作用域决定了变量的可见范围和生命周期。JavaScript有全局作用域、函数作用域和块级作用域。
闭包是指函数能够访问并记住其词法作用域中的变量,即使函数在其作用域外执行。闭包的形成需要两个条件:
- 函数嵌套
- 内部函数引用外部函数的变量
function createCounter() {
let count = 0; // 闭包保护的变量
return function() {
count++; // 内部函数引用外部变量
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
闭包代码输出分析
function createFunctions() {
var result = [];
for (var i = 0; i < 3; i++) {
result[i] = function() {
return i;
};
}
return result;
}
var functions = createFunctions();
console.log(functions); // 输出3
console.log(functions); // 输出3
console.log(functions); // 输出3
问题在于使用var声明的变量没有块级作用域,所有函数共享同一个变量i。循环结束后i的值为3。
解决方案:
- 使用IIFE创建闭包
for (var i = 0; i < 3; i++) {
result[i] = (function(num) {
return function() {
return num;
};
})(i);
}
- 使用let声明块级作用域变量
for (let i = 0; i < 3; i++) {
result[i] = function() {
return i;
};
}
6. React相关问题
变量声明方式区别
var a = 1; // 函数作用域,可重复声明,可重新赋值
let a = 1; // 块级作用域,不可重复声明,可重新赋值
const a = 1; // 块级作用域,不可重复声明,不可重新赋值
const [a, setA] = useState(1); // React状态,值变化触发重新渲染
const a = useRef(1).current; // React引用,值变化不触发重新渲染
useState和useRef区别
| 特性 | useState | useRef |
|---|---|---|
| 触发渲染 | 值变化触发组件重新渲染 | 值变化不触发重新渲染 |
| 返回值 | 返回当前状态和更新函数 | 返回可变ref对象 |
| 适用场景 | 需要反映在UI上的数据 | 不需要触发渲染的可变值 |
| 持久性 | 重新渲染时保持状态 | 组件生命周期内保持引用 |
useRef更新是否会触发重新渲染
useRef().current更新后不会触发组件重新渲染。这是useRef与useState的主要区别之一。
触发组件重新渲染的因素
- 状态更新:useState/useReducer返回的更新函数被调用
- 父组件重新渲染:父组件渲染会导致所有子组件重新渲染
- Props变化:组件接收的props发生变化
- Context更新:组件订阅的Context值发生变化
- Force update:使用forceUpdate方法强制更新
7. 城市下拉框实现
下面是使用React实现的城市下拉框组件:
import React, { useState } from 'react';
// 城市数据
const cityData = {
'北京市': ['东城区', '西城区', '朝阳区', '丰台区', '石景山区', '海淀区', '门头沟区', '房山区', '通州区', '顺义区', '昌平区', '大兴区', '怀柔区', '平谷区', '密云区', '延庆区'],
'上海市': ['黄浦区', '徐汇区', '长宁区', '静安区', '普陀区', '虹口区', '杨浦区', '闵行区', '宝山区', '嘉定区', '浦东新区', '金山区', '松江区', '青浦区', '奉贤区', '崇明区'],
'广州市': ['荔湾区', '越秀区', '海珠区', '天河区', '白云区', '黄埔区', '番禺区', '花都区', '南沙区', '从化区', '增城区'],
'深圳市': ['罗湖区', '福田区', '南山区', '宝安区', '龙岗区', '盐田区', '龙华区', '坪山区', '光明区', '大鹏新区']
};
const CityDistrictSelector = () => {
const [selectedCity, setSelectedCity] = useState('');
const [districts, setDistricts] = useState([]);
// 处理城市选择变化
const handleCityChange = (event) => {
const city = event.target.value;
setSelectedCity(city);
setDistricts(cityData[city] || []);
};
return (
<div className="city-selector">
<h2>城市区域选择器</h2>
<div className="select-group">
<label htmlFor="city-select">选择城市:</label>
<select
id="city-select"
value={selectedCity}
onChange={handleCityChange}
className="city-dropdown"
>
<option value="">请选择城市</option>
{Object.keys(cityData).map(city => (
<option key={city} value={city}>{city}</option>
))}
</select>
</div>
{selectedCity && (
<div className="select-group">
<label htmlFor="district-select">选择区域:</label>
<select
id="district-select"
className="district-dropdown"
disabled={districts.length === 0}
>
<option value="">请选择区域</option>
{districts.map(district => (
<option key={district} value={district}>{district}</option>
))}
</select>
</div>
)}
<style jsx>{`
.city-selector {
max-width: 400px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
h2 {
margin-top: 0;
color: #333;
text-align: center;
}
.select-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #555;
}
select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
background-color: white;
}
select:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
select:disabled {
background-color: #f8f9fa;
color: #6c757d;
}
`}</style>
</div>
);
};
export default CityDistrictSelector;
这个组件实现了:
- 城市选择下拉框,数据来自预定义的城市数据
- 根据选择的城市动态加载对应的区域数据
- 区域选择下拉框,只在选择城市后启用
- 简洁的UI样式和良好的用户体验
您可以将此组件集成到您的React应用中,或根据需求进一步扩展功能,如添加默认值、表单验证等。

针对你的前端面试准备,我结合简历中提到的项目经验和技术栈,整理了以下详细解答。这些答案不仅解释了概念和原理,还融入了实际应用场景和最佳实践,希望能帮助你更好地展示技能。
🧩 前端核心概念详解
1. Iframe 传参
iframe 传参主要有以下几种方式,根据同域/跨域场景选择:
-
URL 查询参数:适用于简单数据传递,通过
src属性传递参数,子页面通过URLSearchParams解析。<!-- 父页面 --> <iframe src="child.html?param1=value1¶m2=value2"></iframe> <!-- 子页面 --> <script> const params = new URLSearchParams(window.location.search); console.log(params.get('param1')); // 'value1' </script>优点是实现简单、兼容性好;缺点是参数暴露在URL中,长度受限,且只能单向传递。
-
window.postMessage:推荐用于跨域通信,允许安全地跨源传递消息。// 父页面发送消息 iframe.contentWindow.postMessage({ key: 'value' }, 'https://子页面源'); // 子页面接收消息 window.addEventListener('message', (event) => { // 安全验证:检查消息来源 if (event.origin !== 'https://父页面源') return; console.log(event.data); // { key: 'value' } });优点是支持跨域、可传递复杂对象;缺点是需要验证
event.origin以防止安全风险。 -
其他方法:同域下还可通过
window.parent直接访问父窗口属性,或使用localStorage/sessionStorage配合storage事件进行通信。
🔒 安全注意:使用 postMessage 时务必验证 event.origin,防止数据被恶意站点截获。跨域 iframe 若无法通信,可能是目标页面设置了 X-Frame-Options: DENY 或 SAMEORIGIN 阻止嵌入。
结合简历:在得物实习的“实时预览”功能中, likely 采用了 postMessage 进行父子页面通信,传递状态或配置数据,确保了跨域环境下的安全数据交换。
2. Pinia 原理
Pinia 是 Vue 官方推荐的状态管理库,其核心原理基于 Vue 3 的响应式系统:
-
核心机制:
- State:通过
reactive()或ref()创建响应式状态。 - Getters:利用
computed()实现计算属性,依赖状态自动更新。 - Actions:普通函数,可直接修改
state(同步或异步),无需mutation。 - Store 管理:通过
defineStore定义并注册唯一 Store,底层通过 Map 管理单例。
- State:通过
-
简易实现:
import { reactive, computed } from 'vue'; const stores = new Map(); function defineStore(id, setup) { return function useStore() { if (!stores.has(id)) { stores.set(id, setup()); } return stores.get(id); }; } // 使用 const useCounter = defineStore('counter', () => { const state = reactive({ count: 0 }); const double = computed(() => state.count * 2); const increment = () => { state.count++; }; return { state, double, increment }; }); -
与 Vuex 区别:
特性 Vuex Pinia API 风格 Options API Composition API Mutations 必需(同步) 无,直接修改 state 模块化 嵌套模块 扁平化独立 Store TypeScript 支持较复杂 原生支持更好 体积 较大 轻量(约 1KB)
结合简历:在灾害管理系统等 Vue 项目中,你通过 Pinia 管理全局状态(如用户信息、表单数据),利用其类型推断和简洁 API 提升开发效率。
3. 装饰器(Decorator)与 AOP
-
装饰器(Decorator):是一种特殊类型的声明,用于扩展类或方法的功能,提供一种灵活的方式添加额外行为。
function log(target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function (...args: any[]) { console.log(`调用 ${key} 方法,参数: ${JSON.stringify(args)}`); return originalMethod.apply(this, args); }; return descriptor; } class Example { @log greet(name: string) { return `Hello, ${name}!`; } } -
AOP(Aspect-Oriented Programming,面向切面编程):是一种编程范式,旨在将横切关注点(如日志、权限、性能监控)从业务逻辑中分离,使代码更模块化、易于维护。装饰器是实现 AOP 的常见方式。
-
使用场景:
- 日志记录:记录方法调用参数、返回值。
- 权限控制:在方法执行前验证用户权限。
- 性能监控:测量方法执行时间。
- 异常处理:统一捕获和处理异常。
- 缓存:缓存方法结果,提升性能。
结合简历:在博客或管理系统中,你可能使用装饰器进行请求封装(如自动添加 Token)、权限校验或性能打点,实现关注点分离。
4. Object.create 使用场景
Object.create(proto, propertiesObject) 用于创建一个新对象,并指定其原型:
-
原型继承:实现纯净的原型链继承。
const parent = { a: 1 }; const child = Object.create(parent); // child.__proto__ === parent console.log(child.a); // 1 (通过原型链访问) -
创建无原型对象:避免原型链污染(如用于 Map 替代品)。
const obj = Object.create(null); // obj.__proto__ === undefined obj.key = 'value'; // console.log(obj.toString); // undefined (无 Object 方法) -
属性描述符定义:创建属性时控制
writable/enumerable/configurable。const obj = Object.create(null, { key: { value: 'static', writable: false, // 不可写 enumerable: true // 可枚举 } });
结合简历:在工具库或框架开发中,你可能用其实现继承或创建纯净配置对象。
5. Object.freeze 深度冻结与 Hook
-
原理:
Object.freeze()浅冻结对象,阻止添加、删除、修改属性(非严格模式静默失败,严格模式抛错)。const obj = { a: 1, b: { c: 2 } }; Object.freeze(obj); obj.a = 2; // 失败(严格模式报错) obj.b.c = 3; // 成功(内部对象未冻结) -
深度冻结:需递归冻结所有嵌套对象。
function deepFreeze(obj) { Object.freeze(obj); for (const key in obj) { if (obj.hasOwnProperty(key) && typeof obj[key] === 'object') { deepFreeze(obj[key]); } } } -
Freeze Hook:通过 Proxy 拦截 set/delete 操作,实现修改警告或阻止。
function createFreezeHook(obj) { return new Proxy(obj, { set(target, key, value) { console.warn(`Cannot set ${key} to ${value}: Object is frozen.`); return true; // 或 false (严格模式报错) }, deleteProperty(target, key) { console.warn(`Cannot delete ${key}: Object is frozen.`); return true; } }); } const frozen = createFreezeHook(Object.freeze({ a: 1 }));
结合简历:在状态管理或常量配置中,你可能使用冻结确保数据不可变。
6. public、protected、private 区别
这些是 TypeScript/ES Next 中的访问修饰符,用于控制类成员的可见性:
| 修饰符 | 访问权限 | 示例代码 |
|---|---|---|
public | 任意位置可访问(默认) | class A { public x = 1; } |
protected | 仅类内部和子类可访问 | class B { protected y = 2; } |
private | 仅类内部可访问(编译后仍可访问) | class C { private z = 3; } |
- TS 编译后:
private仅编译时检查,运行时仍可通过obj['key']访问。 - ECMAScript 私有字段:使用
#field语法(运行时真正私有)。class Example { #privateField = 'secret'; getPrivate() { return this.#privateField; } }
结合简历:在大型 TypeScript 项目中,你常用这些修饰符封装组件类或工具库。
7. Drag API 原理
HTML5 Drag and Drop API 基于事件驱动:
-
核心事件:
事件 触发时机 dragstart开始拖动时(在拖动元素上) drag拖动过程中 dragenter进入目标区域时(在目标上) dragover在目标区域上移动时 dragleave离开目标区域时 drop在目标区域释放时 dragend拖动结束时(在拖动元素上) -
数据传输:通过
dataTransfer对象设置/获取数据。// 拖动源 element.addEventListener('dragstart', (event) => { event.dataTransfer.setData('text/plain', 'Hello World'); event.dataTransfer.effectAllowed = 'copy'; }); // 放置目标 target.addEventListener('drop', (event) => { event.preventDefault(); const data = event.dataTransfer.getData('text/plain'); console.log(data); // 'Hello World' }); target.addEventListener('dragover', (event) => event.preventDefault()); // 需阻止默认行为以允许放置
结合简历:在 BPMN 流程设计器或可视化工具中,你可能使用 Drag API 实现节点拖拽布局。
8. Grid 布局 vs Flex 布局
| 特性 | Grid(二维布局) | Flex(一维布局) |
|---|---|---|
| 维度 | 行和列(同时控制) | 单行或单列(主轴方向) |
| 适用场景 | 整体页面布局、卡片网格 | 组件内元素排列、导航栏 |
| 容器属性 | grid-template-rows/columns | flex-direction, flex-wrap |
| 项目属性 | grid-row, grid-column | flex-grow, flex-shrink |
-
Grid 示例:
.container { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-gap: 10px; } .item { grid-column: 1 / 3; } /* 跨两列 */ -
Flex 示例:
.container { display: flex; justify-content: space-between; } .item { flex: 1; }
结合简历:在响应式项目中,你可能混合使用两者(Grid 用于整体结构,Flex 用于组件内部)。
9. 权限设计
前端权限设计通常基于 RBAC(Role-Based Access Control)模型:
-
路由权限:动态注册路由(如 Vue Router)。
// 过滤异步路由表 const asyncRoutes = [/* 所有路由定义 */]; const allowedRoutes = asyncRoutes.filter(route => { return user.roles.some(role => route.meta?.roles?.includes(role)); }); router.addRoutes(allowedRoutes); -
组件/按钮权限:通过自定义指令或包装组件控制。
<template> <button v-permission="'user:delete'">删除用户</button> </template> // 指令实现 Vue.directive('permission', { inserted(el, binding) { const { value } = binding; const permissions = store.getters.permissions; if (!permissions.includes(value)) { el.parentNode?.removeChild(el); } } });
结合简历:在灾害管理系统中,你实现了 RBAC 动态路由和按钮级权限控制,确保用户仅访问授权功能。
10. 表单加密解密
-
传输加密:使用 HTTPS 协议保障传输安全。对敏感字段(如密码)可额外使用 AES 等算法加密。
import CryptoJS from 'crypto-js'; const key = '密钥'; const encrypted = CryptoJS.AES.encrypt('明文数据', key).toString(); // 发送加密后数据 -
前端解密:通常由后端返回加密数据,前端解密(较少用)。
const bytes = CryptoJS.AES.decrypt(encryptedData, key); const decrypted = bytes.toString(CryptoJS.enc.Utf8);
🔒 安全建议:密钥需安全管理(避免硬编码),敏感操作应在后端处理。
结合简历:在金融或管理系统中,你可能对接加密接口或处理敏感数据加密。
11. 上拉加载原理
通过监听滚动事件判断触底,加载更多数据:
-
滚动监听:
window.addEventListener('scroll', () => { const { scrollTop, clientHeight, scrollHeight } = document.documentElement; if (scrollTop + clientHeight >= scrollHeight - threshold) { loadMore(); // 加载下一页 } }); -
性能优化:
- 防抖:避免频繁触发。
- Intersection Observer:现代浏览器推荐 API。
const observer = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { loadMore(); } }); observer.observe(document.getElementById('footer'));
结合简历:在得物商品列表或博客文章列表,你可能实现过分页或无限滚动。
12. 大文件分片上传
将大文件切片,并行上传,支持断点续传:
-
前端切片:
const chunkSize = 2 * 1024 * 1024; // 2MB/片 const chunks = []; for (let i = 0; i < file.size; i += chunkSize) { chunks.push(file.slice(i, i + chunkSize)); } -
上传与合并:
- 为每个切片生成哈希(如 SparkMD5),用于标识文件和服务端去重。
- 并发上传切片(控制并发数)。
- 服务端接收所有切片后合并文件。
-
断点续传:服务端记录已上传切片,前端跳过已传切片。
结合简历:在灾害管理系统中,你实现了大文件分片上传、秒传和断点续传功能。
13. Lodash 与 Tree-shaking
-
Lodash 支持 Tree-shaking:需使用 lodash-es(ES Modules 版本)或按需导入。
import debounce from 'lodash-es/debounce'; // 支持 shaking // 或 import { debounce } from 'lodash-es'; -
Tree-shaking 原理:
- 基于 ES Modules 的静态分析(编译时确定依赖)。
- Webpack/Rollup 等工具标记未导出代码,并在打包时移除。
-
为什么默认 lodash 不支持:传统版本为 CommonJS 格式(动态引用,无法静态分析)。
结合简历:在项目优化中,你通过配置 Tree-shaking 和代码分割减少打包体积。
14. 媒体查询原理
CSS 媒体查询基于设备特性(如宽度、分辨率)应用样式:
-
原理:浏览器监听视口变化,匹配条件时应用对应样式。
/* 移动端优先 */ .container { padding: 10px; } @media (min-width: 768px) { .container { padding: 20px; } /* 平板 */ } @media (min-width: 1024px) { .container { padding: 30px; } /* 桌面 */ } -
常见查询:
width/height:视口尺寸。orientation:横屏/竖屏。resolution:设备像素比。
结合简历:在响应式项目中,你常用媒体查询适配多端设备。
15. TS 中抽象类和抽象方法
- 抽象类:用
abstract声明,不能实例化,只能被继承。 - 抽象方法:在抽象类中声明,必须由子类实现。
abstract class Animal { abstract makeSound(): void; // 抽象方法 move() { console.log('Moving...'); } // 具体方法 } class Dog extends Animal { makeSound() { console.log('Woof!'); } // 必须实现 } // const a = new Animal(); // 错误:无法实例化抽象类 const d = new Dog(); d.makeSound(); // 'Woof!'
结合简历:在大型 TypeScript 项目中,你使用抽象类定义通用接口或基础组件。
16. 找字符串中第一次出现的非重复字符
function firstNonRepeatingChar(str) {
const map = new Map();
// 第一次遍历:统计字符出现次数
for (let char of str) {
map.set(char, (map.get(char) || 0) + 1);
}
// 第二次遍历:找到第一个计数为1的字符
for (let char of str) {
if (map.get(char) === 1) return char;
}
return null; // 无非重复字符
}
// 示例:
console.log(firstNonRepeatingChar("swiss")); // "w"
console.log(firstNonRepeatingChar("aabb")); // null
时间复杂度:O(n)(两次遍历)。
空间复杂度:O(n)(Map 存储)。
根据你的简历内容,可以看出你在前端开发、工程化、性能优化和全栈方面有扎实的经验。以下是对每个面试问题的详细解答,并结合了你的项目和实践经验进行说明:
1. iframe 传参
方式:
- 通过 URL 查询参数传递(简单数据)
- 使用
postMessage进行跨域通信(安全且支持复杂数据) - 通过
window.name或document.domain(较少用)
结合简历:你在得物实习中使用了 Iframe 实现实时预览, likely 使用 postMessage 进行父子页面通信,传递状态或配置数据。
2. Pinia 原理
Pinia 是 Vue 状态管理库,基于 Composition API:
- 使用
reactive创建响应式 store - 提供
patch批量更新、DevTools 集成、插件系统 - 支持服务端渲染(SSR)
结合简历:你在灾害管理系统使用 Vue,可能接触过 Vuex/Pinia 进行状态管理。
3. 装饰器 Decorator 使用场景?什么是 AOP?
- Decorator:用于扩展类或方法的功能(如日志、权限校验)
- AOP(面向切面编程):将横切关注点(如日志、事务)从业务逻辑中分离
使用场景:
- 日志记录
- 权限控制
- 性能监控
结合简历:你在博客项目中可能使用装饰器进行请求封装或权限校验。
4. Object.create 使用场景?
用于创建一个新对象,并指定原型:
const parent = { a: 1 };
const child = Object.create(parent);
使用场景:原型继承、创建纯净对象(无原型链污染)
5. Object.freeze 原理?深层对象可 freeze 吗?如何修改?
- 原理:冻结对象,禁止添加/删除/修改属性(浅冻结)
- 深层冻结:需递归冻结嵌套对象
- 修改冻结对象:无法直接修改,可通过代理(Proxy)或解冻副本实现
Hook 实现:可基于 Proxy 拦截 set/delete 操作并提示警告。
6. public、protected、private 区别
public:任意访问protected:仅类和子类可访问private:仅类内部可访问
TS/JS 中:通过 TypeScript 或符号(Symbol)实现。
7. Drag API 原理
基于 HTML5 Drag and Drop API:
draggable="true"- 事件:
dragstart,dragend,dragover,drop - 使用
dataTransfer传递数据
结合简历:可能在可视化项目中使用拖拽交互(如 BPMN 流程设计器)。
8. Grid 布局 vs Flex 布局
- Flex:一维布局(行或列)
- Grid:二维布局(行和列)
- 选择:简单线性布局用 Flex,复杂网格用 Grid
简历关联:你熟悉响应式布局, likely 两者都使用过。
9. 权限设计
常见方案:
- RBAC(基于角色的访问控制)
- 前端路由动态注册 + 按钮级权限(自定义指令)
结合简历:你在灾害管理系统中实现了 RBAC 和动态路由,按钮级权限控制。
10. 表单加密解密
- 加密:使用 HTTPS + 加密算法(如 AES)加密敏感数据
- 解密:后端解密处理
最佳实践:全程 HTTPS,敏感字段额外加密。
11. 上拉加载原理
监听滚动事件,判断触底:
window.addEventListener('scroll', () => {
if (scrollTop + clientHeight >= scrollHeight - threshold) {
loadMore();
}
});
优化:防抖 + Intersection Observer API
12. 大文件分片上传
- 前端分片(Blob.slice)
- 并发上传(Promise.all)
- 服务端合并
- 断点续传(基于文件哈希)
结合简历:你在灾害管理系统中实现了大文件分片上传、秒传、断点续传。
13. Lodash 支持 Tree-shaking 吗?为什么?原理是什么?
- 支持:Lodash-es 或模块化导入方式支持 Tree-shaking
- 原理:ES Module 静态分析,移除未引用代码
- 为什么:传统 Lodash 是 CommonJS,无法静态分析
简历关联:你在项目中优化构建配置(Tree Shaking、代码分割)。
14. 媒体查询原理
基于 CSS3 @media,根据设备特性(如宽度、分辨率)应用样式:
@media (max-width: 768px) { ... }
浏览器监听视口变化,动态应用样式。
15. TS 中抽象类和抽象方法
- 抽象类:不能实例化,只能继承
- 抽象方法:必须由子类实现
abstract class Animal {
abstract makeSound(): void;
}
class Dog extends Animal {
makeSound() { ... }
}
16. 代码题:找字符串中第一次出现的非重复字符
function firstNonRepeatingChar(str) {
const map = new Map();
for (let char of str) {
map.set(char, (map.get(char) || 0) + 1);
}
for (let char of str) {
if (map.get(char) === 1) return char;
}
return null;
}
// 示例:firstNonRepeatingChar("swiss") -> "w"
总结回答风格建议:
- 结合简历中提到的技术(如 Vue/React、工程化、性能优化)举例说明
- 突出你的实战经验(如得物实习、国家重点研发项目)
- 简洁清晰,直击要点
538

被折叠的 条评论
为什么被折叠?



