前端常见问题详解
本文档系统地整理了前端开发过程中常见的多个问题,涵盖网络请求、错误处理、性能优化、工程化、界面效果、DOM 操作、微前端、日志埋点、安全、国际化、认证授权等各个方面。每个问题都附有理论解释和部分代码示例,以便开发过程中参考和实践。
1. 网络请求与错误处理
批量请求失败只弹一个 toast
思路说明:
当多个请求同时失败时,为避免连续弹出多个错误提示,可以通过全局错误处理函数并结合防抖(debounce)或节流(throttle)机制,实现只在一定时间内弹出一次 toast。
示例代码:
// 防抖函数示例
function debounce(fn, delay) {
let timer;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
// toast 弹出方法(假设调用第三方 toast 组件)
function showToast(msg) {
Toast.show(msg);
}
// 包装后的防抖版本
const showDebouncedToast = debounce(showToast, 300);
// 在多个请求的 catch 里调用
axios.get('/api/xxx').catch(() => {
showDebouncedToast("请求失败,请稍后重试");
});
Axios 取消请求
原理说明:
使用 Axios 内置的取消令牌(CancelToken)可以在必要时取消请求,避免不必要的网络开销和响应处理。
示例代码:
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/api/data', {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
}).catch(thrown => {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
// 需要取消时调用
cancel('操作取消了请求');
请求超时与重试机制
思路说明:
设置请求超时时间,捕获超时错误后通过重试机制(带重试计数)重新发起请求,从而提高请求成功率。
示例代码:
async function requestWithRetry(url, options = {}, retries = 3) {
try {
const response = await axios({
url,
timeout: 5000, // 设置超时
...options,
});
return response;
} catch (error) {
if (retries > 0) {
return requestWithRetry(url, options, retries - 1);
} else {
throw error;
}
}
}
请求拦截器的使用
说明:
利用 Axios 拦截器,可以在请求发出前附加 token、记录开始时间等,响应到达后计算耗时、统一处理错误信息。拦截器使得每个请求都经过统一的逻辑处理。
示例代码:
// 请求拦截器
axios.interceptors.request.use(config => {
// 比如在请求头中添加 token
config.headers.Authorization = 'Bearer ' + getToken();
config.metadata = { startTime: new Date() };
return config;
}, error => {
return Promise.reject(error);
});
// 响应拦截器
axios.interceptors.response.use(response => {
const duration = new Date() - response.config.metadata.startTime;
console.log('Request duration:', duration);
return response;
}, error => {
// 统一错误处理
showDebouncedToast("请求失败,请稍后重试");
return Promise.reject(error);
});
2. 前端监控与日志埋点
前端监控方案
关键点:
捕获全局错误(使用 window.onerror
、window.onunhandledrejection
)
请求失败、接口响应异常的日志采集
性能监控(页面加载、资源加载、长任务统计)
用户行为追踪(点击、页面停留时间、关键操作上报)
常见方案:
自定义埋点,通过拦截器记录请求信息、页面错误上报服务器
使用 Performance API 获取页面性能数据
示例代码(使用 Sentry 上报):
// Sentry 初始化示例
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'your-dsn-url',
release: 'your-project@1.0.0',
});
// 全局捕获错误
window.onerror = function(message, source, lineno, colno, error) {
Sentry.captureException(error);
};
全站请求耗时统计
思路说明:
在 Axios 拦截器中记录每个请求开始的时间和结束的时间,将耗时数据上报至日志系统,便于分析接口响应瓶颈。
示例代码:
axios.interceptors.request.use(config => {
config.metadata = { startTime: new Date() };
return config;
});
axios.interceptors.response.use(response => {
const duration = new Date() - response.config.metadata.startTime;
// 记录或者上报请求耗时数据
console.log(`Request to ${response.config.url} took ${duration}ms`);
return response;
});
3. 代码质量与工程化
if-else 和 babel-runtime 的作用
if-else 说明:
在代码中大量 if-else 判断可能导致代码逻辑冗长。
可以通过封装公共函数、状态机、策略模式等手段来优化复杂的条件判断,使代码更易维护。
babel-runtime 说明:
babel-runtime
用于抽取 ES6+ 代码转换时用到的辅助函数及 polyfill,避免全局污染,同时减少重复代码引入。
当你的项目使用 Babel 转译 ES6+ 代码时,引入 babel-runtime 能使最终代码体积更小,并确保兼容性。
代码重构与清理未使用代码
重构策略:
模块化:将代码拆分为更小的模块,保证单一职责
静态分析:使用 ESLint、TSLint 等工具检测代码质量
Tree Shaking:利用 webpack 的 Tree Shaking 特性清理未使用代码
插件工具:使用 PurifyCSS(针对 CSS)和 unused-webpack-plugin(针对 JS/TS)等工具检测并清理无用代码
package.json 中 sideEffects 属性
说明:
sideEffects
属性告诉 webpack 哪些模块在导入时不会产生副作用,从而使 Tree Shaking 更有效。
如果你的代码或第三方库模块被标记为无副作用,打包时可以将未使用的代码自动剔除,从而减小包体积。
从零搭建前端基建
搭建步骤:
选择打包工具(webpack、Rollup、Vite 等)
配置 Babel(或 TypeScript 编译器)来支持 ES6+ 特性
配置 ESLint、Prettier 等代码检查工具
配置热更新、代码拆分、懒加载及性能优化插件
配置单元测试和持续集成环境
定义统一的项目目录结构和开发规范
4. CSS 与界面效果
px 转 rem
实现方式:
使用 PostCSS 插件 postcss-pxtorem 在构建过程中自动将 px 单位转换为 rem
或者在 JS 中根据设计稿宽度动态设置根元素 font-size
PostCSS 示例(postcss.config.js):
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 16,
propList: ['*'],
},
},
};
实现翻牌效果
原理说明:
通过 CSS3 的 transform
和 transition
结合 perspective
属性,实现 3D 翻转的视觉效果。
示例代码:
.flip-card {
perspective: 1000px;
}
.flip-card-inner {
transition: transform 0.6s;
transform-style: preserve-3d;
}
.flip-card:hover .flip-card-inner {
transform: rotateY(180deg);
}
.flip-card-front, .flip-card-back {
backface-visibility: hidden;
position: absolute;
width: 100%;
height: 100%;
}
.flip-card-back {
transform: rotateY(180deg);
}
flex:1 的含义
解释:
在 Flex 布局中,flex: 1
表示元素可伸缩且平分容器中剩余空间。
等同于 flex-grow: 1; flex-shrink: 1; flex-basis: 0%
,使得该元素在没有固定宽度时尽可能扩展占据剩余空间。
实现折叠面板效果
思路说明:
通过操作 DOM 元素高度、设置 CSS 过渡动画,实现面板内容的展开与收起。
通过点击事件切换状态并动态调整元素高度。
示例代码:
<div class="panel">
<div class="panel-header" onclick="togglePanel()">点击展开/收起</div>
<div class="panel-body" style="height: 0; overflow: hidden; transition: height 0.3s;">
这里是内容区域
</div>
</div>
<script>
function togglePanel() {
const panelBody = document.querySelector('.panel-body');
if (panelBody.style.height === '0px' || panelBody.style.height === '') {
panelBody.style.height = panelBody.scrollHeight + 'px';
} else {
panelBody.style.height = '0';
}
}
</script>
网页加载进度条
实现方式:
可采用 NProgress 这样的第三方库快速实现
或者自定义监听页面资源加载事件,动态更新进度条状态
NProgress 示例:
// 引入 NProgress
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
// 在页面开始加载时调用
NProgress.start();
// 在所有资源加载完成后调用
window.onload = function() {
NProgress.done();
};
图片懒加载
实现方式:
利用 Intersection Observer API 监听图片元素是否进入可视区域
或者监听 scroll 事件进行判断,再动态设置图片 src
属性
Intersection Observer 示例:
const lazyImages = document.querySelectorAll('img.lazy');
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img);
}
});
});
lazyImages.forEach(img => {
imageObserver.observe(img);
});
5. DOM 操作与数据判断
判断 DOM 关系:A 是否是 B 的子元素
实现说明:
使用 DOM 内置方法 Node.contains
判断元素关系,简单且性能良好。
示例代码:
if (B.contains(A)) {
console.log("A 是 B 的子元素");
}
判断对象是否为空(含原型链自定义属性)
思路说明:
对象是否“空”需要检查自身属性以及原型链上是否存在除 Object 原型中预定义属性以外的自定义属性。
遍历 for...in
时会遍历自身属性和可枚举的原型链属性。
示例代码:
function isTrulyEmpty(obj) {
for (let key in obj) {
// 如果是对象自有属性或原型上有除标准原型外的属性,则认为不为空
if (obj.hasOwnProperty(key) || Object.getPrototypeOf(obj)[key] !== undefined) {
return false;
}
}
return true;
}
注:实际应用中需根据具体需求调整判断逻辑。
大对象深度对比
实现思路:
使用递归遍历对象所有属性,注意处理循环引用的问题。
可借助现有库如 lodash 的 _.isEqual
实现深度比较。
示例(简化版):
function deepEqual(obj1, obj2) {
if (obj1 === obj2) return true;
if (typeof obj1 !== 'object' || typeof obj2 !== 'object' || !obj1 || !obj2) {
return false;
}
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (let key of keys1) {
if (!deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
6. 前端架构与微前端
前端国际化方案
策略说明:
采用成熟的国际化库,如 i18next、vue-i18n 或 react-intl
多语言资源通常以 JSON 文件形式存储,通过语言切换动态更新页面文本
示例(React + react-intl):
import { IntlProvider, FormattedMessage } from 'react-intl';
import messages_en from './locales/en.json';
import messages_zh from './locales/zh.json';
const messages = {
en: messages_en,
zh: messages_zh,
};
function App() {
const locale = 'zh';
return (
<IntlProvider locale={locale} messages={messages[locale]}>
<div>
<FormattedMessage id="app.greeting" defaultMessage="Hello" />
</div>
</IntlProvider>
);
}
应用灰度发布与用户体验保证
策略说明:
利用环境变量、配置中心及 API 网关实现新功能灰度发布
在页面请求接口时通过限流、降级策略和分布式缓存确保稳定性
用户体验保证包括平滑切换、友好的错误提示和适时的更新提示
微前端架构与 JavaScript 隔离
原理说明:
微前端目标是将多个独立的子应用组合到一个整体应用中。
传统 iframe 方案虽然能做到样式和 JS 隔离,但交互不便、性能损耗较大。
现代方案如 Qiankun 采用沙箱(Sandbox)技术(例如通过 Proxy、eval 及隔离全局变量)来实现应用隔离,既保证子应用的独立性,也允许适当通信。
React 渲染优化及 key 的使用
问题说明:
在列表渲染中,不建议使用 index 作为 key,因为这样可能导致组件复用错误,无法正确识别组件状态变化。
应尽可能使用稳定且唯一的标识作为 key。
示例:
// 错误示例:使用 index 作为 key
{items.map((item, index) => <Item key={index} data={item} />)}
// 正确示例:使用唯一 id
{items.map(item => <Item key={item.id} data={item} />)}
Context 使用中的注意点
说明:
React Context 提供跨组件数据传递,但若大量数据存储在 context 中,可能引起上下文订阅组件的频繁重渲染。
应注意分离大范围共享数据和局部状态,或使用 memoization 来优化。
7. 性能优化与资源管理
大文件上传与大数据量渲染
策略说明:
大文件上传:采用分片上传、断点续传技术,常用工具如 uppy
大数据渲染:使用虚拟列表(Virtual List)、分页、懒加载,库如 react-virtualized
QPS 峰值处理策略
措施:
后端通过 API 限流、缓存、分布式负载均衡处理高并发
前端可以使用异步任务队列、Web Worker 分担计算任务,必要时显示降级界面
JS 执行大量任务不卡顿
策略说明:
将大量任务分解为小块,利用事件循环空隙执行(如使用 requestIdleCallback
或 setTimeout
)
可采用 Web Worker 将计算任务移至后台线程
示例(使用 setTimeout 分批执行):
function processTasks(tasks) {
if (!tasks.length) return;
const task = tasks.shift();
// 执行任务
doTask(task);
setTimeout(() => processTasks(tasks), 0);
}
Webpack 打包优化
内容说明:
Hash 生成:基于模块内容生成 hash,确保模块内容改变时 hash 改变
运行时 chunk 加载:将运行时代码与应用代码分离,按需加载,减少首屏加载时间
配置 Tree Shaking、懒加载、代码分割、缓存优化等均是常见手段
请求耗时统计
实现方式:
在 Axios 拦截器中记录请求开始时间与响应时间,计算耗时数据后上报至日志系统或统计平台。
示例代码参考:见上文 请求拦截器的使用
8. 安全、跨域与缓存
浏览器同源策略与 CDN 加载
说明:
浏览器同源策略限制跨域 DOM 操作、Ajax 请求等。
CDN 提供静态资源服务,加载时不涉及复杂的 Cookie、LocalStorage 跨域问题,因此大部分情况下不会触发严格的跨域限制。
Cookie 的跨域共享
说明:
Cookie 不能在完全不同的主域间共享。
可通过设置 Domain
属性来实现二级域名之间共享。例如,设置 Domain=.example.com
可使 sub1.example.com
和 sub2.example.com
共享 Cookie。
如何防止别人调试或移除水印
策略说明:
代码混淆:通过打包工具(如 webpack 的 UglifyJS、Terser)混淆代码
检测调试:在代码中加入检测开发者工具打开的逻辑,动态生成水印
服务端渲染:关键内容由服务端生成,降低前端篡改风险
9. HTML、CSS 与浏览器 API
HTML 中 data- 属性
说明:
HTML 元素上以 data-
开头的属性用于存储自定义数据,不会影响页面展示
可通过 element.dataset
对象进行访问
示例:
<div data-user-id="12345" data-role="admin"></div>
const div = document.querySelector('div');
console.log(div.dataset.userId); // "12345"
判断 DOM 是否在可视区域
实现方式:
使用 Element.getBoundingClientRect()
获取元素位置,再判断是否在 viewport 内。
示例代码:
function isInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
}
Canvas 应用场景:电影院选票示例
思路说明:
使用 HTML5 Canvas 绘制座位图,利用事件监听实现选座逻辑
根据需要绘制座位状态(已选、空闲、不可用)
示例代码:
<canvas id="ticketCanvas" width="600" height="400"></canvas>
<script>
const canvas = document.getElementById('ticketCanvas');
const ctx = canvas.getContext('2d');
// 示例:绘制一个座位
function drawSeat(x, y, status) {
ctx.fillStyle = status === 'selected' ? '#ff0000' : '#ccc';
ctx.fillRect(x, y, 40, 40);
}
// 绘制多个座位
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
drawSeat(50 + j * 50, 50 + i * 50, 'free');
}
}
// 可扩展:绑定点击事件,实现选座逻辑
canvas.addEventListener('click', (e) => {
// 计算点击位置,判断点击了哪个座位
console.log(e.offsetX, e.offsetY);
});
</script>
requestIdleCallback 和 documentFragment 的使用
说明:
requestIdleCallback
可用于在浏览器空闲时执行低优先级任务,避免主线程阻塞。
documentFragment
是内存中构建 DOM 的轻量容器,插入后可以一次性触发 DOM 更新,减少重排和重绘。
示例代码:
// 使用 requestIdleCallback 执行低优先级任务
requestIdleCallback(() => {
console.log("浏览器空闲时执行的任务");
});
// 使用 documentFragment 构建 DOM
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);
10. 工具链与前端 CLI
如何用 webpack 构建 React 应用
搭建步骤:
初始化项目,安装 webpack、Babel、React 及相关依赖
配置 webpack 的入口(entry)、输出(output)及模块规则(module.rules)
配置 Babel(.babelrc 或 babel.config.js),支持 JSX 和 ES6+
配置开发环境(如 webpack-dev-server)实现热更新
根据需要配置代码分割、懒加载、Tree Shaking 等优化项
示例 webpack 配置(简化版):
// webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.jsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.[hash].js',
publicPath: '/',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
// 其他 loader(如 css-loader、file-loader 等)
]
},
devServer: {
historyApiFallback: true,
hot: true,
}
};
Node.js 实现命令行统计代码行数
思路说明:
利用 Node.js 的 fs 模块读取目录下所有文件
过滤出 JS、TS、CSS 文件,统计文件中非空行数
遍历目录(递归)累加行数
示例代码:
const fs = require('fs');
const path = require('path');
function countLines(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
return content.split('\n').filter(line => line.trim() !== '').length;
}
function traverseDir(dir) {
let total = 0;
fs.readdirSync(dir).forEach(item => {
const fullPath = path.join(dir, item);
if (fs.statSync(fullPath).isDirectory()) {
total += traverseDir(fullPath);
} else if (/\.(js|ts|css)$/.test(fullPath)) {
total += countLines(fullPath);
}
});
return total;
}
console.log('总行数:', traverseDir('./src'));
script 标签的属性及 SPA hash 路由
script 标签常见属性:
src
:引入外部脚本
async
:脚本异步加载,执行顺序不保证
defer
:脚本延迟加载,页面解析完成后执行
SPA hash 路由好处:
利用 URL 的 hash 部分实现前端路由,不需要服务端配置
用户刷新页面时不会因路由问题导致 404
11. 认证、授权与单点登录
OAuth2.0 与单点登录(SSO)
OAuth2.0 说明:
一种授权协议,允许第三方应用访问用户受保护资源
通过授权码、隐式授权等模式实现安全授权
单点登录(SSO)流程:
用户访问需要认证的应用
应用将用户重定向至认证中心登录
登录成功后,认证中心返回一个 token 或授权码
应用利用 token 验证用户身份,并创建会话
Token 验证与权限设计
思路说明:
常用 JSON Web Token(JWT)在前后端之间传递用户身份信息
前端根据 token 内的信息(如用户角色、权限列表)决定页面展示和功能权限
结合路由守卫、组件级权限校验构建完整权限体系
12. 其他常见问题
JS 数字超出 Number 最大值的处理
说明:
JavaScript 中 Number 类型存在上限(Number.MAX_SAFE_INTEGER
)
对于超大整数,可使用 BigInt 类型(ES2020 新增)或第三方库如 big.js
示例代码:
const bigNumber = BigInt("900719925474099123456789");
console.log(bigNumber);
前端页面白屏排查
常见原因:
资源加载失败
JS 语法错误或异常
路由配置错误或打包错误
排查步骤:
检查浏览器 Console 输出错误信息
检查网络请求是否正常加载所有资源
使用 sourcemap 定位错误代码位置
虚拟滚动与懒加载实现
策略说明:
虚拟滚动:只渲染可视区域内的 DOM 节点,减少渲染开销
图片懒加载:利用 Intersection Observer 或 scroll 事件延迟加载图片
示例:
使用库 react-virtualized 实现虚拟列表
参考上文 图片懒加载 示例代码
Git Pull 与 Git Fetch 的区别
说明:
git fetch
仅拉取远程更新,不进行自动合并;
git pull
拉取后自动将远程分支与当前分支合并。