哈喽大家好👋 作为前端开发,你一定在调试接口时遇到过这种诡异情况: 明明代码里只写了一次POST请求,打开浏览器Network一看,却硬生生出现了两条请求记录:一条OPTIONS、一条POST。
很多人第一反应:我哪里写bug了?接口重复触发了?后端是不是有问题?
今天完全站在前端开发者的视角,从我们写代码、踩坑、排错、优化的全过程,把预检机制讲透,看完既能彻底弄懂原理,面试也能直接脱口而出惊艳面试官。
一、先纠正误区:从来就没有「两次POST」
首先我们站在前端视角先看清真相: ✅ 你代码里的 axios.post / fetch ,永远只会执行1次业务请求 ✅ 多出来的那一次,是浏览器偷偷帮我们发的 OPTIONS 预检请求 ✅ 整个过程,完全是浏览器行为,前端代码感知不到、也控制不了
整个流程(前端眼里到底发生了什么)
1. 我们在JS里发起了一个跨域的网络请求 2. 浏览器立刻在底层判断:这个请求是不是「非简单请求」 3. 如果判定为非简单请求:- 浏览器自动、先发送一次 OPTIONS 预检询问后端
- 询问内容:这个跨域请求,你服务器允许接收吗?允许我带这些头、这些方法吗? 4. 后端返回跨域许可的响应头 5. 浏览器确认合规之后,才正式发送我们写的POST业务请求 6. 如果后端预检拒绝,浏览器直接拦截,控制台直接抛出熟悉的 CORS跨域错误 ,真实POST根本不会发出去
二、前端必记:什么情况,一定会触发预检?
划重点:只有跨域场景才会有预检,同源请求永远不会有OPTIONS
跨域的时候,只要满足下面任意一条,浏览器就铁定给你发预检👇
1. 请求方法不是GET/POST/HEAD 我们前端常用的RESTful接口: PUT 、 DELETE 、 PATCH ,全部默认触发预检。 2. 我们主动加了自定义/非标准请求头 这是90%前端踩坑的元凶:- 给请求加了 token 、 Authorization 登录鉴权头
- 自定义了业务标识头、设备信息头等
- 哪怕只是多带了一个额外的Header,就会触发预检 3. POST的Content-Type改成了 application/json 这绝对是新手最多踩的坑! 我们现在前端几乎所有POST接口,都会传JSON数据,就必须设置:js
axios.post(url, data, { headers: { 'Content-Type': 'application/json' } })
而 application/json 不属于浏览器认定的「简单表单类型」,直接判定为非简单请求,强制触发OPTIONS预检。
什么是不会触发预检的「简单请求」?
必须同时全部满足:
- 请求方法:只能是 GET / POST / HEAD
- 没有任何自定义请求头
- Content-Type 只能是以下3种:- application/x-www-form-urlencoded (普通表单)
- multipart/form-data (文件上传表单)
- text/plain
三、前端开发:预检带来的实际痛点
站在我们日常开发的角度,多余的预检可不是小事:
1. 多一次HTTP请求,接口整体耗时翻倍,页面加载、接口响应变慢 2. 高频率调用的接口,大量OPTIONS请求严重拖累性能 3. 开发调试时,抓包混乱,排查问题难度变大 4. 后端预检配置稍有不对,前端直接跨域报错,非常影响开发效率
四、前端主导的优化方案(面试加分项,全是我们能掌控的)
很多优化大家都只会说后端配置,其实前端也有大量可落地、可主导的优化手段👇
- 开发环境:用代理彻底消灭跨域+预检
本地开发阶段,最优解永远是配置脚手架代理
- Vite / Webpack 配置 proxy 代理
- 所有接口请求打到本地开发服务,由开发服务器转发请求到后端
- 对于浏览器来说,请求就是同源请求,直接从根源消除跨域、消除预检
- 再也不用本地调试来回折腾后端跨域配置,开发体验拉满
vite.config.js 示例:
js
export default defineConfig({ server: { proxy: { '/api': { target: 'http://后端真实接口地址', changeOrigin: true } } } })
- 生产环境:推动后端配置预检缓存
和后端配合,在OPTIONS预检响应头加上:
http
Access-Control-Max-Age: 86400
- 含义:预检成功的结果,浏览器缓存86400秒=1天
- 缓存有效期内,相同接口、相同配置的跨域请求,再也不会重复发OPTIONS
- 前端零改动,接口性能直接大幅提升,性价比最高的优化手段
- 前端业务层面:尽量适配简单请求,减少预检触发
- 非强业务必需,尽量精简自定义请求头,不要冗余加Header
- 可以兼容的场景,优先使用表单格式提交数据,避免默认 application/json 带来的预检
- 区分普通接口和鉴权接口,无登录的公开接口,尽量保持简单请求格式
- 生产部署:前后端同域部署
终极根治方案: 前端页面、后端接口,部署在同一个域名、同一个协议、同一个端口下 不存在跨域,自然永远不会有预检OPTIONS请求,彻底解决所有跨域相关问题。
五、前端面试满分背诵版回答
面试官问:跨域POST为什么会发两次请求? ✅ 满分回答(纯前端视角):
首先这不是两次POST,多出的一次是浏览器CORS机制自动发起的OPTIONS预检请求。
1. 当我们前端发起跨域请求时,浏览器会主动区分简单请求和非简单请求。 2. 如果我们的请求使用了PUT/DELETE等方法、携带了token等自定义请求头、或者POST传递JSON数据,就会被判定为非简单请求。 3. 浏览器会先发送OPTIONS预检,询问后端服务器是否允许该跨域请求。只有后端预检通过,浏览器才会发送我们真正的POST业务请求;预检失败则直接抛出跨域错误。 4. 优化上,开发环境我们可以配置本地代理规避跨域;生产环境可以推动后端配置Access-Control-Max-Age缓存预检结果,同时前端尽量适配简单请求、精简请求头,减少不必要的预检开销。
六、前端常见避坑总结
1. ❌ 前端代码没法禁用、关闭OPTIONS预检,这是浏览器的标准安全策略 2. ❌ 不是POST专属,GET带自定义头、PUT/DELETE请求一样会触发预检 3. ❌ 跨域报错不一定是后端没开跨域,也可能是OPTIONS预检请求就失败了 4. ✅ 预检的本质,是浏览器保护用户,防止恶意网站发起非法跨域攻击
站在前端的角度理解,预检一点都不玄乎: 它就是浏览器给跨域请求加的一道前置安全门。 看懂了触发规则、明白我们前端能做什么优化,以后不管是排查接口两次请求、解决跨域报错、还是面试应答,都能稳准狠搞定。
觉得内容干货满满的话,点赞+收藏,以后排查跨域、网络问题随时翻出来看~