Mock.js 拦截 Axios 和 Fetch 请求的原理与配置方法
深入解析 Mock.js 拦截 Axios 和 Fetch 请求的底层原理,详解为什么 Axios 能原生拦截而 Fetch 默认失效,并提供 Axios 规范化配置五步流程、Fetch 专用适配层代码实现以及生产环境工程化避坑指南。
window.XMLHttpRequest 原型对象。Axios 在浏览器端默认使用 XMLHttpRequest 适配器,因此引入 Mock.js 后可以直接完成拦截;而现代前端标准的 window.fetch 拥有独立的底层通道,完全绕开了 XHR,导致原生 Mock.js 对 Fetch 请求毫无感知。要解决这一差异,既要在项目中对 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 跨域错误。
二、Axios 请求的拦截配置与实操流程
在以 Axios 为核心请求工具的现代前端工程(如 Vue、React 或 Vite/Webpack 项目)中,配置 Mock.js 的核心目标是:实现模块化管理、支持动态参数与 RESTful 路由,并在生产打包时干净剔除。
步骤 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);
});
MockXMLHttpRequest 在前端 JavaScript 内部短路,没有向操作系统网络套接字写入任何数据。
三、Fetch 请求拦截的实现方案与代码落地
在许多不依赖 Axios、直接采用原生 fetch() API 或基于 Fetch 封装的库(如 ky、wretch)的项目中,Mock.js 原生规则将直接穿透。解决这一问题的标准工程思路是:为 window.fetch 编写一个代理拦截器(Adapter),使其同样去读取 Mock.js 的规则池并封装标准 Response 返回。
方案 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 判定不匹配,请求被抛给原生逻辑甚至引发报错。
/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 体系下的核心差异:
| 对比项 | 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,同样能以极小的侵入性实现全流程的数据模拟。掌握这两条链路的底层逻辑与对应配置,可以帮助团队在接口联调早期建立更加顺畅的开发体验。
为什么叫JavaScript:语言命名由来及与Java的历史渊源
2026-11-05 11:37:48
JS获取当前时间年月日时分秒并格式化为YYYY-MM-DD HH:mm:ss
2026-11-04 21:25:21
不同语言间JS与Java/Python加密结果不一致怎么办
2026-11-04 18:27:23
JS怎么获取当前日期并格式化为YYYY-MM-DD
2026-11-03 18:47:03
怎么用JS在网页上实时动态显示当前时间
2026-11-03 17:20:33








