在使用jQuery做前后端分离项目时,后端经常需要通过X-Requested-With这个请求头来判断当前请求是否为AJAX请求。同域环境下,jQuery会自动带上这个头,一切正常。可一旦请求变成跨域,情况就复杂了:直接添加这个自定义头会触发浏览器的预检请求(OPTIONS),如果服务端CORS配置没有放行该头,请求直接失败;而不添加的话,后端又无法正确识别AJAX请求。本文就来聊聊如何利用$.ajaxPrefilter这个全局拦截器,优雅地处理这个问题。

一、先弄清楚X-Requested-With在跨域场景下的问题根源
jQuery在发送AJAX请求时,默认会在请求头中带上X-Requested-With: XMLHttpRequest,很多后端框架(比如Java的Spring、PHP的各种框架)都依赖这个头来区分AJAX请求和普通页面请求。在同域环境下,这个头属于“简单头”的一部分吗?其实不是。X-Requested-With并不在浏览器允许的“简单请求头”列表中,同域时不受CORS约束所以没感觉,一旦跨域,添加它就会让请求从“简单请求”变成“预检请求”。
所谓预检请求,就是浏览器在正式发送请求之前,先向服务端发一个OPTIONS方法的请求,询问服务端是否允许该跨域请求携带这些自定义头。如果服务端返回的Access-Control-Allow-Headers中没有包含X-Requested-With,浏览器就会直接拒绝正式请求,控制台会报类似“Request header field X-Requested-With is not allowed by Access-Control-Allow-Headers”的错误。
所以问题的解法有两个方向:一是服务端在CORS响应头中明确放行X-Requested-With,二是前端只在必要的时候添加这个头。而前端这边,如果每个请求都手写一遍headers配置,代码会非常冗余,这时候$.ajaxPrefilter就派上用场了。
二、$.ajaxPrefilter的运作机制与基本用法
$.ajaxPrefilter是jQuery提供的全局AJAX请求预过滤器,它会在每个$.ajax(包括$.get、$.post、$.getJSON这些封装方法)真正发送之前被调用。你可以把它理解成一个全局的请求拦截器:所有请求的配置对象(options)在发出去之前,都会先经过你注册的处理函数,你有机会在这里修改url、headers、type等任何配置。
它的基本签名是$.ajaxPrefilter(fn),其中fn接收三个参数:options是当前请求的配置对象,originalOptions是未经jQuery处理的原始配置,jqXHR是请求的XMLHttpRequest对象的jQuery包装。此外,你还可以给prefilter指定一个dataTypes过滤条件,比如$.ajaxPrefilter('json script', fn)表示只拦截dataType为json或script的请求。
先看一个最简单的全局添加请求头的写法:
// 全局拦截:为所有AJAX请求添加X-Requested-With头
$.ajaxPrefilter(function(options) {
// 初始化headers对象,避免覆盖用户已配置的头
options.headers = options.headers || {};
options.headers['X-Requested-With'] = 'XMLHttpRequest';
});这段代码对所有请求一视同仁,简单粗暴。但如果你的页面中同时存在同域和跨域请求,同域请求jQuery本来就自动带这个头,重复设置倒也无害。真正需要考虑的是:跨域请求经过这个拦截器添加头之后,是否会导致原本可以“简单请求”直接通过的请求,变成触发预检的请求?答案是肯定的。所以更精细的做法,是判断请求目标是否跨域再决定是否添加。
三、实战:判断跨域后再自动注入请求头
判断一个请求URL是否跨域,可以借助正则表达式提取域名部分,与当前页面的location.host做比较。如果域名不同,就是跨域请求;此时我们再决定是否添加X-Requested-With。这种方案的好处是,同域请求走jQuery默认行为,跨域请求则统一由拦截器处理,业务代码完全不用关心这些细节。
// 提取URL中协议、域名、端口部分的正则
var rurl = /^([\w.+-]+:)?(?:\/\/([^\/?#]*)|[^\/?#]|#)/i;
$.ajaxPrefilter(function(options) {
// 如果当前请求没有显式配置crossDomain,则根据URL自动判断
if (options.crossDomain === undefined) {
var parts = rurl.exec(options.url);
options.crossDomain = !!(parts &&
(parts[1] !== location.protocol || parts[2] !== location.host));
}
// 跨域请求时才添加X-Requested-With头
if (options.crossDomain) {
options.headers = options.headers || {};
options.headers['X-Requested-With'] = 'XMLHttpRequest';
}
});这段代码借鉴了jQuery源码中判断跨域的思路。先尝试从URL中解析出协议和主机部分,如果解析结果与当前页面的location.protocol和location.host不一致,就标记为跨域。注意location.host包含端口号,而location.hostname不包含,判断时要保持口径一致,否则8080这类非默认端口的场景会判断出错。
还有一种更省事的方式:直接利用jQuery自己维护的options.crossDomain属性。jQuery在内部处理请求时,如果发现请求目标是跨域的,会把这个标志设置为true,同时jQuery在跨域时默认不会发送X-Requested-With头(正是为了避免触发预检)。所以如果你确定服务端已经放行了该头,只需在prefilter中根据这个标志补回头部即可:
$.ajaxPrefilter(function(options) {
// 跨域时jQuery会默认剥离X-Requested-With,这里手动补回
if (options.crossDomain) {
options.headers = options.headers || {};
options.headers['X-Requested-With'] = 'XMLHttpRequest';
}
});四、服务端CORS配置必须配套放行
前端做了拦截器处理后,别忘了服务端也要配合。如果服务端不放行X-Requested-With,前端做再多工作也是白搭,预检请求依然会失败。以Nginx为例,需要在跨域响应中加上这样的配置:
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, X-Requested-With";
add_header Access-Control-Allow-Credentials true;
# 处理预检请求:OPTIONS请求直接返回204
if ($request_method = OPTIONS) {
return 204;
}关键点有两个:一是Access-Control-Allow-Headers中必须明确包含X-Requested-With,写法不区分大小写;二是预检的OPTIONS请求必须正确响应,有些框架或服务器会把OPTIONS请求当成业务请求处理,返回405或404,导致预检失败,所以最好在Nginx或网关层把OPTIONS请求直接短路返回204。
另外要注意,如果前端请求带了cookie(withCredentials为true),服务端的Access-Control-Allow-Origin不能写成通配符*,必须回显具体的来源域名,否则浏览器同样会拒绝。这是跨域配置中最常见的坑之一。
五、几点补充建议
首先是执行顺序问题。$.ajaxPrefilter注册的处理函数遵循先进先出的顺序依次执行,如果你在多个地方注册了prefilter,注意它们对options的修改会叠加。其次是兼容性,如果你的项目还在用JSONP处理跨域,要注意JSONP本质上是script标签加载,根本没有请求头的概念,给JSONP请求设置headers是无效的,prefilter里可以通过options.dataTypes判断并跳过。
最后一点建议:这种全局拦截逻辑最好封装在一个独立的js文件中,在所有业务脚本之前引入,保证拦截器在任何请求发出之前注册完毕。同时配合$.ajaxSetup可以统一设置timeout、contentType等默认值,形成一套完整的全局请求规范。这样一来,业务代码只需要关心接口本身,跨域、请求头这些脏活全部交给统一层处理,代码可维护性会明显提升。
jQueryajaxPrefilter跨域请求修改时间:2026-09-13 20:53:00