上周四下午,我正在调样式,突然电脑风扇开始咆哮。
打开任务管理器:内存占用 15.5G / 16G。
我人傻了,16G 内存开发个前端项目还能卡?
仔细一看:
- VSCode:2.1G(跑着项目,正常)
- Chrome 30 个标签页:4.5G(掘金、Stack Overflow、GitHub、文档...能理解)
- APIfox:1.5G(???)
- 微信 + 企业微信:1.1G(没办法,工作要用)
- 其他若干进程…:...
APIfox这一个工具,占了 1.5G?
关键是,我只是想 Mock 几个接口而已,为什么要:
- 装一个 500M+ 的客户端
- 开代理(还得记得关)
- 来回切窗口(浏览器 → APIfox → VSCode)
- 占用 1.5G 内存
我试过其他方案:
- 在代码里写死 Mock 数据 → 有时候忘记删掉,上线差点带着假数据冲进生产
- 用 Mock.js → 配置太麻烦,还要改代码
我想要的是:不装客户端、不改代码、不开代理、不耗内存、一键切换 Mock 开关 。 于是,我花了一个周末,做了一个chrome浏览器插件。
现在mock不用 APIfox,内存占用降了不少,电脑顺多了,浏览器里一键 Mock,调试又快又省心。
想知道怎么实现的吗?接下来将从零开始,和你一起实现这个mock小工具
效果演示: 下面是插件在拦截 fetch/ajax 请求并返回 Mock 数据时的演示:
插件功能预览
- 拦截页面的
fetch、ajax请求 - 规则支持模糊匹配和完全匹配
- 支持按 HTTP 方法过滤(GET/POST/PUT/DELETE)
- 自定义返回 JSON 数据
持续优化中:性能、匹配规则和用户体验会不断迭代,欢迎反馈与建议
升级版:AI‑Mock(更强、更好用)
- 想要更强的可视化与 AI 能力?升级到 AI‑Mock,查看使用说明 👇
- 不仅免费,还开源?这个 AI Mock 神器我必须曝光它 juejin.cn/post/757879…
Chrome扩展商店一键安装:AI Mock, 如果对你有帮助欢迎好评。 chromewebstore.google.com/detail/ai-m…
根本原理:劫持浏览器的网络请求方法
一句话总结
在网页加载前,偷偷替换掉浏览器原生的 fetch 和 XMLHttpRequest ,让所有网络请求先经过我们的"检查站",符合规则的就返回假数据,不符合的就放行。
原理:
正常情况:
网页代码 → fetch('/api/user') → 浏览器发送真实请求 → 服务器
插件介入后:
网页代码 → fetch('/api/user')
↓
我们的假 fetch(检查是否需要 Mock)
↓
需要 Mock?
├─ 是 → 直接返回假数据 ✅
└─ 否 → 调用真正的 fetch 发送请求 → 服务器
具体实现(简化版)
// 1. 保存原始方法
const 真正的fetch = window.fetch;
// 2. 替换成我们的方法
window.fetch = function(url) {
// 3. 检查是否需要 Mock
if (url.includes('/api/user')) {
console.log('拦截成功!返回假数据');
return Promise.resolve({
json: () => ({ name: 'Mock User' })
});
}
// 4. 不需要 Mock,调用原始方法
return 真正的fetch(url);
};
关键点:
- 必须先保存原始方法,否则无法发送真实请求
- 必须在网页加载前执行,否则拦截不到早期请求
- XMLHttpRequest 同理,重写
open和send方法
项目结构
quick-mock/
├── manifest.json # 插件配置文件
├── popup.html # 弹窗页面
├── popup.css # 弹窗样式
├── popup.js # 弹窗逻辑
├── content.js # 内容脚本
└── injected.js # 注入脚本
🚀 快速开始
获取代码
- GitHub:github.com/Teernage/qu… ⭐ 如果对您有帮助欢迎Star
- Gitee:gitee.com/xuzhenxin11…
一键安装
- 打开Chrome浏览器
- 地址栏输入:
chrome://extensions/ - 打开右上角"开发者模式"
- 点击"加载已解压的扩展程序"
- 选择quick-mock文件夹
- 搞定!扩展安装完成!
深入解析:从零开始理解插件实现
界面层(用户交互)
1. popup.html - 插件的"脸面"- 作用:用户点击插件图标看到的弹窗界面
- 包含:输入框、按钮、规则列表
🔗 文件源码链接:
2. popup.css - 界面样式
🔗 文件源码链接:
3. popup.js - 界面逻辑
作用:处理用户配置mock操作
功能:
- 点击"添加规则" → 保存到 chrome.storage
- 点击"删除" → 从 chrome.storage 移除
- 渲染规则列表
🔗 文件源码链接:
核心层(拦截逻辑)
4. content.js - "中间人"
作用:连接插件和网页的桥梁
能力:
- 可以访问 Chrome API(读取 chrome.storage)
- 可以访问网页 DOM
- 不能访问网页的 JavaScript 环境(window.fetch)
职责:
- 把 injected.js 注入到网页
- 读取用户配置的 Mock 规则
- 通过 postMessage 与 injected.js 通信
🔗 文件源码链接:
5. injected.js - "劫匪"
作用:真正执行拦截的代码
能力:
-
可以访问网页的 JavaScript 环境(window.fetch) -
可以重写 fetch 和 XMLHttpRequest -
不能访问 Chrome API(chrome.storage)
职责:
- 重写 window.fetch
- 重写 XMLHttpRequest.prototype.open/send
- 拦截请求,询问 content.js 是否需要 Mock
- 根据回复决定返回假数据还是真实请求
🔗 文件源码链接:
💡 深入理解脚本通信机制
如果你想深入了解 background/service worker、content script、injected script、popup 之间真实的运行上下文与消息链路,强烈推荐阅读: 〈大部分人都错了!这才是 Chrome 插件多脚本通信的正确姿势〉 这篇文章用原理+示例拆解了常见误区与正确做法,能帮你更透彻地理解本插件的通信机制。
配置层
6. manifest.json - 插件的"身份证"
🔗 文件源码链接:
安装扩展
- 打开Chrome浏览器
- 地址栏输入:
chrome://extensions/ - 打开右上角"开发者模式"
- 点击"加载已解压的扩展程序"
- 选择quick-mock文件夹
- 搞定!扩展安装完成!
文件协作流程(完整链路)
第一阶段:用户配置规则
┌─────────────┐
│ 用户操作 │ 在 popup.html 输入 Mock 规则
└──────┬──────┘
│
↓
┌─────────────┐
│ popup.js │ 点击"添加"按钮
└──────┬──────┘
│
│ chrome.storage.local.set({ mockRules: [...] })
↓
┌─────────────┐
│Chrome Storage│ 持久化存储规则(关闭浏览器也不丢失)
└─────────────┘
第二阶段:页面加载时注入脚本
用户打开网页(如 https://example.com)
↓
┌─────────────┐
│manifest.json│ 检测到匹配的 URL
└──────┬──────┘
│
│ 自动注入
↓
┌─────────────┐
│ content.js │ 在网页上下文运行(但在隔离沙箱)
└──────┬──────┘
│
│ 创建 <script> 标签
│ script.src = chrome.runtime.getURL('injected.js')
│ document.head.appendChild(script)
↓
┌─────────────┐
│ injected.js │ 在网页真实环境运行
└──────┬──────┘
│
│ window.fetch = 我们的假fetch;
↓
拦截就绪!
第三阶段:拦截请求(实时通信)
网页代码执行:fetch('/api/user')
↓
┌─────────────────────┐
│ injected.js
│ 我们的假 fetch 被调用
└──────┬──────────────┘
│
│ window.postMessage({
│ type: 'MOCK_REQUEST',
│ url: '/api/user',
│ method: 'GET'
│ })
↓
┌─────────────────────┐
│ content.js │ 监听 message 事件
└──────┬──────────────┘
│
│ 1. 读取 chrome.storage.local
│ 2. 遍历规则,检查是否匹配
│ 3. 找到匹配规则
↓
│ window.postMessage({
│ type: 'MOCK_RESPONSE',
│ shouldMock: true,
│ mockData: { name: 'Mock User' }
│ })
↓
┌─────────────────────┐
│ injected.js │ 收到回复
└──────┬──────────────┘
│
│ if (shouldMock) {
│ return new Response(JSON.stringify(mockData));
│ } else {
│ return 真fetch(url); // 调用原始方法
│ }
↓
网页代码收到响应:{ name: 'Mock User' }
为什么要分 content.js 和 injected.js?
只用 Content Script 行不行?
不行! 因为 Content Script 运行在隔离的沙箱中,无法访问网页的 window.fetch。
// content.js 中这样做是无效的!
window.fetch = function() {
console.log('拦截失败!'); // 网页看不到这个修改
}
只用 Injected Script 行不行?
不行! 因为 Injected Script 无法访问 chrome.storage 等 Chrome API,无法读取用户配置的 Mock 规则。
### 正确方案:两者配合
用户配置 Mock 规则
↓
存储到 chrome.storage (Popup)
↓
读取规则 (Content Script) ← 可以访问 Chrome API
↓
通过 postMessage 通信
↓
拦截 fetch (Injected Script) ← 可以修改 window.fetch
通俗比喻
content.js = 银行金库管理员
- 有钥匙(Chrome API 权限)
- 能读取保险箱(chrome.storage)
- 但不能直接接触客户(网页 JavaScript)
injected.js = 银行大堂经理
- 直接面对客户(网页代码)
- 能拦截客户请求(重写 fetch)
- 但没有金库钥匙(无法访问 chrome.storage)
解决方案:两人用对讲机(postMessage)通信
客户发起请求 → 大堂经理拦截 → 对讲机问管理员"要不要放行"
→ 管理员查保险箱 → 回复"不放行,给假钞" → 大堂经理返回假钞
关键技术点总结
1. 为什么要用 run_at: "document_start" ?
// manifest.json
"run_at": "document_start" // 在 HTML 解析前运行
原因: 如果网页在插件加载前就执行了 fetch('/api/data'),我们就拦截不到了。
2. 为什么要用 web_accessible_resources ?
// manifest.json
"web_accessible_resources": [{
"resources": ["injected.js"],
"matches": ["<all_urls>"]
}]
原因: 默认情况下,网页无法加载插件内部的文件(跨域限制)。这个配置相当于给 injected.js 开了"绿色通道"。
3. 为什么用 postMessage 而不是全局变量?
// ❌ 错误做法
window.mockRules = [...]; // content.js 设置
console.log(window.mockRules); // injected.js 读取(读不到!)
// ✅ 正确做法
window.postMessage({ type: 'MOCK_REQUEST' }, '*'); // injected.js 发送
window.addEventListener('message', (e) => { ... }); // content.js 接收
原因: content.js 和 injected.js 虽然在同一个网页,但 JavaScript 环境是隔离的,就像两个平行世界,只能通过 postMessage 这个"传送门"通信。
4. 为什么要保存原始 fetch、xhr?
const originalFetch = window.fetch; // 必须先保存
window.fetch = async function(url) {
if (needMock) {
return mockResponse;
}
return originalFetch(url); // 不 Mock 时调用原始方法
};
原因: 如果不保存,所有请求都会被拦截,无法发送真实请求。
总结
核心要点
- 根本原理:重写
window.fetch和XMLHttpRequest,在网页代码执行前劫持请求 - 为什么分两个脚本:content.js 能读插件配置,injected.js 能拦截请求
- 怎么通信:
postMessage(唯一方式) - 什么时候注入:
document_start(越早越好) - 为什么能拦截:在网页代码执行前就替换了原生方法
适用场景
- 前端开发时,后端接口还没好
- 调试线上 Bug,想临时改返回数据
- 演示 Demo,不想依赖真实服务器
- 自动化测试,需要稳定的 Mock 数据
- 接口文档不完善,想自己造数据测试
最后
如果这个插件帮到了你,欢迎:
- 如果觉得对您有帮助,欢迎点赞 👍 收藏 ⭐ 关注 🔔 支持一下!
- ⭐ GitHub Star 支持 github.com/Teernage/qu…
- 💬 评论区聊聊你的使用场景
这个插件会持续优化,支持更丰富的配置项、更好的交互等
如果你有好的想法,欢迎在评论区或 GitHub Issue 提出!
如果觉得对您有帮助,欢迎点赞 👍 收藏 ⭐ 关注 🔔 支持一下!
往期实战推荐: