一、手写代码(别背,得能跑)
1. 实现一个防抖/节流(必考)
javascript
// 防抖:用户停止操作后再执行
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// 节流:固定时间执行一次(滚动、拖拽)
function throttle(fn, delay) {
let last = 0;
return function(...args) {
const now = Date.now();
if (now - last >= delay) {
last = now;
fn.apply(this, args);
}
};
}
面试官常问:如果用户一直点,最后一次必须执行吗?防抖的立即执行版本怎么改?节流最后一次漏掉怎么办?(用时间戳+定时器双保险)
2. 模拟实现 Promise.all
javascript
function myPromiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'));
}
let results = [];
let completed = 0;
if (promises.length === 0) return resolve(results);
promises.forEach((p, idx) => {
Promise.resolve(p).then(val => {
results[idx] = val;
completed++;
if (completed === promises.length) resolve(results);
}).catch(err => reject(err));
});
});
}
实战坑:遇到非 Promise 值直接包裹;空数组立即返回;捕获错误时 reject。
- 深拷贝(考虑循环引用、特殊类型)
javascript
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (hash.has(obj)) return hash.get(obj);
let clone = Array.isArray(obj) ? [] : {};
hash.set(obj, clone);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key], hash);
}
}
return clone;
}
面试官追问:Date/RegExp/函数怎么处理?怎么支持 Symbol 键?------增加类型判断分支。
二、实战场景题(都是血泪史)
- 一个后台管理页面,列表数据多,每次点查询都卡死,怎么办?
真实原因:你后端返回了全量数据,前端直接渲染,页面 DOM 节点上万。
实战方案:
· 先找后端加 page size 参数,换分页(最简单)
· 如果必须前端分页,用虚拟列表(只渲染可视区)
· 如果只是数据量大、渲染慢,用 requestAnimationFrame 分帧渲染,避免主线程长时间阻塞
- 用户反馈:手机 H5 页面点了没反应,咋排查?
真实场景:移动端 300ms 延迟?或者页面有透明层挡住了?
排查步骤:
-
手机连接电脑,打开 Chrome DevTools → Remote Devices 查看元素是否被覆盖
-
检查 touch 和 click 是否混用,是否忘记 preventDefault
-
检查 CSS pointer-events: none 有没有误设
-
如果用了 FastClick 库,确认版本兼容
- 上线后,发现某个 JS 文件报错,但本地无法复现,怎么快速定位?
实战流程:
-
查看错误监控平台(Sentry/自己写的)报错堆栈,定位到文件行号
-
去线上 CDN 下载对应版本的 sourcemap(上线时一定要上传),通过 sourcemap 映射回源码
-
如果没 sourcemap,用 console 埋点 + 用户设备信息(浏览器、系统、网络环境)猜测复现场景
-
实在不行,加 debug 日志,灰度发布给一小部分用户,看日志输出
- 某个 React 组件频繁重渲染,怎么定位?
真实案例:父组件状态更新,所有子组件都跟着重绘,页面卡。
定位:
· 安装 React DevTools → 勾选 Highlight updates,看哪些组件变黄
· 检查子组件是否用 React.memo 包裹
· 父组件传 props 时,是否每次都新创建对象/函数,用 useCallback 缓存
· 如果用了 Redux,确认 selector 是否每次返回新对象(用 reselect 做缓存)
三、性能优化(不只是压缩图片)
- 首屏时间从 3s 优化到 1s 的实操
真实优化项(按优先级):
· 代码分割:路由懒加载,React.lazy + Suspense
· 预加载: 关键资源提前加载
· 图片:WebP + 响应式图片 + 懒加载
· 字体:用 font-display: swap 避免白屏
· 服务端渲染:如果是内容型网站,上 SSR 或静态生成(Next.js)
· CDN 预热:将核心资源提前推到边缘节点
- 首屏出现大面积白屏,怎么抢救?
真实场景:移动端弱网下,JS 没加载完,页面啥也没有。
方案:
· 内联关键 CSS(首屏样式)
· 加骨架屏(Skeleton),用户看到占位,心理上觉得快了
· 把首屏需要的数据请求提前,放在 head 里 数据接口
· 用 async/defer 控制 JS 加载顺序,不让它阻塞渲染
四、工程化与构建(Webpack/Rspack)
- 线上代码压缩后,报错如何还原?
必备技能:
· 确保构建时生成 .map 文件,但不要部署到线上(防止源码泄露)
· 上传到错误监控平台(Sentry/自建),平台会自动用 sourcemap 解析
· 手动还原:用 source-map 包或 Chrome 的 "Add Source Map" 功能
- Webpack 构建慢,从哪里下手?
实战排查:
· 用 speed-measure-webpack-plugin 看哪个 loader/plugin 耗时
· 排除不必要文件的解析(exclude: /node_modules/)
· 用 thread-loader 多进程
· 缓存:cache: { type: 'filesystem' }
· 换 Rspack,兼容 Webpack 大部分配置,速度能快 5-10 倍
五、算法(面试必考)
- 翻转二叉树(考察递归/迭代)
javascript
function invertTree(root) {
if (!root) return null;
[root.left, root.right] = [invertTree(root.right), invertTree(root.left)];
return root;
}
- 实现一个 URL 解析函数
javascript
function parseQuery(url) {
const search = url.split('?')[1];
if (!search) return {};
const params = new URLSearchParams(search);
const result = {};
for (let [key, val] of params.entries()) {
if (result[key]) {
result[key] = Array.isArray(result[key]) ? [...result[key], val] : [result[key], val];
} else {
result[key] = val;
}
}
return result;
}
注意:处理重复参数(?a=1&a=2)和中文编码(decodeURIComponent)
六、综合大厂真题(场景)
- 设计一个多端适配的登录弹窗组件
考察点:组件封装、样式适配、状态管理
思路:
· 用 Portal 渲染到 body
· 支持函数式调用:Modal.confirm({ title, content, onOk })
· 移动端和 PC 端样式不同(通过媒体查询或 React 检测 UA 切换)
· 关闭时销毁 DOM,避免内存泄露
- 扫码登录的原理与前端实现
回答框架:
· 生成唯一二维码(包含 ticket/token)
· 前端轮询后端接口,询问该 ticket 是否已扫码确认
· 移动端扫码后,调用后端确认接口,后端标记 ticket 已使用
· 前端轮询到确认,获取登录态 token,跳转
· 优化:用 WebSocket 代替轮询,但长连接开销大,看场景
七、最后的面试技巧(真实经验)
· 手写题:先写注释说明思路,再写代码,如果卡壳,讲清楚你卡在哪一步,比硬写要好。
· 项目细节:面试官必问"这个项目最难的点是什么?"------不要说"技术难点",要说"业务与技术的结合点",比如"数据量大导致前端卡,我用了虚拟列表 + 分帧渲染,解决了体验问题"。
· 反问环节:不要问"加班多吗""贵司做什么业务",可以问"这个岗位目前最大的挑战是什么""团队在微前端方面有什么实践"
- 项目结构设计与模块化
题目:请分析本项目(辰驭汽车官网)的文件组织方式。你认为这样的结构有哪些优点?如果项目规模扩大,你会如何改进?
参考答案
· 优点:将 index.html 放在根目录,css/、js/ 分别存放样式与脚本,models/
下按车型独立存储详情页,结构清晰,便于维护和定位。 · 改进点: · 当车型增多时,models/ 下文件会急剧膨胀,可引入构建工具(如
Vite)将详情页模板化,通过路由动态加载配置数据,避免重复 HTML。 · 将车型配置数据抽离为单独的 JSON 文件或
API,前端统一渲染,降低维护成本。 · 添加 components/ 目录存放可复用的头部、底部、卡片等组件,减少重复代码。
- 粒子背景的实现与性能考量
题目:首页使用了粒子背景(代码中提及 #jsi-particle-container),请说明粒子动画的一般实现原理。如果粒子数量过多(例如超过 500 个),如何保证页面流畅?
参考答案
· 实现原理:通常使用 Canvas 或 WebGL,通过 requestAnimationFrame 循环更新粒子位置并绘制。粒子位置变化、边界碰撞检测、鼠标交互等都是常见需求。
· 性能优化:
· 使用 requestAnimationFrame 代替 setInterval,与屏幕刷新率同步。
· 减少粒子数量,或根据设备性能动态调整数量(如检测帧率)。
· 离屏 Canvas 缓存静态背景。
· 使用 will-change 提示浏览器优化。
· 若使用第三方库(如 three.js),注意避免每帧创建新对象。
· 本项目中可进一步将粒子脚本独立为单独文件,避免阻塞首屏渲染。
- 车型列表的动态渲染
题目:主页的车型卡片是通过 JavaScript 动态生成的,请说明这种做法的好处和潜在问题。如果你来重构,会采用什么方式保证数据和视图的同步?
参考答案
· 好处:
· 便于维护车型数据,新增车型只需修改数据数组,无需改动 HTML。
· 可轻松实现按类别(轿车/SUV/MPV)分组渲染。
· 潜在问题:
· SEO 不友好(爬虫可能无法执行 JS 获取内容)。
· 首次加载时可能出现短暂白屏或内容闪烁。
· 若数据量极大,一次性渲染大量 DOM 可能卡顿。
· 改进建议:
· 对于 SEO 关键页面,采用服务端渲染或预渲染(如 SSG)。
· 可使用前端框架(Vue/React)配合虚拟 DOM 优化渲染性能,并支持数据响应式更新。
· 将车型数据从内嵌改为异步请求(fetch),利于后续与后台对接。
- 详情页配置切换(Tabs)的实现
题目:车型详情页(如 sagitta.html)中通过 JavaScript 动态生成配置选项卡并切换内容。请解释实现思路,并说明如何确保不同配置的数据准确对应。
参考答案
· 实现思路:
· 定义 configs 数组,每个元素代表一个配置版本(含名称、动力、价格、选装项等)。
· 页面加载时,遍历 configs 生成选项卡按钮,并为每个按钮绑定点击事件。
· 点击某个按钮时,高亮当前按钮,并调用 renderDetail 函数,根据该配置数据刷新右侧详情区域。
· 数据对应准确性:
· 通过数组索引或唯一 ID 绑定,确保渲染时使用的是正确的配置对象。
· 在 renderDetail 中直接传入对应的配置对象,避免通过 DOM 属性传递数据(减少字符串解析错误)。
· 扩展性:若配置数据来自后端,可设计统一的 API 返回结构,前端只需根据 configId 请求详情并渲染。
- 响应式布局与适配
题目:项目使用了媒体查询实现移动端适配(如 @media (max-width: 800px))。请简述响应式设计的核心原则,并指出该项目在移动端可能存在的改进点。
参考答案
· 核心原则:
· 使用相对单位(rem/em/vw/vh)而非固定像素。
· 设置视口 meta 标签 。
· 通过媒体查询针对不同断点调整布局(如列数、字体大小、边距)。
· 项目改进点:
· 当前媒体查询只调整了容器内边距和卡片列数,可进一步优化:
· 导航栏改为汉堡菜单,折叠项。
· 详情页的选项卡改为横向滚动,避免换行。
· 价格和配置信息用更紧凑的布局展示。
· 触摸目标(按钮)大小应不小于 44×44 像素,提升易用性。
- 代码复用与组件化
题目:详情页模板中,每个车型都需要单独编写一份 HTML,并嵌入对应的配置数据。如何优化这种重复劳动?如果让你用现代前端框架(如 React)重写,你会如何设计组件?
参考答案
· 优化思路:
· 将详情页结构抽象为模板,通过构建工具(如 webpack、Vite)在编译时注入不同车型数据,生成静态文件。
· 或将配置数据存储为 JSON,前端动态请求并渲染,这样只需一个 detail.html 页面,通过 URL 参数 ?car=sagitta 加载对应数据。
· React 组件设计:
· 创建 CarDetail 组件,接收 carId 作为 prop,内部使用 useEffect 获取配置数据,渲染选项卡和详情。
· 将配置选项卡拆分为独立子组件 ConfigTabs,详情区域为 ConfigDetail,实现关注点分离。
· 数据层可使用 Context 或状态管理库(如 Zustand)缓存车型数据,避免重复请求。
- 交互与用户体验优化
题目:卡片悬停时有轻微上浮和边框颜色变化的效果。请说明这类交互动效的意义,并列举其他可提升用户体验的交互细节。
参考答案
· 意义:
· 提供即时视觉反馈,让用户感知元素是可交互的,提升操作确定感。
· 轻微动效增加高级感,符合汽车品牌调性。
· 其他优化细节:
· 为按钮添加 :active 状态,模拟按压效果。
· 图片懒加载(详情页车型图片),减少首屏加载。
· 骨架屏(Skeleton)替代加载时的白屏。
· 页面切换时添加过渡动画,避免生硬跳转。
· 键盘可访问性(Tab 键导航焦点样式)。
- 性能优化建议
题目:假设网站上线后,发现首屏加载速度较慢,请基于当前代码给出至少三条优化建议。
参考答案
-
图片优化:项目中未包含车型图片,但实际应使用 WebP 格式、响应式图片(srcset),并懒加载非首屏图片。
-
CSS/JS 优化:
· 将关键 CSS 内联到 ,非关键样式异步加载。
· 使用 defer 或 async 加载非核心 JavaScript。
· 压缩和合并静态资源。
-
减少渲染阻塞:
· 粒子背景脚本(Canvas 绘制)可延迟初始化,或仅在用户交互后才启动。
· 避免使用昂贵的 CSS 属性(如 box-shadow 大量使用)导致重绘。
-
利用缓存:设置合理的 HTTP 缓存头,对字体、框架库等静态资源长期缓存。
-
预加载关键资源:使用 预加载首屏必需的字体或图片。
- 数据管理与可维护性
题目:当前车型配置数据散落在各详情页的 JavaScript 中,修改价格或配置需要改动多个文件。请提出一种集中管理数据的方案。
参考答案
· 集中管理方案:
· 创建 data/cars.json 文件,定义所有车型的配置信息(包括每种配置的详细参数)。
· 在主页和详情页中统一通过 fetch 加载该 JSON,动态生成内容。
· 优点:
· 一处修改,全局生效。
· 便于接入后台 CMS 系统,实现数据与视图分离。
· 减少重复代码,降低维护出错概率。
· 实现细节:
· 主页动态加载 JSON 后渲染卡片。
· 详情页通过 URL 参数(如 ?car=sagitta)筛选对应车型数据,动态渲染配置选项卡。
· 利用浏览器缓存或 Service Worker 缓存 JSON,减少网络请求。
- 跨浏览器兼容性
题目:项目使用了 backdrop-filter: blur() 和 CSS Grid 等现代特性,如何确保在不支持这些特性的浏览器(如旧版 IE)上正常显示?
参考答案
· 渐进增强/优雅降级:
· 对于不支持 backdrop-filter 的浏览器,可提供纯色背景备选方案(通过 @supports 检测)。
· 使用 Autoprefixer 自动添加浏览器前缀。
· 对于 CSS Grid,可准备 Flexbox 回退布局(例如使用 @supports not (display: grid) 定义备用样式)。
· polyfill:对于 JS 新特性(如 Promise、fetch),可引入 polyfill 库(如 core-js)或使用 Babel 转换。
· 测试:通过 BrowserStack 或真实设备进行兼容性测试,确保核心功能(车型查看、配置切换)在主流浏览器上可用。
- 安全考虑
题目:项目中使用了内联脚本和动态生成 HTML,是否存在 XSS 风险?如何防范?
参考答案
· 风险分析:
· 车型数据由前端数组直接渲染,若数据源被篡改(或用户输入被注入恶意脚本),使用 innerHTML 或 insertAdjacentHTML 可能导致 XSS。
· 防范措施:
· 使用 textContent 或 innerText 替代 innerHTML 插入用户可控内容。
· 若必须插入 HTML,先对数据进行转义(如替换 < 为 <)。
· 利用现代框架(如 React 的 JSX)默认转义,降低风险。
· 避免在 URL 参数中直接拼接到 innerHTML,使用 encodeURIComponent 处理。
· 额外建议:
· 设置 CSP(内容安全策略)头,限制脚本来源。
· 依赖第三方库(如 jQuery)时确保版本无已知漏洞。
- 版本控制与协作
题目:如果你将该项目提交到 Git 仓库,你会如何编写 .gitignore 文件?请列举至少三个应忽略的条目。
参考答案
· 忽略条目:
· node_modules/(若使用 npm 包管理)
· .DS_Store(macOS 系统文件)
· dist/ 或 build/(构建输出目录)
· .env(环境变量文件)
· *.log(日志文件)
· 补充:
· 忽略 IDE 配置文件(如 .vscode/)可根据团队约定决定是否提交。
· 若项目依赖本地配置,应提供示例文件(如 .env.example)供协作者复制。
13. 自动化构建与部署
题目:如果需要将该项目部署到生产环境,你会选择怎样的构建工具和部署流程?请简述关键步骤。
参考答案
· 构建工具:
· 使用 Vite 或 Parcel 快速构建,支持开发热更新和生产打包。
· 配置 CSS 压缩(如 cssnano)、JS 压缩(terser)、图片优化(imagemin)。
· 部署流程:
-
运行构建命令(npm run build),生成 dist 目录。
-
将 dist 目录上传至 CDN 或静态托管服务(如 Vercel、Netlify、阿里云 OSS)。
-
配置域名 DNS 解析。
-
设置 HTTP/2、Gzip 压缩、缓存策略。
-
利用 CI/CD(如 GitHub Actions)实现自动部署:代码推送后自动测试、构建、部署至生产环境。
- 页面 SEO 优化
题目:作为一个汽车品牌官网,SEO 至关重要。请基于当前项目结构,提出至少三条 SEO 改进措施。
参考答案
-
语义化 HTML:使用 header main>
等标签,而非全用
。 -
Meta 标签:
· 添加 description 和 keywords meta 标签,描述品牌和车型。
· 设置 og:title、og:image 等开放图谱标签,提升社交分享效果。
-
结构化数据:使用 JSON-LD 标注车型信息(价格、品牌、型号等),帮助搜索引擎生成富摘要。
-
可访问性:为图片添加 alt 属性,使用 aria-label 改善屏幕阅读器体验,间接提升 SEO 评价。
-
URL 优化:车型详情页 URL 应包含车型名称(如 /models/sagitta)而非无意义 ID,且保持静态化。
-
移动端适配:响应式设计已被搜索引擎列为排名因素,需确保移动端体验良好。
- 异常处理与用户提示
题目:假设在加载车型数据时网络请求失败,应如何优雅地告知用户并提供重试机会?请写出核心代码思路。
参考答案
· 方案:
· 使用 fetch 请求数据,并用 try...catch 捕获网络错误。
· 在容器中展示错误提示和重试按钮。
· 重试时重新请求数据。
· 示例代码:
javascript
async function loadCars() {
try {
const res = await fetch('/data/cars.json');
if (!res.ok) throw new Error('HTTP error');
const data = await res.json();
renderCards(data);
} catch (err) {
const container = document.querySelector('.car-grid');
container.innerHTML = `
<div style="text-align:center; padding:40px;">
<p>加载失败,请检查网络。</p>
<button id="retry-btn">重新加载</button>
</div>`;
document.getElementById('retry-btn').onclick = loadCars;
}
}
``
比如这个代码
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>清爽简单的HTML5音乐播放器演示_郭wes代码</title>
<link rel="stylesheet" href="css/solid.css">
<link rel="stylesheet" href="css/fontawesome.css">
<link rel="stylesheet" href="css/style.css">
<link rel="icon" href="img/favicon.ico">
</head>
<body>
<div id="dowebok">
<div id="bg-artwork"></div>
<div id="bg-layer"></div>
<div id="player">
<div id="player-track">
<div id="album-name"></div>
<div id="track-name"></div>
<div id="track-time">
<div id="current-time"></div>
<div id="track-length"></div>
</div>
<div id="s-area">
<div id="ins-time"></div>
<div id="s-hover"></div>
<div id="seek-bar"></div>
</div>
</div>
<div id="player-content">
<div id="album-art">
<img src="images/1.jpg" class="active" id="_1">
<img src="images/2.jpg" id="_2">
<img src="images/3.jpg" id="_3">
<img src="images/4.jpg" id="_4">
<img src="images/5.jpg" id="_5">
<img src="images/6.jpg" id="_6">
<img src="images/7.jpg" id="_7">
<img src="images/8.jpg" id="_8">
<img src="images/9.jpg" id="_9">
<div id="buffer-box">加载中...</div>
</div>
<div id="player-controls">
<div class="control">
<div class="button" id="play-previous">
<i class="fas fa-backward"></i>
</div>
</div>
<div class="control">
<div class="button" id="play-pause-button">
<i class="fas fa-play"></i>
</div>
</div>
<div class="control">
<div class="button" id="play-next">
<i class="fas fa-forward"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="js/jquery.min.js"></script>
<script src="js/index.js"></script>
</body>
</html>