Ajax是Asynchronous JavaScript and XML的缩写,实现无刷新数据交互。
// Ajax特点:
// 1. 无需刷新页面即可与服务器通信
// 2. 异步执行,不阻塞页面
// 3. 可以发送和接收各种数据格式
// 传统方式:页面跳转
// 用户点击 → 页面刷新 → 服务器响应 → 新页面
// Ajax方式:异步请求
// 用户点击 → Ajax请求 → 服务器响应 → 更新部分页面
// 常见应用
// 1. 表单验证
// 2. 搜索建议
// 3. 无限滚动
// 4. 实时更新
// 5. 文件上传
// 6. 数据加载
// 示例:搜索建议
const searchInput = document.querySelector("#search");
const suggestions = document.querySelector("#suggestions");
searchInput.addEventListener("input", debounce(async function() {
const keyword = this.value;
if (keyword.length < 2) {
suggestions.innerHTML = "";
return;
}
const results = await search(keyword);
renderSuggestions(results);
}, 300));
XMLHttpRequest是Ajax的核心对象。
// 创建XHR对象
let xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
// IE6及以下
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
// 现代浏览器直接使用
const xhr2 = new XMLHttpRequest();
const xhr = new XMLHttpRequest();
// readyState: 请求状态
// 0: 未初始化
// 1: 已打开
// 2: 已发送
// 3: 接收中
// 4: 完成
// status: HTTP状态码
// 200: 成功
// 301: 永久重定向
// 302: 临时重定向
// 304: 未修改
// 400: 错误请求
// 401: 未授权
// 403: 禁止访问
// 404: 未找到
// 500: 服务器错误
// responseText: 响应文本
// responseXML: 响应XML
// statusText: 状态文本
发送HTTP请求。
const xhr = new XMLHttpRequest();
// 配置请求
xhr.open("GET", "/api/users?id=1", true);
// 发送请求
xhr.send();
// 监听响应
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error("请求失败:", xhr.status);
}
}
};
const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/users", true);
// 设置请求头
xhr.setRequestHeader("Content-Type", "application/json");
// 发送数据
const data = {
name: "东巴文",
email: "info@db-w.cn"
};
xhr.send(JSON.stringify(data));
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
function ajax(options) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const {
method = "GET",
url,
data = null,
headers = {},
timeout = 0
} = options;
// 配置请求
xhr.open(method, url, true);
// 设置请求头
Object.entries(headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
// 设置超时
xhr.timeout = timeout;
// 监听响应
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const response = JSON.parse(xhr.responseText);
resolve(response);
} catch (e) {
resolve(xhr.responseText);
}
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
}
};
// 错误处理
xhr.onerror = () => reject(new Error("网络错误"));
xhr.ontimeout = () => reject(new Error("请求超时"));
// 发送请求
if (data && typeof data === "object") {
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(data));
} else {
xhr.send(data);
}
});
}
// 使用
ajax({
method: "GET",
url: "/api/users/1"
})
.then(user => console.log(user))
.catch(error => console.error(error));
处理服务器响应。
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data", true);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
// 获取响应头
const contentType = xhr.getResponseHeader("Content-Type");
const allHeaders = xhr.getAllResponseHeaders();
// 根据类型处理响应
if (contentType.includes("application/json")) {
const data = JSON.parse(xhr.responseText);
console.log(data);
} else if (contentType.includes("text/html")) {
document.getElementById("content").innerHTML = xhr.responseText;
} else {
console.log(xhr.responseText);
}
}
};
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data.xml", true);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const xml = xhr.responseXML;
const items = xml.getElementsByTagName("item");
for (const item of items) {
console.log(item.textContent);
}
}
};
XMLHttpRequest的事件处理。
const xhr = new XMLHttpRequest();
// readystatechange: 状态变化
xhr.onreadystatechange = function() {
console.log("状态:", xhr.readyState);
};
// load: 请求完成
xhr.onload = function() {
console.log("加载完成:", xhr.status);
};
// error: 请求失败
xhr.onerror = function() {
console.error("请求失败");
};
// abort: 请求中止
xhr.onabort = function() {
console.log("请求中止");
};
// timeout: 请求超时
xhr.ontimeout = function() {
console.error("请求超时");
};
// progress: 上传/下载进度
xhr.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total * 100).toFixed(0);
console.log(`下载进度: ${percent}%`);
}
};
xhr.open("GET", "/api/data", true);
xhr.timeout = 5000;
xhr.send();
function uploadFile(file) {
const xhr = new XMLHttpRequest();
// 上传进度
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total * 100).toFixed(0);
console.log(`上传进度: ${percent}%`);
updateProgress(percent);
}
};
xhr.upload.onload = function() {
console.log("上传完成");
};
xhr.upload.onerror = function() {
console.error("上传失败");
};
const formData = new FormData();
formData.append("file", file);
xhr.open("POST", "/api/upload", true);
xhr.send(formData);
}
处理跨域请求问题。
// 同源:协议、域名、端口相同
// http://db-w.cn/page1
// http://db-w.cn/page2 同源
// https://db-w.cn/page1 不同源(协议不同)
// http://www.db-w.cn/page1 不同源(域名不同)
// http://db-w.cn:8080/page1 不同源(端口不同)
// 跨域请求会被浏览器阻止
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://other.com/api/data", true);
xhr.send(); // 可能被阻止
// CORS: Cross-Origin Resource Sharing
// 服务器设置响应头允许跨域
// 简单请求
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://api.db-w.cn/data", true);
xhr.withCredentials = true; // 发送Cookie
xhr.send();
// 预检请求(非简单请求)
// 浏览器先发送OPTIONS请求
// 服务器返回允许的方法和头
// 非简单请求条件:
// 1. 方法不是GET/POST/HEAD
// 2. 头部包含自定义字段
// 3. Content-Type不是简单类型
// JSONP: 利用script标签跨域
function jsonp(url, callback) {
const callbackName = "jsonp_" + Date.now();
window[callbackName] = function(data) {
callback(data);
delete window[callbackName];
document.body.removeChild(script);
};
const script = document.createElement("script");
script.src = `${url}?callback=${callbackName}`;
document.body.appendChild(script);
}
// 使用
jsonp("http://api.db-w.cn/data", function(data) {
console.log(data);
});
// 服务器返回:
// jsonp_1234567890({ "name": "东巴文" })
处理表单数据。
// 从表单创建
const form = document.querySelector("form");
const formData = new FormData(form);
// 手动创建
const formData2 = new FormData();
formData2.append("name", "东巴文");
formData2.append("email", "info@db-w.cn");
formData2.append("age", 25);
const formData = new FormData();
formData.append("name", "东巴文");
formData.append("tags", "js");
formData.append("tags", "css"); // 同名多值
// 获取值
console.log(formData.get("name")); // "东巴文"
console.log(formData.getAll("tags")); // ["js", "css"]
// 检查是否存在
console.log(formData.has("name")); // true
// 设置值(覆盖)
formData.set("name", "新名称");
// 删除
formData.delete("age");
// 遍历
for (const [key, value] of formData) {
console.log(key, value);
}
// 上传文件
const input = document.querySelector('input[type="file"]');
input.addEventListener("change", function() {
const file = this.files[0];
const formData = new FormData();
formData.append("file", file);
formData.append("name", file.name);
const xhr = new XMLHttpRequest();
xhr.upload.onprogress = function(e) {
const percent = (e.loaded / e.total * 100).toFixed(0);
console.log(`上传: ${percent}%`);
};
xhr.onload = function() {
console.log("上传完成");
};
xhr.open("POST", "/api/upload", true);
xhr.send(formData);
});
掌握了Ajax后,让我们继续学习:
东巴文(db-w.cn) - 让编程学习更简单
🎯 东巴文寄语:Ajax是现代Web应用的基础技术,虽然Fetch API更加现代,但理解XMLHttpRequest对于理解Web通信原理非常重要。在 db-w.cn,我们帮你掌握网络请求的核心知识!