ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

前端Mock劫持Ajax实战指南:从XHR拦截到工程化方案

2026/9/14 4:46:50 拓冰建站 浏览量
前端Mock劫持Ajax实战指南:从XHR拦截到工程化方案 作为一个常年跟后端联调掰扯、又经常被产品临时改需求的前端我对Mock这件事的感情很复杂。一方面没有Mock我压根没法在后端接口没写好的时候按时交付页面另一方面要是Mock用得不够“聪明”它带来的坑能让你在线上环境找bug找到怀疑人生。之前我在团队里推过一轮“前后端并行开发”的流程核心就是靠Mock把界面和交互先跑起来。结果不到两周就有同事跑过来说“页面卡死了”“接口返回的数据不对”“明明代码里写死了数据怎么请求还在发”。那段时间我几乎每天都在帮人擦屁股也正是在这个过程里把Mock劫持Ajax的坑踩了个遍。这篇文章不聊那些理论层面的“Mock是什么”就聊实战从轻量拦截到框架级方案、从原生XHR劫持到路由级转发每个方案我都会给出可直接抄作业的代码和参数选择逻辑。后面还附了一份我从翻车现场总结出来的排查手册希望能让你少走几个弯路。1. Mock劫持Ajax的核心思路与方案选型先说一个很反直觉的点很多前端新手理解的Mock就是在代码里写死几组假数据然后等后端好了再手动替换。这不算错但远远不够。真正的Mock劫持指的是在你的代码发起Ajax请求的那一刻由Mock层把请求拦下来返回你预设的数据整个过程对业务代码完全透明。这样做的好处很直接你的页面代码从头到尾走的都是真实请求链路后端的URL、参数格式、响应结构从一开始就被“钉死”了后面切换真实接口时只需要把Mock关掉不用改业务代码。项目越复杂这种“以假乱真”的价值越明显。1.1 为什么选择“劫持”而不是“改动代码”我记得有一次项目里有个数据报表页面依赖四个接口的数据做聚合计算。当时后端只完成了两个接口另外两个要等三天。如果靠“在页面里写死数据”我至少得写两套渲染逻辑一套是假的静态数据一套是真的接口数据。等到后端就绪我还得小心翼翼地删掉假逻辑生怕漏改一处导致线上出问题。但用劫持方案就不一样了。我可以在Mock配置里提前把四个接口的数据结构定义好页面只需要按约定的结构写一套渲染逻辑。至于数据是Mock返回的还是后端返回的页面根本不关心。这就像你在家试穿网购的衣服试的是衣服本身合不合身而不是先把自己饿瘦了再等衣服到货。1.2 三种主流劫持方案的对比与场景匹配目前前端领域常用的Mock劫持方案主要有三条路线每条的优缺点和适用场景差别很大我直接用表格做个对比方案劫持层面侵入性适用场景典型工具正则/字符串替换URL请求入口低简单接口代理、临时改个路径自写脚本、YAPIAxios/Fetch拦截器请求库内部中项目里统一用了axios或fetchaxios-mock-adapterService Worker浏览器网络层高但最干净复杂项目、需要离线开发Mock Service WorkerMSW从这张表能看出来没有“最好的方案”只有“当前阶段最合适的方案”。如果你的项目就是用axios封装了所有请求那拦截器方案是最快的如果你的项目要同时处理图片、脚本、接口各种资源那Service Worker这种网络层面的方案更彻底。我在团队里推行的方法是能用拦截器就别上SW除非跨域或离线需求真的很强。原因很简单SW的调试难度和维护成本都更高对刚接触Mock的同事不够友好。1.3 一个容易被忽略的认知Mock不是后端没做好才用的这里我想多啰嗦一句。很多人觉得Mock是“后端拖后腿时的补救措施”其实这是对Mock最大的误解。一个设计良好的Mock层核心价值是帮你把前端代码与外部依赖解耦。哪怕后端接口已经全部就绪你在做复杂交互调试、异常场景复现比如超时、500、返回空数组时Mock依然比改后端代码方便得多。我自己的习惯是哪怕后端已经提供了可用接口我也会在本地留一套Mock配置。不是为了造假而是为了能随时复现“接口挂了”的极端情况看看页面到底会怎么表现。这在你处理线上紧急工单时特别有用——你能在本地快速还原问题而不是干等着运维反馈。2. 从零开始手写一个Mock劫持Ajax的最小实现聊完选型逻辑我们直接进入实操。要从根源上理解Mock劫持Ajax的原理最好的办法不是立刻引入各种库而是自己动手写一个最简版本。你会惊讶地发现核心代码量比想象中少得多。2.1 拦截XMLHttpRequest看懂浏览器请求的最小闭环在浏览器环境下几乎所有Ajax请求最终还是通过XMLHttpRequest简称XHR或者fetch API发出的。jQuery的ajax、axios在浏览器端的适配器底层都是XHR。所以只要我们能在全局替换掉window.XMLHttpRequest就能实现“上游拦截”。先看这段最小实现// mock-xhr.js (function () { const RealXHR window.XMLHttpRequest; function MockXHR() { const realXhr new RealXHR(); this._realXhr realXhr; this._mockUrl null; this._mockData null; // 把真实XHR实例上的关键属性和方法透传 Object.defineProperty(this, readyState, { get: () realXhr.readyState, }); Object.defineProperty(this, status, { get: () realXhr.status, }); Object.defineProperty(this, responseText, { get: () realXhr.responseText, }); this.open function (method, url, async) { this._method method; this._url url; // 注意这里不能调用真实xhr的open因为我们要决定是否劫持 }; this.send function (body) { if (this._matchMock(this._url)) { // 命中Mock直接模拟异步返回 setTimeout(() { this._mockData this._getMockData(this._url); this._realXhr.readyState 4; this._realXhr.status 200; this._realXhr.responseText JSON.stringify(this._mockData); if (this.onreadystatechange) { this.onreadystatechange(); } if (this.onload) { this.onload(); } }, 100); } else { // 未命中走真实请求 realXhr.open(this._method, this._url, true); realXhr.send(body); } }; this.setRequestHeader function (header, value) { realXhr.setRequestHeader(header, value); }; } MockXHR.prototype._matchMock function (url) { return window.__MOCK_CONFIG__ window.__MOCK_CONFIG__.hasOwnProperty(url); }; MockXHR.prototype._getMockData function (url) { return window.__MOCK_CONFIG__[url]; }; window.XMLHttpRequest MockXHR; })();这段代码的核心逻辑就三件事在send之前先判断请求URL是否在Mock配置表里如果命中就不发真实请求用setTimeout模拟异步返回通过修改readyState和status让回调正常触发。这只是一个教学版的实现真实项目里你还要处理onerror、withCredentials、timeout、upload进度等场景。但它的存在价值是让你看清所谓“劫持”本质上是替换了浏览器的默认行为而不是什么黑魔法。2.2 轻量级进阶用正则做灵活匹配避免URL写死上面的代码有个很明显的问题Mock配置表的key是完整URL如果后端接口路径里带了动态参数比如/api/user/123就没办法精确匹配了。实际开发中这种动态路径太常见了所以我一般会把匹配规则升级成正则匹配。// 匹配规则升级支持正则 MockXHR.prototype._matchMock function (url) { const config window.__MOCK_CONFIG__; if (!config) return false; return Object.keys(config).some((pattern) { return new RegExp(pattern).test(url); }); }; MockXHR.prototype._getMockData function (url) { const config window.__MOCK_CONFIG__; const matchedKey Object.keys(config).find((pattern) { return new RegExp(pattern).test(url); }); return config[matchedKey]; };配置表就变成这样window.__MOCK_CONFIG__ { /api/user/\\d$: { id: 1, name: 张三, age: 28 }, /api/order/list: { code: 0, data: [] }, };这里有个细节值得注意正则表达式在配置表里是用字符串写的所以\d要写成\\d。我第一次用这个方案时就是被这个转义坑的匹配了半天不生效还以为是逻辑写错了。这也是“看起来简单做起来踩坑”的典型。2.3 代码的局限性为什么企业级项目不推荐自己维护手写实现有一个逃不开的问题随着Mock规则越来越多代码会变得越来越不可控。你不仅要处理各种请求方法GET/POST/PUT/DELETE还要处理查询参数、请求体、请求头甚至要模拟延迟、超时、网络错误。这些逻辑堆在一起维护成本会呈指数级上升。所以我现在的建议是手写代码适合学习原理、适合一次性脚本但如果是长期项目直接用社区维护的成熟库更稳妥。用别人的库不只是因为代码现成还因为那些边边角角的兼容性问题已经被大量用户踩过坑了。2.4 实战推荐axios-mock-adapter的配置与参数说明如果你的项目里统一用的是axios那我最推荐的Mock方案是axios-mock-adapter。理由很简单它直接拦截axios的请求适配器不需要改window.XMLHttpRequest也不影响非axios发起的请求侵入性控制得刚刚好。安装和基本用法如下npm install axios-mock-adapter --save-devimport axios from axios; import MockAdapter from axios-mock-adapter; // 创建一个mock实例并绑定到axios实例上 const mock new MockAdapter(axios, { delayResponse: 300 }); // 模拟GET请求带正则匹配动态参数 mock.onGet(/\/api\/user\/\d/).reply((config) { const userId config.url.split(/).pop(); return [200, { code: 0, data: { id: userId, name: 用户 userId, role: admin, }, }]; }); // 模拟POST请求读取请求体中的参数 mock.onPost(/api/login).reply((config) { const { username, password } JSON.parse(config.data); if (username admin password 123456) { return [200, { code: 0, token: mock-token-123, expires: 7200 }]; } return [401, { code: 1001, message: 用户名或密码错误 }]; }); // 模拟网络超时 mock.onGet(/api/slow).timeout(); // 模拟服务器500错误 mock.onGet(/api/error).reply(500, { message: 服务器内部错误 });这段代码里的每个onGet、onPost都像一个路由规则按顺序从上到下匹配命中了就返回预设内容。注意看第三段代码mock.onGet(/api/slow).timeout()这一行能模拟接口超时。这在调试前端loading状态、超时重试逻辑时特别有用省去了找后端帮忙制造故障的时间。2.5 必踩的坑包装过的axios实例需要单独绑定这里有个高频翻车点我必须拿出来单独讲。很多公司的前端项目会自己封装一个request.js内部创建了一个独立的axios实例// request.js import axios from axios; const service axios.create({ baseURL: process.env.VUE_APP_BASE_URL, timeout: 15000, }); service.interceptors.request.use(/* ... */); service.interceptors.response.use(/* ... */); export default service;如果你像我之前一样在入口文件里写new MockAdapter(axios, ...)你会发现Mock根本不生效。原因很简单业务代码里使用的是自定义的service实例而不是全局的axios对象。MockAdapter绑定在全局axios上拦截链路压根没有经过它。正确做法是绑定到你的服务实例上import service from /utils/request; import MockAdapter from axios-mock-adapter; // 关键绑定到service实例而不是全局axios const mock new MockAdapter(service, { delayResponse: 200 }); mock.onGet(/api/user/info).reply(200, { code: 0, name: 绑定了service实例, });记住一个原则MockAdapter绑定的axios实例必须和业务代码里发请求的那个实例是同一个。这个坑我至少帮三个同事排查过每次都是查半天才发现是实例绑错了。3. 你一定会遇见的翻车现场切换Tab页面卡死了前面铺垫了这么多基础内容现在进入真正的“翻车实录”。我选一个最具代表性的案例来完整复盘在Tab页切换场景下Mock劫持导致的页面卡顿与白屏问题。这个问题几乎每个用过Mock的人都会遇到但很多人不知道根因在哪。3.1 问题现象是Mock的锅还是代码的锅事情是这样的项目里有个订单管理页面顶部有“全部订单”“待付款”“已发货”“已完成”四个Tab。每次切换Tab页面会重新请求对应的订单列表接口。上线前我在本地Mock环境检验功能发现一个诡异的现象第一次切换Tab一切正常第二次切换到某个Tab时页面就开始卡顿连续快速切换五次左右页面直接白屏。当时我第一反应是页面性能问题可能是渲染太多DOM导致的。但看了Performance面板之后发现CPU占比并不高反而网络请求列表里出现了大量挂起的请求。顺着这个线索继续查才发现根子出在Mock的匹配逻辑上。3.2 根因分析Mock规则混乱导致请求堆积用axios-mock-adapter的时候我写了一个很“粗犷”的拦截规则mock.onGet(/\/api\/order/).reply((config) { // 根据URL里的status参数返回不同数据 const status new URL(config.url).searchParams.get(status); return [200, generateOrderList(status)]; });这个正则匹配了所有包含/api/order的GET请求理论上没问题。但我忽略了一点axios-mock-adapter的规则匹配是有顺序的一旦前面的规则没有精确匹配它会继续尝试后面的规则直到全部遍历完。而我在这个规则之前还定义了一个模糊匹配mock.onGet(/\/api\/.*/).reply(200, {});于是一个糟糕的连锁反应出现了当请求URL是/api/order/list?status1page2时正则/\/api\/.*/先被尝试它能匹配所以请求被立即返回{}。而更具体的/api/order规则根本没有机会执行。页面拿到空数据渲染异常、状态混乱再叠加路由切换时旧请求未取消就出现了“越切越卡”的现象。3.3 定位问题的完整排查链路从Performance到Network我一般排查这种问题有个固定套路这里分享下具体步骤打开Chrome DevTools的Performance面板录一段“连续切换Tab”的操作看主线程的Tasks分布如果发现大量XHR任务堆积切到Network面板勾选“Fetch/XHR”过滤看请求状态如果发现一堆“pending”状态的请求基本可以锁定是请求层的问题与渲染无关在Network里点开某个pending请求查看它所在的并发队列和延迟时间回到代码里逐个检查MockAdapter的规则匹配顺序重点看是否有“宽泛规则抢在具体规则前面”的情况。我这套链路走完大概花了二十分钟其中一半时间都在怀疑自己写的业务代码。所以也给你们提个醒遇到页面卡死先把请求层排查干净了再去看渲染层。3.4 解决方案窄化Mock规则并合理组织匹配顺序修复方案其实不复杂核心就两点规则要窄顺序要正确。首先是窄化规则。把模糊匹配/\/api\/.*/改成具体的路径mock.onGet(/api/user/info).reply(200, mockUserInfo); mock.onGet(/\/api\/order\/list/).reply((config) { // 具体的订单列表逻辑 }); mock.onGet(/\/api\/order\/detail\/\d/).reply((config) { // 具体的订单详情逻辑 });其次是规则顺序。把所有onGet、onPost按“精确到模糊”的顺序排列像路由表一样管理。axios-mock-adapter虽然自带“先到先得”的匹配机制但我们在书写时就该把顺序控制好不能指望别人去猜。最后是清理机制。如果项目里存在定时器或者轮询请求记得在Mock切走时清理干净避免请求在后台无限堆积。我习惯在Mock配置里额外导出一个reset方法统一处理这些清理逻辑。这次翻车让我学到一个很深的教训Mock规则写得太宽等于没有Mock。它表面上帮你“什么都拦一下”实际上给了你一堆错误数据掩盖了真正的问题。4. 进阶vue3 TS项目里的Mock实践方案随着团队项目逐步迁移到Vue3 TypeScript我又花了些时间把Mock方案做了升级。这里直接说结论在Vue3 TS的工程里我最终采用的是“API层抽象 Vite代理 独立的Mock服务”三段式结构。这套组合既保留了Mock的灵活性又解决了TypeScript类型约束下的代码提示问题。4.1 为什么单独提Vue3 TS类型与模块化带来的新问题Vue3项目里普遍使用了Composition API和模块化组织代码这给Mock带来的挑战是每个接口的入参出参都需要有类型定义Mock数据如果与类型不一致会在运行时暴露奇怪的问题。比如TypeScript编译期没报错但Mock返回的数据少了一个字段页面渲染时直接undefined而你很难第一时间想到是Mock数据的问题。另一个痛点是Vue3项目通常配合Vite使用Vite的开发服务器本身就提供了代理能力。如果我们把Mock直接挂在window.XMLHttpRequest上不仅绕过了Vite的代理配置还会在多个模块之间产生全局污染。4.2 三段式结构接口抽象层、Mock数据层、条件启用这三段式结构的组织方式如下第一段是接口抽象层api/目录所有请求都通过这里发出去返回类型用TS定义好// api/user.ts import request from /utils/request; export interface IUserInfo { id: number; name: string; avatar: string; roles: string[]; } export function getUserInfo() { return request.getIUserInfo(/api/user/info); }第二段是Mock数据层mock/目录每个模块维护自己的Mock数据和规则// mock/user.ts import { MockAdapter } from axios-mock-adapter; import type { IUserInfo } from /api/user; export function mockUser(adapter: MockAdapter) { adapter.onGet(/api/user/info).reply(200, { id: 9527, name: Mock老张, avatar: https://example.com/avatar.png, roles: [admin, editor], } satisfies IUserInfo); }使用satisfies语法是TS 4.9的特性它能确保Mock数据在结构上符合IUserInfo但不会像as那样强制断言。这一步能提前拦住“Mock数据少字段”的低级错误。第三段是条件启用通常写在main.ts或独立的mock/index.ts// mock/index.ts import MockAdapter from axios-mock-adapter; import service from /utils/request; import { mockUser } from ./user; import { mockOrder } from ./order; const enabled import.meta.env.VITE_ENABLE_MOCK true; export function setupMock() { if (!enabled) return; const adapter new MockAdapter(service, { delayResponse: 200 }); // 顺序先具体后模糊 mockUser(adapter); mockOrder(adapter); }在main.ts里调用import { setupMock } from ./mock; setupMock();通过环境变量VITE_ENABLE_MOCK控制Mock的开启与关闭。开发环境设为true生产构建自动设为false从源头杜绝“把Mock带上了线上”的惨剧。4.3 Vite代理与Mock的联动跨域与路径前缀问题在Vite项目里如果你配置了server.proxy那Mock劫持的URL就必须特别注意。假设你的request.js里配置的baseURL是/apiVite代理把/api转发到http://backend.example.com那么MockAdapter在匹配时也要匹配/api开头的路径。很多人在这一步被坑是因为他们用了后端的完整地址比如http://backend.example.com/api/user/info作为Mock匹配规则。问题是在开发环境下这个完整地址是会被Vite代理转发掉的请求根本没发到MockAdapter那里。所以你一定要理清楚在Vite代理生效的情况下前端代码里看到的URL是以/api开头的相对路径Mock规则只需要匹配这个相对路径就对了。4.4 环境开关与生产安全让Mock离线上远远的新手最慌的一个问题是“Mock数据会不会不小心跑到线上”。我可以给你一个比较绝对的建议不要只靠环境变量来控制Mock还要在构建层面做“死”。我的做法分三层import.meta.env.VITE_ENABLE_MOCK只有在development模式下才可能为truevite.config.ts里只在serve命令时加载Mock插件执行vite build时彻底不打包Mock代码mock/index.ts里加一个运行时校验检查当前环境process.env.NODE_ENV production时直接抛错。三个保险一起上基本杜绝了Mock“偷渡”到线上的可能性。5. 从劫持到模拟Service Worker方案能不能解决所有问题聊完了最常用的拦截器方案很多人会问那是不是用Service Worker做Mock就一劳永逸了MSWMock Service Worker确实是目前前端Mock领域最“正统”的方案因为它工作在浏览器网络层连图片、脚本、CSS这些资源都能拦截不局限于XHR/Fetch请求。但它也有自己的麻烦事。5.1 Service Worker的优势与劣势不要神化它MSW最大的优势是“真实”。它在浏览器和网络之间架起一层拦截业务代码里发请求跟真实的浏览器环境几乎一致不需要关心你用的是axios还是fetch也不需要关心你封装了几个实例。对于一些老项目或复杂项目这个特性特别诱人。但它的代价是初始化复杂、调试困难、在部分浏览器环境有兼容性问题。你需要在public目录下放mockServiceWorker.js需要在入口处动态注册还需要处理serviceWorker的缓存更新策略。如果你只是想快速给一个页面加几个Mock接口用MSW反而显得笨重。5.2 什么场景下值得切换到MSW我目前只在两种场景下会主动切换到MSW一是项目里不止用了axios还存在大量fetch、图片请求等需要模拟的场景。二是需要做离线开发或沉浸式开发希望整个页面“断网”也能完整运行。除此之外用axios-mock-adapter这种库就足够了。5.3 一个折中方案用猴子补丁实现轻量MSW效果如果你既想要SW的真实网络层拦截又不想被它的复杂度劝退可以试试一个取巧的思路在开发环境用vite-plugin-pwa的思路注册一个临时Service Worker只拦截API前缀的请求。但这个方案对前端基建能力要求偏高说实话不太适合绝大多数团队。我个人还是更倾向于把简单的事做简单。Mock的核心是解决数据依赖问题不是展示技术能力。如果你一上来就上重型方案团队同事的学习成本会很高反而容易放弃使用。6. 常见问题速查表与避坑锦囊文章最后我把这几年用Mock劫持Ajax遇到的高频问题整理成一张速查表。这不是网上随处可见的“知识点大全”而是我自己和团队同事真实踩过、并有对应解决方法的实战索引。6.1 必知必会的排查问题清单问题现象可能原因解决办法Mock不生效请求走了真实接口MockAdapter绑定了错误的axios实例确保绑定的是业务代码中实际使用的实例页面白屏或数据不显示Mock数据缺少字段、字段类型不符合TS定义用satisfies做静态检查运行时打日志校验切换Tab后页面越来越卡Mock规则写得太宽导致请求异常堆积窄化匹配规则精确到具体路径接口返回200但页面没反应Mock返回结构与封装好的响应体结构不一致统一在code/data/message结构下做Mock数据生产环境出现了Mock数据环境变量控制失效Mock代码被打了进去构建层面彻底排除运行时校验NODE_ENV正则配置不生效字符串里的反斜杠未转义\\d而不是\d分页参数被忽略用了固定匹配而非正则/函数匹配改用onGet(/regex/)或读取config.paramsPOST请求参数读不到config.data可能是字符串而非对象使用JSON.parse(config.data)注意判空6.2 高价值实战技巧分页、鉴权、延迟这批细节怎么处理Mock分页是我见过最容易“随便写写”的环节。很多人在Mock里直接写死一页数据导致前端分页器不管怎么切页码都在重复渲染相同内容。正确的做法是让Mock支持从请求参数里读取page和pageSizemock.onGet(/api/order/list).reply((config) { const { page 1, pageSize 10, status } config.params || {}; const filteredList allOrders.filter((item) item.status.includes(status)); const start (page - 1) * pageSize; const end start pageSize; return [200, { code: 0, data: { list: filteredList.slice(start, end), total: filteredList.length, page: Number(page), pageSize: Number(pageSize), }, }]; });注意config.params这里如果你在业务代码里是通过request.get(/api/order/list, { params: { page } })这种方式传参的那axios-mock-adapter会自动解析到config.params上。如果你用的是/api/order/list?page1这种拼接在URL里的方式就得手动用new URL(config.url).searchParams.get(page)去取。两种方式都能用但要保持前后一致。延迟也是一个值得细调的参数。我习惯在Mock初始化时设置一个不大不小的延迟200~300ms这样页面的loading态能被自然触发联调时也不会因为“响应太快”而漏掉loading体验。注意延迟不是越大越好超出一秒会严重影响开发效率。6.3 从工程化视角对Mock的几点建议最后给几条工程化层面的建议是我在推行Mock流程后感受到的切身体会Mock配置要与接口文档同步维护。接口变更时在改代码前先把Mock数据更新了这样能逼迫前后端对接口结构达成一致减少后期扯皮。Mock目录要按业务模块拆分不要把所有规则堆在一个文件里。我的习惯是mock/下对应api/的目录结构一个模块一个文件互不干扰。团队里要有一个人专门负责Mock基建。不是让大家各自为战而是统一封装、统一升级。等有人踩了坑、填了坑其他人只要跟着升级就能少走弯路。留一条“异常出口”。Mock环境偶尔也需要模拟真实环境比如暂时关掉Mock跑一两个真实接口验证联调是否通畅。我一般在Mock配置里加一个名单机制名单内的URL放行、走真实请求名单外的全部Mock。写在最后这大半年的Mock劫持Ajax实战让我最大的改变是不再把Mock当成“临时凑合”的方案而是作为前端工程化里一个正式的组成部分。它不只是帮你在后端写好接口前“撑场子”更是帮你提前暴露各种边界情况、接口结构问题以及你自己的代码在数据缺席时会怎么表现的照妖镜。回想那次把axios实例绑错导致的“Mock失效”其实问题本身很小但它让我意识到了解原理的重要性。如果你不了解底层是怎么把请求“偷梁换柱”的出问题时你根本不知道往哪里排查。所以这篇文章里我特意保留了那段手写XHR劫持的最小实现就是希望你在用各种现成库的时候心里清楚它们的工作机制。如果这篇文章能让你少踩一个Mock的坑那它的价值就超额完成了。以后如果还有关于Mock和前端工程化的新坑我会继续回来补充和你们一起把这锅夹生饭慢慢炖熟。