当你在前端项目中写下 axios.get('/api/list')
并顺利拿到数据时,有没有想过一个问题:如果后端接口压根没开发,这些数据是从哪来的?Mock 工具就像一位隐形的魔术师,在你看不到的地方完成了一场 "偷天换日" 的表演 ------ 拦截你的请求,返回预设的数据,让前端开发彻底摆脱对后端的依赖。
为什么需要请求拦截?
在前后端分离的开发模式中,"接口不同步" 是永远的痛:
- 产品经理催着要 Demo,但后端接口排期还在下周
- 前端页面逻辑写完了,只能用假数据
const data = [...]
硬撑 - 联调时发现接口返回格式和文档不符,大量组件需要返工
Mock 工具的出现,正是通过 "请求拦截" 解决了这些问题。它的核心价值在于:在不修改前端业务代码的前提下,用模拟数据替代真实接口返回,让前后端开发可以并行推进。
举个直观的例子:当你调用 axios.post('/api/login', { username: 'test' })
时,Mock 会在请求发出前 "截胡",直接返回 { code: 0, token: 'xxx' }
的模拟数据,整个过程中浏览器不会产生真实的网络请求,但前端代码却能正常处理 "登录成功" 的逻辑。
拦截的底层基石:浏览器的请求 API
要理解 Mock 的拦截原理,首先得知道前端请求在浏览器中是如何工作的。目前主流的请求方式有两种:XMLHttpRequest
(简称 XHR)和 fetch
,Mock 正是通过重写这两个 API 实现拦截的。
1. XMLHttpRequest
几乎所有主流请求库(axios、jQuery.ajax、vue-resource 等)的底层都依赖 XMLHttpRequest
。它的工作流程如下:
js
// 原生 XHR 请求流程
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data'); // 初始化请求
xhr.addEventListener('readystatechange', function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log('请求结果:', xhr.responseText); // 处理响应
}
});
xhr.send(); // 发送请求
这个流程中有两个关键节点:open
方法(设置请求信息)和 send
方法(发送请求)。Mock 工具正是通过重写这两个方法,实现了对 XHR 请求的拦截。
Mock 如何拦截 XHR?
我们可以用一段简化的代码模拟 Mock.js 的拦截逻辑:
js
// 保存原生 XHR 构造函数
const originalXHR = window.XMLHttpRequest;
const that = this;
// 重写全局 XHR 构造函数
window.XMLHttpRequest = function() {
const xhr = new originalXHR();
// 保存原生 open和send方法
const originalOpen = xhr.open;
const originalSend = xhr.send;
// 重写 open 记录请求信息
xhr.open = function(method, url) {
// 记录请求方法和 URL(关键:用于后续匹配 Mock 规则)
this._method = method.toUpperCase();
this._url = url;
// 调用原生 open 方法
return originalOpen.apply(this, arguments);
};
// 重写 send 进行拦截
xhr.send = function(body) {
// 查找匹配的规则
const matched = that.rules.find(rule =>
rule.url === this._url && rule.method === this._method
);
if (matched) {
// 模拟异步响应
setTimeout(() => {
// 1. 先设置 readyState 为 4
Object.defineProperty(this, 'readyState', {
value: 4,
configurable: true
});
// 2. 设置响应数据
Object.defineProperty(this, 'responseText', {
value: JSON.stringify(matched.response()),
configurable: true
});
// 3. 设置状态码
Object.defineProperty(this, 'status', {
value: 200,
configurable: true
});
// 4. 设置响应头 (axios会检查这个)
this.getAllResponseHeaders = () => {
return 'Content-Type: application/json';
};
// 5. 触发事件
if (typeof this.onreadystatechange === 'function') {
this.onreadystatechange();
}
this.dispatchEvent(new Event('readystatechange'));
this.dispatchEvent(new Event('load'));
}, matched.delay || 0);
} else {
return originalSend.call(this, body);
}
};
return xhr;
};
这段代码揭示了核心逻辑:Mock 通过重写 XHR 的 open 和 send 方法,在请求发送前进行规则匹配,命中则返回模拟数据,否则走正常请求流程。
当你使用 axios 时,它内部创建的 XMLHttpRequest
实例已经是被 Mock 改写过的版本,所以能被无缝拦截。
2. fetch
fetch
是 ES6 新增的请求 API,基于 Promise 设计,语法更简洁,但它并不依赖 XHR,因此需要单独处理。
js
// 原生 fetch 请求
fetch('/api/user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: 1 })
})
.then(res => {
if (!res.ok) throw new Error('请求失败');
return res.json();
})
.then(data => console.log(data))
.catch(err => console.error(err));
Mock 拦截 fetch
的思路与 XHR 类似,即重写全局的 fetch
函数:
js
// 保存原生fetch
const originalFetch = window.fetch;
const that = this;
window.fetch = async function(url, options = {}) {
// 提取请求方法 (默认GET)
const method = options.method?.toUpperCase() || 'GET';
//匹配规则
const matched = that.rules.find(rule =>
rule.url === url && rule.method === method
);
if (matched) {
// 命中规则:返回模拟数据的Response 对象
return new Promise(resolve => {
setTimeout(() => {
resolve(new Response(
JSON.stringify(matched.response()),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
));
}, matched.delay || 0);
});
}
// 未命中:调用原生 fetch
return originalFetch(url, options);
};
通过这种方式,所有通过 fetch
发起的请求都会经过 Mock 的规则检查,实现无缝拦截。
亲手实现一个简易 Mock 工具
光说不练假把式,我们来实现一个简化版的 Mock 工具,亲身体验拦截的全过程。
步骤 1:创建 Mock 核心逻辑
js
// mock.js
export default class SimpleMock {
constructor() {
this.rules = []; // 存储 Mock 规则
this.init(); // 初始化拦截
}
// 添加 Mock 规则
addRule(rule) {
this.rules.push({
...rule,
method: rule.method?.toUpperCase() || 'GET'
});
}
// 初始化 XHR 和 fetch 拦截
init() {
this.interceptXHR();
this.interceptFetch();
}
// 拦截 XHR
interceptXHR() {
const originalXHR = window.XMLHttpRequest;
const that = this;
window.XMLHttpRequest = function() {
const xhr = new originalXHR();
const originalOpen = xhr.open;
const originalSend = xhr.send;
// 重写 open 记录请求信息
xhr.open = function(method, url) {
this._method = method.toUpperCase();
this._url = url;
return originalOpen.apply(this, arguments);
};
// 重写 send 进行拦截
xhr.send = function(body) {
// 查找匹配的规则
const matched = that.rules.find(rule =>
rule.url === this._url && rule.method === this._method
);
if (matched) {
// 模拟异步响应
setTimeout(() => {
Object.defineProperty(this, 'readyState', {
value: 4,
configurable: true
});
Object.defineProperty(this, 'responseText', {
value: JSON.stringify(matched.response()),
configurable: true
});
Object.defineProperty(this, 'status', {
value: 200,
configurable: true
});
this.getAllResponseHeaders = () => {
return 'Content-Type: application/json';
};
if (typeof this.onreadystatechange === 'function') {
this.onreadystatechange();
}
this.dispatchEvent(new Event('readystatechange'));
this.dispatchEvent(new Event('load'));
}, matched.delay || 0);
} else {
return originalSend.call(this, body);
}
};
return xhr;
};
}
// 拦截 fetch
interceptFetch() {
const originalFetch = window.fetch;
const that = this;
window.fetch = async function(url, options = {}) {
const method = options.method?.toUpperCase() || 'GET';
const matched = that.rules.find(rule =>
rule.url === url && rule.method === method
);
if (matched) {
return new Promise(resolve => {
setTimeout(() => {
resolve(new Response(
JSON.stringify(matched.response()),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
));
}, matched.delay || 0);
});
}
return originalFetch(url, options);
};
}
}
步骤 2:在 React 项目中使用
jsx
import { useEffect, useState } from 'react';
import axios from 'axios';
import SimpleMock from './mock';
// 创建 Mock 实例
const mock = new SimpleMock();
// 添加规则
mock.addRule({
url: '/api/todos',
method: 'GET',
delay: 500, // 模拟 500ms 延迟
response: () => ({
code: 0,
message: 'success'
data: [
{ id: 1, title: '学习 Mock 原理', completed: false }
]
})
});
mock.addRule({
url: '/api/todos',
method: 'POST',
response: () => ({
code: 0,
message: 'success'
})
});
function App() {
const [todos, setTodos] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
// 测试 XHR 拦截
axios.get('/api/todos')
.then(res => {
setTodos(res.data.data);
})
.catch(err => {
setError('加载失败:' + err.message);
});
// 测试 fetch 拦截
fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.then(res => {
if (!res.ok) throw new Error('请求失败');
return res.json();
})
.then(data => console.log(data.message)) // 输出 "添加成功"
.catch(err => console.error('POST 失败:', err));
}, []);
if (error) return <div style={{ color: 'red' }}>{error}</div>;
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
export default App;
步骤 3:验证拦截效果
运行项目后,打开浏览器控制台的 Network 面板:
-
你会看到
/api/todos
的 GET 和 POST 请求,但仔细观察会发现:- 这些请求没有真实的网络交互(Size 可能显示为 "from memory cache")
- 响应数据正是我们在规则中定义的模拟数据
- GET 请求会有 500ms 的延迟(符合我们设置的 delay)
-
组件能正常渲染模拟的 todo 列表,证明拦截成功。

通过这个简易工具,我们完美复现了 Mock 拦截请求的核心流程,也验证了底层原理的正确性。
拦截时容易踩的那些坑
在使用 Mock 工具时,很多问题都源于对拦截原理理解不透彻,这里总结几个常见坑点:
1. 部分请求无法拦截?
- 原因:如果请求库使用了特殊的请求方式(如 WebSocket),Mock 可能无法拦截;另外,部分浏览器插件或安全策略可能会恢复原生的 XHR/fetch 方法。
- 解决:检查请求库的底层实现,确保基于 XHR 或 fetch;避免在生产环境引入 Mock 代码。
2. 模拟数据与真实接口格式不一致?
- 原因:Mock 规则定义时没有严格遵循接口文档,导致联调时大量修改。
- 解决:前后端共同维护接口文档(如 Swagger),Mock 规则严格按照文档生成;联调前用工具对比模拟数据与真实接口的差异。
3. 生产环境意外启用 Mock?
- 原因:环境判断逻辑错误,导致生产环境仍加载了 Mock 代码。
- 解决 :通过构建工具(Webpack/Vite)在生产打包时剔除 Mock 代码;使用
process.env.NODE_ENV
严格判断环境。
总结
Mock 拦截请求的技术原理并不复杂,理解拦截原理的价值,不仅在于能更好地使用工具,更在于当遇到问题时能快速定位根源。比如当请求突然不被拦截时,你能想到是规则匹配失败还是 XHR 被意外恢复;当数据格式不符时,你能意识到是 Mock 规则与接口文档不一致。
你在使用 Mock 时遇到过哪些印象深刻的问题?欢迎在评论区分享你的经历和解决方案~