Mock.js 拦截 Axios 和 Fetch 请求的原理与配置方法

作者:阿沐

时间:2026-11-04

浏览:669

深入解析 Mock.js 拦截 Axios 和 Fetch 请求的底层原理,详解为什么 Axios 能原生拦截而 Fetch 默认失效,并提供 Axios 规范化配置五步流程、Fetch 专用适配层代码实现以及生产环境工程化避坑指南。

文章目录
前端架构 · 网络请求模拟
Mock.js 拦截网络请求的核心机制,是利用“猴子补丁”(Monkey Patching)直接重写浏览器的原生 window.XMLHttpRequest 原型对象。Axios 在浏览器端默认使用 XMLHttpRequest 适配器,因此引入 Mock.js 后可以直接完成拦截;而现代前端标准的 window.fetch 拥有独立的底层通道,完全绕开了 XHR,导致原生 Mock.js 对 Fetch 请求毫无感知。要解决这一差异,既要在项目中对 Axios 进行模块化路由配置,也需要针对 Fetch 提供一层专用的代理适配层。本文将具体阐明其底层的运行机制,并给出直接可用的配置代码与调试技巧。
Mock.js 拦截 Axios 和 Fetch 请求的原理与配置方法

一、核心原理:为什么 Axios 能直接拦截,而 Fetch 会失效?

要理解两者的表现差异,首先要观察 Mock.js 启动时在浏览器环境中所做的具体操作。Mock.js 并不是在网络协议层面(如操作系统网卡或代理服务)进行报文捕获,而是工作在前端 JavaScript 单线程的运行时内存中。

1. Mock.js 的“猴子补丁”拦截机制

当执行 import Mock from 'mockjs' 或在页面中载入脚本时,Mock.js 内部会执行一段替换逻辑:它将全局的 window.XMLHttpRequest 备份为内部引用,然后将自定义的 MockXMLHttpRequest 构造函数赋值给 window.XMLHttpRequest。

这个自定义的构造函数模拟了标准 XHR 对象的所有公开属性与事件周期(如 open、send、setRequestHeader、onreadystatechange 等)。当调用 send() 发送数据时,Mock.js 不会去创建真实的底层 TCP 连接,而是遍历内部注册的规则列表 Mock._mocked。如果请求的 URL 和 Method 命中规则,它便依据预设的数据模板计算生成 Mock 数据,并通过宏任务或微任务直接触发 onload 或 onreadystatechange 回调,把模拟数据交还给调用方。

2. Axios 为何天生支持拦截

Axios 是一套同构的 HTTP 请求库。在浏览器环境下,Axios 的分发适配器默认定位到 xhrAdapter。当执行 axios.get('/api/users') 时,底层其实是通过 new window.XMLHttpRequest() 实例化一个通信句柄。因为此时全局的 window.XMLHttpRequest 已经被 Mock.js 偷梁换柱,Axios 拿到的正是 MockXMLHttpRequest 实例,请求流程自然被截留到了 Mock 内存闭环中。

3. 原生 Fetch 为何完全绕过 Mock.js

window.fetch 是 WHATWG 与 W3C 组织制定的现代异步请求标准,它属于浏览器底层的内置原生函数,其规范与执行通道独立于 XMLHttpRequest。在浏览器内核中,fetch() 的调度不依赖任何 XHR 对象。

由于原生 Mock.js 只重写了 window.XMLHttpRequest,未对 window.fetch 做任何改写,因此当执行 fetch('/api/users') 时,代码直接进入了浏览器内核的原生网络处理流程。如果当前开发服务器或远端没有对应接口,控制台就会如实报出 404 Not Found 或 CORS 跨域错误。

Mock.js 拦截 Axios 和 Fetch 请求的原理与配置方法
图 1:Mock.js 底层拦截架构与双请求管道对比

二、Axios 请求的拦截配置与实操流程

在以 Axios 为核心请求工具的现代前端工程(如 Vue、React 或 Vite/Webpack 项目)中,配置 Mock.js 的核心目标是:实现模块化管理、支持动态参数与 RESTful 路由,并在生产打包时干净剔除。

Mock.js 拦截 Axios 和 Fetch 请求的原理与配置方法
图 2:Axios 接入 Mock.js 规范化配置五步实操流程

步骤 1:安装项目所需依赖

在前端工程根目录下执行安装命令。建议将 mockjs 放置在开发依赖中:

# 在当前工程中安装 axios 和 mockjs
npm install axios
npm install mockjs --save-dev

步骤 2:规划模块化 Mock 目录结构

避免把所有 Mock 规则堆叠在一个庞大的文件中。建议在 src 目录下设立专门的 mock 结构:

src/
├── api/             # 统一封装的业务请求接口
│   └── user.js
├── mock/            # Mock 核心规则目录
│   ├── index.js     # Mock 汇总入口与全局配置
│   └── modules/     # 按业务域拆分的模拟数据
│       └── user.js
└── main.js          # 项目启动入口

步骤 3:编写业务 Mock 模块与匹配规则

在 src/mock/modules/user.js 中定义用户相关的响应。特别需要注意的是:Axios 发送 GET 请求时通常会在 URL 拼接查询参数(如 ?page=1),如果使用固定字符串匹配会导致拦截失败,必须使用正则表达式匹配路径。

// src/mock/modules/user.js
import Mock from 'mockjs';

// 模拟用户列表接口:支持路径正则匹配与动态响应函数
const getUserList = (options) => {
  // options 包含 url、type、body 属性
  return Mock.mock({
    code: 200,
    message: 'success',
    'data|5-10': [
      {
        'id|+1': 1,
        name: '@cname',
        email: '@email',
        'role|1': ['admin', 'editor', 'viewer'],
        createTime: '@datetime'
      }
    ]
  });
};

// 模拟用户登录接口:POST 请求
const login = (options) => {
  const body = JSON.parse(options.body || '{}');
  if (body.username === 'admin' && body.password === '123456') {
    return {
      code: 200,
      token: 'mock-token-xyz-8899',
      message: '登录成功'
    };
  }
  return {
    code: 401,
    message: '用户名或密码错误'
  };
};

// 导出需要注册的规则配置列表
export default [
  {
    url: RegExp('/api/user/list.*'),
    method: 'get',
    response: getUserList
  },
  {
    url: '/api/user/login',
    method: 'post',
    response: login
  }
];

接着在 src/mock/index.js 中加载所有规则,并配置响应延时,以更真实地模拟网络往返:

// src/mock/index.js
import Mock from 'mockjs';
import userRules from './modules/user';

// 配置模拟响应延时(毫秒区间)
Mock.setup({
  timeout: '200-600'
});

// 注册所有模块的路由规则
const rules = [...userRules];
rules.forEach(item => {
  Mock.mock(item.url, item.method, item.response);
});

export default Mock;

步骤 4:在前端入口按环境变量条件加载

在应用主入口(如 src/main.js)中,只在非生产环境中引入 Mock 配置。这样可以防止 Mock 代码和多余规则被打入生产环境部署包:

// src/main.js
import { createApp } from 'vue';
import App from './App.vue';

// 仅在本地开发或测试阶段启用 Mock 拦截
if (process.env.NODE_ENV !== 'production') {
  // 动态加载或直接同步引入
  require('./mock');
  console.log('[Mock] 本地数据拦截已启动');
}

createApp(App).mount('#app');

步骤 5:发起 Axios 请求与状态验证

在业务逻辑中直接调用 Axios:

// 业务调用示例
import axios from 'axios';

axios.get('/api/user/list?page=1').then(res => {
  console.log('Axios 获取到 Mock 数据:', res.data);
});
重要验证提示
执行上述代码后,按 F12 打开浏览器开发者工具。在 Console 控制台能看到完整的返回 JSON,但在 Network(网络)面板中找不到任何 HTTP 请求记录。这是正常且预期的行为,因为请求已被全局 MockXMLHttpRequest 在前端 JavaScript 内部短路,没有向操作系统网络套接字写入任何数据。

三、Fetch 请求拦截的实现方案与代码落地

在许多不依赖 Axios、直接采用原生 fetch() API 或基于 Fetch 封装的库(如 ky、wretch)的项目中,Mock.js 原生规则将直接穿透。解决这一问题的标准工程思路是:为 window.fetch 编写一个代理拦截器(Adapter),使其同样去读取 Mock.js 的规则池并封装标准 Response 返回。

Mock.js 拦截 Axios 和 Fetch 请求的原理与配置方法
图 3:Fetch 拦截适配补丁机制与数据流转原理

方案 A:手写轻量 Fetch 拦截适配层(推荐)

这种方式不额外增加任何第三方包依赖,纯原生实现。其原理是:保存原生的 window.fetch 引用,重写 window.fetch,在发起请求时检查 Mock._mocked 中的规则映射。若命中则构造符合 Fetch 规范的 Response 对象返回;未命中则退回执行原生 fetch 放行请求。

在 src/mock/fetch-patch.js 中写入以下完整代码:

// src/mock/fetch-patch.js
import Mock from 'mockjs';

/**
 * 为 Mock.js 注入 window.fetch 支持
 */
export function setupFetchMock() {
  if (typeof window === 'undefined' || !window.fetch) {
    return;
  }

  // 1. 备份原生 window.fetch
  const originalFetch = window.fetch;

  // 2. 重写 window.fetch
  window.fetch = function (input, init = {}) {
    // 规范化获取请求 url 与 method
    let url = typeof input === 'string' ? input : input.url;
    const method = (init.method || 'GET').toUpperCase();

    // 3. 从 Mock.js 内部规则注册池寻找匹配项
    const matchedMock = (Mock._mocked || []).find(item => {
      // 检查 HTTP Method 是否一致(未指定时支持所有方法)
      const methodMatch = !item.rtype || item.rtype.toUpperCase() === method;
      if (!methodMatch) return false;

      // 检查 URL 是否匹配(支持正则或字符串全等)
      if (item.rurl instanceof RegExp) {
        return item.rurl.test(url);
      }
      return item.rurl === url;
    });

    // 4. 若未命中任何规则,放行调用原生 fetch 发送真实请求
    if (!matchedMock) {
      return originalFetch.call(window, input, init);
    }

    // 5. 命中规则:计算 Mock 数据并包装为标准 Response
    return new Promise((resolve) => {
      // 构造与 MockXMLHttpRequest 类似的 options 参数
      const mockOptions = {
        url,
        type: method,
        body: init.body
      };

      let result;
      if (typeof matchedMock.template === 'function') {
        result = matchedMock.template(mockOptions);
      } else {
        result = Mock.mock(matchedMock.template);
      }

      // 模拟真实网络响应延迟(取值与 Mock.setup 类似,默认 200ms)
      setTimeout(() => {
        // 构建符合 Fetch API 规范的标准 Response 实例
        const response = new Response(JSON.stringify(result), {
          status: 200,
          statusText: 'OK',
          headers: new Headers({
            'Content-Type': 'application/json; charset=utf-8'
          })
        });

        resolve(response);
      }, 200);
    });
  };
}

在 src/mock/index.js 中调用此函数,即可让所有通过 Mock.mock(...) 注册的规则无缝支持 Fetch:

// src/mock/index.js 入口补充
import Mock from 'mockjs';
import { setupFetchMock } from './fetch-patch';

// 激活 Fetch 适配补丁
setupFetchMock();

// 注册接口示例
Mock.mock(RegExp('/api/fetch-test.*'), 'get', {
  code: 200,
  message: 'Fetch 拦截成功',
  'list|3': ['@word']
});

执行原生 Fetch 验证调用:

// 验证 Fetch 请求
fetch('/api/fetch-test?limit=3')
  .then(res => res.json())
  .then(data => {
    console.log('Fetch 成功返回数据:', data);
  })
  .catch(err => {
    console.error('Fetch 错误:', err);
  });

方案 B:引入社区成熟适配包

如果不希望在业务仓内维护补丁函数,也可以直接采用社区封装好的中间包,例如 mockjs-fetch 或 fetch-mock。以 mockjs-fetch 为例:

# 安装适配扩展
npm install mockjs-fetch --save-dev
// 在 mock/index.js 中引入
import Mock from 'mockjs';
import mockFetch from 'mockjs-fetch';

// 将 Mock 对象传递给适配器
mockFetch(Mock);

// 后续声明与标准 Mock.js 保持一致
Mock.mock('/api/system/info', 'get', { version: '1.0.0' });

无论是手写补丁还是采用三方包,其核心逻辑完全相同:都是重写 window.fetch,将请求接管并转换为 Promise<Response> 返回。

四、常见问题排查与工程化避坑指南

1. Axios 携带 Query 参数或 RESTful 参数无法匹配

很多初学者在声明路由时写成字符串形式,如 Mock.mock('/api/order/detail', ...)。在业务端调用 axios.get('/api/order/detail?id=123') 时,Mock.js 内部使用 === 字符串全等对比,导致 URL 判定不匹配,请求被抛给原生逻辑甚至引发报错。

排查与修复方法
凡是带有动态参数、查询字符串或 RESTful 路径变量(如 /api/order/1001)的接口,必须传入 RegExp 对象而非字符串:
Mock.mock(new RegExp('^/api/order/detail(\?.*)?$'), 'get', ...)

2. Axios 设置了 withCredentials 后的跨域警报

当项目中配置了 axios.defaults.withCredentials = true 准备跨域携带 Cookie 时,由于 Mock.js 替换了 XHR,某些严格的校验场景(例如某些安全扫描脚本或早期版本适配器)可能会抛出 InvalidStateError。

原因:MockXMLHttpRequest 没有完整实现所有原生 Cookie 证书通道。开发环境下如果所有接口均已 Mock 化,可暂时关闭 withCredentials;或者确保 Mock 拦截早于 Axios 拦截器初始化。

3. 生产打包导致产物体积膨胀

Mock.js 包含较为庞大的随机数据字典(涵盖中文姓名、地名、段落文本等)。如果在生产环境使用静态 import './mock',Webpack 或 Vite 会直接将 Mock.js 及其字典全部打包进生产包,导致 chunk 体积额外增加数百 KB。

打包优化建议
在现代化工程中,推荐在 vite.config.js 或 vue.config.js 中借助环境变量进行剔除,或者采用环境守卫动态导入:
if (import.meta.env.MODE === 'development') {
  import('./mock').then(() => {
    console.log('Mock 仅在本地生效');
  });
}

五、Axios 与 Fetch 拦截方案综合对比与总结

为了帮助大家在日常技术选型与团队规范搭建中形成清晰认知,下表与图示系统梳理了 Axios 与 Fetch 在 Mock.js 体系下的核心差异:

Mock.js 拦截 Axios 和 Fetch 请求的原理与配置方法
图 4:Axios 与 Fetch 拦截方案核心维度对比矩阵
对比项 Axios 方案 Fetch 方案
底层通信载体 window.XMLHttpRequest window.fetch 全局 API
Mock.js 原生兼容 完全开箱即用,无需任何补丁 原生不支持,必须注入代理适配层
实现拦截成本 极低:规范化书写规则即可 中等:需手写约 50 行补丁或依赖三方插件
DevTools 表现 Network 面板无记录(内存中闭环) 未命中走真实网络,命中则无记录
响应对象形态 AxiosResponse 封装结构 标准 WHATWG Response 实例

综上所述,Mock.js 凭借对 XMLHttpRequest 的灵活重写,为传统的 Axios 体系提供了极低成本的联调模拟能力;而在拥抱原生 Fetch 的现代前端技术架构中,通过重写 window.fetch 提取 Mock._mocked 并返回标准 Response,同样能以极小的侵入性实现全流程的数据模拟。掌握这两条链路的底层逻辑与对应配置,可以帮助团队在接口联调早期建立更加顺畅的开发体验。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章