前端常见问题详解

本文档系统地整理了前端开发过程中常见的多个问题,涵盖网络请求、错误处理、性能优化、工程化、界面效果、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.onerrorwindow.onunhandledrejection

请求失败、接口响应异常的日志采集

性能监控(页面加载、资源加载、长任务统计)

用户行为追踪(点击、页面停留时间、关键操作上报)

常见方案

集成第三方服务如 SentryLogRocket

自定义埋点,通过拦截器记录请求信息、页面错误上报服务器

使用 Performance API 获取页面性能数据

示例代码(使用 Sentry 上报):

// Sentry 初始化示例
import * as Sentry from '@sentry/browser';

Sentry.init({
  dsn: 'your-dsn-url',
  release: '[email protected]',
});

// 全局捕获错误
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 的 transformtransition 结合 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. 前端架构与微前端

前端国际化方案

策略说明

采用成熟的国际化库,如 i18nextvue-i18nreact-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 执行大量任务不卡顿

策略说明

将大量任务分解为小块,利用事件循环空隙执行(如使用 requestIdleCallbacksetTimeout

可采用 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 不能在完全不同的主域间共享。

可通过设置 Domain 属性来实现二级域名之间共享。例如,设置 Domain=.example.com 可使 sub1.example.comsub2.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 拉取后自动将远程分支与当前分支合并。

苏ICP备2025153828号