XMLHttpRequest是浏览器提供的一个JavaScript对象,也是Ajax技术得以实现的基础。在它出现之前,网页想要从服务器获取新数据,只能通过表单提交或者整页刷新,用户体验非常割裂。有了XMLHttpRequest之后,前端可以在后台悄悄向服务器发请求,拿到数据后再局部更新页面,这就是常说的异步无刷新交互。本文将围绕这个对象的创建、核心方法、状态监听以及常见问题展开讲解。

一、XMLHttpRequest对象的创建与基本用法
创建XMLHttpRequest对象非常简单,现代浏览器中都支持通过new XMLHttpRequest()直接实例化。早年IE5和IE6需要借助ActiveXObject来创建,如今已无需兼容这种写法,但如果项目要维护老旧系统,还是建议做一层封装判断。
// 现代浏览器的标准创建方式
var xhr = new XMLHttpRequest();
// 兼容老版本IE的写法(仅维护老项目时需要)
var xhrOld = window.XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject("Microsoft.XMLHTTP");
对象创建之后,一个完整的请求流程分为四步:初始化请求、设置回调、发送请求、处理响应。初始化使用open方法,它接收三个参数,第一个是请求类型(GET或POST),第二个是请求地址,第三个是布尔值,true表示异步,false表示同步。需要特别注意的是,同步请求会阻塞JavaScript线程,浏览器控制台会直接给出弃用警告,实际开发中务必传true。
var xhr = new XMLHttpRequest();
// 第一步:初始化一个异步GET请求
xhr.open("GET", "https://ipipp.com/api/user?id=1001", true);
// 第二步:监听状态变化
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log("响应内容:", xhr.responseText);
}
};
// 第三步:发送请求
xhr.send();
send方法的参数对于GET请求而言传null或者不传即可,因为GET请求的参数已经拼接在URL后面。而对于POST请求,send的参数就是请求体内容,通常是序列化后的表单字符串或者JSON字符串。
二、readyState状态机与onreadystatechange事件
XMLHttpRequest内部维护了一个状态机,readyState属性记录着当前请求处于哪个阶段,一共五个取值,理解它们对调试异步问题很有帮助。
- 0(未初始化):对象已创建,但尚未调用open方法。
- 1(已启动):open方法已调用,尚未调用send。
- 2(已发送):send方法已调用,请求发出,但还没有收到响应。
- 3(接收中):正在接收服务器返回的数据,responseText中是部分数据。
- 4(完成):响应全部接收完毕,可以安全使用响应数据了。
onreadystatechange事件会在readyState每次变化时触发一次,所以一次完整请求中该回调会被触发多次。我们在回调里通常只关心readyState等于4的时刻,此时再结合status判断HTTP状态码,200到300之间(不含300)或者304都算成功。很多人只判断status为200,忽略了304缓存命中的情况,导致明明请求成功却走了错误分支,这是一个常见误区。
xhr.onreadystatechange = function () {
console.log("当前状态:", xhr.readyState);
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
// 成功:根据响应类型解析数据
var data = JSON.parse(xhr.responseText);
render(data);
} else {
console.error("请求失败,状态码:" + xhr.status);
}
}
};
另外,XMLHttpRequest还提供了更语义化的事件监听方式,比如onload在请求完成时触发,onerror在网络层错误时触发,ontimeout在超时时触发。这些事件比onreadystatechange更直观,新项目推荐使用,还可以配合timeout属性设置超时时间,避免请求无限挂起。
三、GET与POST请求的实战差异
GET请求把参数拼在URL后面,格式是?key1=value1&key2=value2,参数需要用encodeURIComponent编码,否则遇到中文或特殊字符会出错。GET请求有URL长度限制(浏览器实现各异,一般建议控制在2KB以内),且参数明文暴露在地址栏和服务器日志中,不适合传密码等敏感信息。
// GET请求:参数拼接在URL上
var page = 2;
var keyword = encodeURIComponent("前端教程");
xhr.open("GET", "https://ipipp.com/api/list?page=" + page + "&kw=" + keyword, true);
xhr.send();
POST请求则把数据放在请求体中,必须先用setRequestHeader设置Content-Type,告诉服务器请求体的格式。表单提交对应的是application/x-www-form-urlencoded,上传JSON对应application/json,如果忘了设置请求头,很多后端框架会解析不到参数,这是新手最常踩的坑之一。
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://ipipp.com/api/login", true);
// 必须设置请求头,否则服务器无法识别请求体格式
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
// 表单格式:键值对用 & 连接,值需要编码
xhr.send("username=admin&password=" + encodeURIComponent("123456"));
如果发送JSON数据,除了改Content-Type为application/json,send的参数要用JSON.stringify序列化,同时注意跨域场景下自定义的Content-Type可能触发预检请求(OPTIONS),服务器需要正确响应预检才能让正式请求通过。
四、跨域限制与XMLHttpRequest的局限
出于同源策略的安全限制,XMLHttpRequest默认只能请求与当前页面同协议、同域名、同端口的服务。一旦三者有任何一个不同,浏览器就会拦截响应并报跨域错误。需要澄清的是,请求其实已经发出去了,服务器也可能正常处理了,只是浏览器把响应挡在了JavaScript之外,所以这个问题无法只靠前端规避。
解决跨域的主流方案是CORS,即服务器在响应头中返回Access-Control-Allow-Origin等字段,明确声明允许哪些来源访问。前端这边XMLHttpRequest不需要特殊处理,浏览器会自动完成协议层面的协商。此外,JSONP利用<script>标签不受同源限制的特点实现跨域GET请求,但它只支持GET且安全性较差,如今已基本被CORS取代。
除了跨域,XMLHttpRequest还有一些固有局限:回调写法容易嵌套过深,代码可读性差;获取进度信息需要依赖onprogress事件,用法略显繁琐。因此现代浏览器推出了fetchAPI,它基于Promise,写法更简洁,配合async/await可以让异步代码接近同步的阅读体验。不过fetch不会在HTTP状态码为404、500时自动reject,需要手动检查response.ok,且默认不支持请求超时取消,这几点又不如XMLHttpRequest的timeout属性直接。两者并非替代关系,理解XMLHttpRequest的原理依然是掌握前端网络请求的必修课,因为Axios等主流库的底层在浏览器端依然构建在它之上。
XMLHttpRequestAjax异步请求修改时间:2026-09-04 00:58:53