【前端效率工具】再也不用 APIfox 联调!零侵入 Mock,全程不改代码、不开代理

7,348 阅读9分钟

上周四下午,我正在调样式,突然电脑风扇开始咆哮。

打开任务管理器:内存占用 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 数据时的演示:

mock插件效果.gif

插件功能预览

  • 拦截页面的 fetchajax 请求
  • 规则支持模糊匹配和完全匹配
  • 支持按 HTTP 方法过滤(GET/POST/PUT/DELETE)
  • 自定义返回 JSON 数据
  • 持续优化中:性能、匹配规则和用户体验会不断迭代,欢迎反馈与建议

升级版:AI‑Mock(更强、更好用)

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 同理,重写 opensend 方法

项目结构

quick-mock/
├── manifest.json      # 插件配置文件
├── popup.html         # 弹窗页面
├── popup.css          # 弹窗样式
├── popup.js           # 弹窗逻辑
├── content.js         # 内容脚本 
└── injected.js        # 注入脚本 

🚀 快速开始

获取代码

一键安装

  1. 打开Chrome浏览器
  2. 地址栏输入:chrome://extensions/
  3. 打开右上角"开发者模式"
  4. 点击"加载已解压的扩展程序"
  5. 选择quick-mock文件夹
  6. 搞定!扩展安装完成!

深入解析:从零开始理解插件实现

界面层(用户交互)

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)

职责:

  1. 把 injected.js 注入到网页
  2. 读取用户配置的 Mock 规则
  3. 通过 postMessage 与 injected.js 通信

🔗 文件源码链接

5. injected.js - "劫匪"

作用:真正执行拦截的代码

能力:

  • 可以访问网页的 JavaScript 环境(window.fetch)

  • 可以重写 fetch 和 XMLHttpRequest

  • 不能访问 Chrome API(chrome.storage)

职责:

  1. 重写 window.fetch
  2. 重写 XMLHttpRequest.prototype.open/send
  3. 拦截请求,询问 content.js 是否需要 Mock
  4. 根据回复决定返回假数据还是真实请求

🔗 文件源码链接

💡 深入理解脚本通信机制

如果你想深入了解 background/service worker、content script、injected script、popup 之间真实的运行上下文与消息链路,强烈推荐阅读: 〈大部分人都错了!这才是 Chrome 插件多脚本通信的正确姿势〉 这篇文章用原理+示例拆解了常见误区与正确做法,能帮你更透彻地理解本插件的通信机制。

配置层

6. manifest.json - 插件的"身份证"

🔗 文件源码链接

安装扩展

  1. 打开Chrome浏览器
  2. 地址栏输入:chrome://extensions/
  3. 打开右上角"开发者模式"
  4. 点击"加载已解压的扩展程序"
  5. 选择quick-mock文件夹
  6. 搞定!扩展安装完成!

安装mock插件.gif

文件协作流程(完整链路)

第一阶段:用户配置规则

┌─────────────┐
│  用户操作    │ 在 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 {
       │   returnfetch(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 时调用原始方法
};

原因: 如果不保存,所有请求都会被拦截,无法发送真实请求。

总结

核心要点

  1. 根本原理:重写 window.fetchXMLHttpRequest,在网页代码执行前劫持请求
  2. 为什么分两个脚本:content.js 能读插件配置,injected.js 能拦截请求
  3. 怎么通信postMessage(唯一方式)
  4. 什么时候注入document_start(越早越好)
  5. 为什么能拦截:在网页代码执行前就替换了原生方法

适用场景

  • 前端开发时,后端接口还没好
  • 调试线上 Bug,想临时改返回数据
  • 演示 Demo,不想依赖真实服务器
  • 自动化测试,需要稳定的 Mock 数据
  • 接口文档不完善,想自己造数据测试

最后

如果这个插件帮到了你,欢迎:

  • 如果觉得对您有帮助,欢迎点赞 👍 收藏 ⭐ 关注 🔔 支持一下!
  • GitHub Star 支持 github.com/Teernage/qu…
  • 💬 评论区聊聊你的使用场景

这个插件会持续优化,支持更丰富的配置项、更好的交互等

如果你有好的想法,欢迎在评论区或 GitHub Issue 提出!

如果觉得对您有帮助,欢迎点赞 👍 收藏 ⭐ 关注 🔔 支持一下!

往期实战推荐: