🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

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

上週四下午,我正在調樣式,突然電腦風扇開始咆哮。

打開任務管理器:內存佔用 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 數據
  • 持續優化中:性能、匹配規則和用戶體驗會不斷迭代,歡迎反饋與建議

根本原理:劫持瀏覽器的網絡請求方法

一句話總結

在網頁加載前,偷偷替換掉瀏覽器原生的 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. 搞定!擴展安裝完成!

安裝mock插件.gif

深入解析:從零開始理解插件實現

界面層(用戶交互)

1. popup.html - 插件的"臉面"- 作用:用戶點擊插件圖標看到的彈窗界面

  • 包含:輸入框、按鈕、規則列表

🔗 文件源代碼鏈接

  • GitHub: github.com/Teernage/qu…
  • Gitee: gitee.com/xuzhenxin11…

2. popup.css - 界面樣式

🔗 文件源代碼鏈接

  • GitHub: github.com/Teernage/qu…
  • Gitee: gitee.com/xuzhenxin11…

3. popup.js - 界面邏輯

作用:處理用戶配置mock操作
功能:

  • 點擊"添加規則" → 保存到 chrome.storage
  • 點擊"刪除" → 從 chrome.storage 移除
  • 渲染規則列表

🔗 文件源代碼鏈接

  • GitHub: github.com/Teernage/qu…
  • Gitee: gitee.com/xuzhenxin11…

核心層(攔截邏輯)

4. content.js - "中間人"

作用:連接插件和網頁的橋梁
能力:

  • 可以訪問 Chrome API(讀取 chrome.storage)
  • 可以訪問網頁 DOM
  • 不能訪問網頁的 JavaScript 環境(window.fetch)

職責:

  1. 把 injected.js 注入到網頁
  2. 讀取用戶配置的 Mock 規則
  3. 通過 postMessage 與 injected.js 通信

🔗 文件源代碼鏈接

  • GitHub: github.com/Teernage/qu…
  • Gitee: gitee.com/xuzhenxin11…

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. 根據回覆決定返回假數據還是真實請求

🔗 文件源代碼鏈接

  • GitHub: github.com/Teernage/qu…
  • Gitee:gitee.com/xuzhenxin11…

配置層

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

🔗 文件源代碼鏈接

  • GitHub: github.com/Teernage/qu…
  • Gitee:gitee.com/xuzhenxin11…

安裝擴展

  1. 打開Chrome瀏覽器
  2. 地址欄輸入:chrome://extensions/
  3. 打開右上角"開發者模式"
  4. 點擊"加載已解壓的擴展程序"
  5. 選擇quick-mock文件夾
  6. 搞定!擴展安裝完成!

文件協作流程(完整鏈路)

第一階段:用戶配置規則

┌─────────────┐
│  用戶操作    │ 在 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 時調用原始方法
};

原因: 如果不保存,所有請求都會被攔截,無法發送真實請求。

總結

核心要點

  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 提出!

如果覺得對您有幫助,歡迎點贊 👍 收藏 ⭐ 關注 🔔 支持一下!

往期實戰推薦:


原文出處:https://juejin.cn/post/7570984257666056238


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝13   💬4   ❤️4
437
🥈
我愛JS
📝1   💬3   ❤️2
46
🥉
酷豪
1
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付