pin_drop当前位置:知识文库 ❯ 图文
后端通信:Ajax基础与XMLHttpRequest - 从入门到实践详解
教程简介
Ajax(Asynchronous JavaScript and XML)是Web开发史上最重要的技术突破之一,它使网页能够在不重新加载整个页面的情况下与服务器交换数据并更新局部内容。虽然现代开发中Fetch API已成为首选,但XMLHttpRequest(XHR)作为Ajax的核心实现,仍然是理解前后端通信基础的重要知识点。许多第三方库(如jQuery的$.ajax)和旧项目仍在使用XHR。
本教程将全面讲解Ajax原理、XMLHttpRequest对象的使用方法、请求与响应处理、进度事件、超时处理,以及XHR的局限性。
核心概念
1. Ajax原理
Ajax不是一种单一技术,而是几种技术的组合:
HTML/XHTML:页面结构
CSS:页面样式
JavaScript:动态操作DOM
DOM:动态显示和交互
XMLHttpRequest:异步数据交换
XML/JSON:数据格式
Ajax的工作流程:
用户在页面上触发事件(点击按钮、提交表单等)
JavaScript创建XMLHttpRequest对象
向服务器发送异步请求
服务器处理请求并返回数据
JavaScript接收响应数据
使用DOM操作更新页面局部内容
与传统Web开发的区别:
2. XMLHttpRequest对象
XMLHttpRequest是浏览器提供的内置对象,用于在后台与服务器通信:
代码示例
// 创建XHR对象
const xhr = new XMLHttpRequest();XHR属性
readyState状态值
XHR方法
XHR事件
3. 基本使用流程
代码示例
// 1. 创建XHR对象
const xhr = new XMLHttpRequest();
// 2. 配置请求
xhr.open('GET', '/api/users', true);
// 3. 设置请求头(可选)
xhr.setRequestHeader('Accept', 'application/json');
// 4. 监听状态变化
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 请求成功
const data = JSON.parse(xhr.responseText);
console.log(data);
} else {
// 请求失败
console.error('请求失败:', xhr.status);
}
}
};
// 5. 发送请求
xhr.send();4. 同步vs异步
代码示例
// 异步请求(推荐)
xhr.open('GET', '/api/data', true);
// 同步请求(不推荐,已废弃)
xhr.open('GET', '/api/data', false);同步请求的问题:
阻塞主线程,页面冻结
用户体验极差
在主线程上已被大多数浏览器废弃
只能在Worker中使用
5. 请求头设置
代码示例
// 设置Content-Type
xhr.setRequestHeader('Content-Type', 'application/json');
// 设置Authorization
xhr.setRequestHeader('Authorization', 'Bearer token123');
// 注意:某些头部浏览器不允许设置
// Forbidden headers: Accept-Charset, Accept-Encoding, Cookie, Host, Origin, Referer等6. 响应处理
代码示例
// 文本响应
const text = xhr.responseText;
// JSON响应
const data = JSON.parse(xhr.responseText);
// XML响应
const xml = xhr.responseXML;
// Blob响应
xhr.responseType = 'blob';
xhr.onload = function() {
const blob = xhr.response;
const url = URL.createObjectURL(blob);
};
// ArrayBuffer响应
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
const buffer = xhr.response;
};7. 进度事件
代码示例
// 上传进度
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total * 100).toFixed(1);
console.log(`上传进度: ${percent}%`);
}
};
// 下载进度
xhr.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total * 100).toFixed(1);
console.log(`下载进度: ${percent}%`);
}
};8. 超时处理
代码示例
xhr.timeout = 10000; // 10秒超时
xhr.ontimeout = function() {
console.error('请求超时');
};9. XHR的局限性
代码示例
示例1:XMLHttpRequest完整演示
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XMLHttpRequest完整演示</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #f0f2f5;
padding: 20px;
color: #333;
}
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card {
background: #fff;
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.card h2 {
font-size: 18px;
color: #1a73e8;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #e8eaed;
}
.method-btns {
display: flex;
gap: 8px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.method-btn {
padding: 8px 16px;
border: 2px solid #dadce0;
border-radius: 8px;
background: #fff;
cursor: pointer;
font-weight: 600;
font-size: 13px;
transition: all 0.2s;
}
.method-btn:hover { border-color: #1a73e8; }
.method-btn.active { background: #1a73e8; color: #fff; border-color: #1a73e8; }
.url-bar {
display: flex;
gap: 10px;
margin-bottom: 16px;
}
.url-bar select {
padding: 10px 14px;
border: 1px solid #dadce0;
border-radius: 8px;
font-weight: 700;
font-size: 14px;
}
.url-bar input {
flex: 1;
padding: 10px 14px;
border: 1px solid #dadce0;
border-radius: 8px;
font-size: 14px;
}
.url-bar input:focus, .url-bar select:focus { outline: none; border-color: #1a73e8; }
.send-btn {
padding: 10px 24px;
background: #1a73e8;
color: #fff;
border: none;
border-radius: 8px;
font-weight: 700;
cursor: pointer;
font-size: 14px;
}
.send-btn:hover { background: #1557b0; }
.send-btn:disabled { background: #dadce0; cursor: not-allowed; }
.state-bar {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
.state-item {
flex: 1;
padding: 10px;
border-radius: 8px;
text-align: center;
font-size: 12px;
font-weight: 600;
background: #f8f9fa;
color: #999;
transition: all 0.3s;
}
.state-item.active { background: #1a73e8; color: #fff; }
.state-item.done { background: #34a853; color: #fff; }
.state-item.error { background: #ea4335; color: #fff; }
.state-num { font-size: 18px; display: block; margin-bottom: 2px; }
.progress-bar {
height: 6px;
background: #e8eaed;
border-radius: 3px;
overflow: hidden;
margin-bottom: 16px;
}
.progress-fill {
height: 100%;
background: #1a73e8;
border-radius: 3px;
transition: width 0.3s;
width: 0%;
}
.response-area {
background: #1e1e1e;
color: #d4d4d4;
padding: 16px;
border-radius: 8px;
font-family: 'Consolas', monospace;
font-size: 13px;
line-height: 1.7;
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
.log-area {
background: #f8f9fa;
padding: 12px;
border-radius: 8px;
font-family: 'Consolas', monospace;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
line-height: 1.8;
}
.log-time { color: #888; }
.log-info { color: #1a73e8; }
.log-success { color: #34a853; }
.log-error { color: #ea4335; }
.log-warn { color: #ff9800; }
textarea {
width: 100%;
padding: 10px;
border: 1px solid #dadce0;
border-radius: 8px;
font-family: 'Consolas', monospace;
font-size: 13px;
resize: vertical;
margin-bottom: 12px;
}
</style>
</head>
<body>
<div class="container">
<h1>XMLHttpRequest完整演示</h1>
<div class="card">
<h2>请求配置</h2>
<div class="url-bar">
<select id="method">
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="DELETE">DELETE</option>
</select>
<input type="text" id="url" value="https://httpbin.org/get" placeholder="请求URL">
<button class="send-btn" id="sendBtn" onclick="sendXHR()">发送</button>
</div>
<div id="bodySection" style="display:none;">
<textarea id="body" rows="3" placeholder='请求体内容,如:{"name":"张三"}'></textarea>
</div>
</div>
<div class="card">
<h2>请求状态</h2>
<div class="state-bar">
<div class="state-item" id="state0"><span class="state-num">0</span>UNSENT</div>
<div class="state-item" id="state1"><span class="state-num">1</span>OPENED</div>
<div class="state-item" id="state2"><span class="state-num">2</span>HEADERS</div>
<div class="state-item" id="state3"><span class="state-num">3</span>LOADING</div>
<div class="state-item" id="state4"><span class="state-num">4</span>DONE</div>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill"></div>
</div>
</div>
<div class="card">
<h2>响应内容</h2>
<div class="response-area" id="responseArea">等待发送请求...</div>
</div>
<div class="card">
<h2>事件日志</h2>
<div class="log-area" id="logArea">等待操作...</div>
</div>
</div>
<script>
let xhr = null;
document.getElementById('method').addEventListener('change', function() {
const show = ['POST', 'PUT', 'PATCH'].includes(this.value);
document.getElementById('bodySection').style.display = show ? 'block' : 'none';
});
function log(msg, type = 'info') {
const time = new Date().toLocaleTimeString();
const logArea = document.getElementById('logArea');
logArea.innerHTML += `<div><span class="log-time">[${time}]</span> <span class="log-${type}">${msg}</span></div>`;
logArea.scrollTop = logArea.scrollHeight;
}
function resetStates() {
for (let i = 0; i <= 4; i++) {
document.getElementById('state' + i).className = 'state-item';
}
}
function sendXHR() {
if (xhr) {
xhr.abort();
}
const method = document.getElementById('method').value;
const url = document.getElementById('url').value;
const body = document.getElementById('body').value;
resetStates();
document.getElementById('state0').classList.add('active');
document.getElementById('progressFill').style.width = '0%';
document.getElementById('responseArea').textContent = '请求中...';
document.getElementById('sendBtn').disabled = true;
xhr = new XMLHttpRequest();
log(`创建XHR对象,readyState: ${xhr.readyState}`, 'info');
xhr.onreadystatechange = function() {
log(`readyState变化: ${xhr.readyState}`, 'info');
for (let i = 0; i <= 4; i++) {
const el = document.getElementById('state' + i);
el.className = 'state-item';
if (i < xhr.readyState) el.classList.add('done');
if (i === xhr.readyState) el.classList.add('active');
}
if (xhr.readyState === 4) {
document.getElementById('sendBtn').disabled = false;
if (xhr.status >= 200 && xhr.status < 300) {
log(`请求成功: ${xhr.status} ${xhr.statusText}`, 'success');
document.getElementById('state4').classList.remove('active');
document.getElementById('state4').classList.add('done');
try {
const data = JSON.parse(xhr.responseText);
document.getElementById('responseArea').textContent = JSON.stringify(data, null, 2);
} catch {
document.getElementById('responseArea').textContent = xhr.responseText;
}
} else {
log(`请求失败: ${xhr.status} ${xhr.statusText}`, 'error');
document.getElementById('state4').classList.remove('active');
document.getElementById('state4').classList.add('error');
document.getElementById('responseArea').textContent = `错误: ${xhr.status} ${xhr.statusText}`;
}
}
};
xhr.onprogress = function(e) {
if (e.lengthComputable) {
const percent = (e.loaded / e.total * 100).toFixed(0);
document.getElementById('progressFill').style.width = percent + '%';
log(`下载进度: ${percent}%`, 'info');
}
};
xhr.onloadstart = function() { log('请求开始', 'info'); };
xhr.onloadend = function() { log('请求结束', 'info'); };
xhr.onerror = function() { log('网络错误', 'error'); };
xhr.ontimeout = function() { log('请求超时', 'warn'); };
xhr.timeout = 30000;
log(`发送请求: ${method} ${url}`, 'info');
xhr.open(method, url, true);
xhr.setRequestHeader('Accept', 'application/json');
if (['POST', 'PUT', 'PATCH'].includes(method) && body) {
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(body);
} else {
xhr.send();
}
}
</script>
</body>
</html>示例2:XHR文件上传进度
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XHR文件上传进度</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #f0f2f5;
padding: 20px;
color: #333;
}
.container { max-width: 700px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card {
background: #fff;
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.card h2 {
font-size: 18px;
color: #1a73e8;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #e8eaed;
}
.drop-zone {
border: 2px dashed #dadce0;
border-radius: 12px;
padding: 40px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
margin-bottom: 16px;
}
.drop-zone:hover, .drop-zone.dragover {
border-color: #1a73e8;
background: #e3f2fd;
}
.drop-zone-icon { font-size: 40px; margin-bottom: 10px; }
.drop-zone-text { color: #888; font-size: 14px; }
.file-list { list-style: none; }
.file-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border: 1px solid #e8eaed;
border-radius: 8px;
margin-bottom: 8px;
}
.file-icon { font-size: 24px; }
.file-info { flex: 1; }
.file-name { font-weight: 600; font-size: 14px; }
.file-size { font-size: 12px; color: #888; }
.file-progress {
height: 6px;
background: #e8eaed;
border-radius: 3px;
overflow: hidden;
margin-top: 6px;
}
.file-progress-fill {
height: 100%;
background: #1a73e8;
border-radius: 3px;
transition: width 0.3s;
}
.file-status { font-size: 12px; font-weight: 600; }
.status-pending { color: #888; }
.status-uploading { color: #1a73e8; }
.status-done { color: #34a853; }
.status-error { color: #ea4335; }
.upload-btn {
width: 100%;
padding: 14px;
background: #1a73e8;
color: #fff;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 700;
cursor: pointer;
transition: background 0.2s;
}
.upload-btn:hover { background: #1557b0; }
.upload-btn:disabled { background: #dadce0; cursor: not-allowed; }
</style>
</head>
<body>
<div class="container">
<h1>XHR文件上传进度</h1>
<div class="card">
<h2>选择文件</h2>
<div class="drop-zone" id="dropZone">
<div class="drop-zone-icon"></div>
<div class="drop-zone-text">拖放文件到此处,或点击选择文件</div>
<input type="file" id="fileInput" multiple style="display:none">
</div>
<ul class="file-list" id="fileList"></ul>
<button class="upload-btn" id="uploadBtn" onclick="uploadFiles()" disabled>上传文件</button>
</div>
</div>
<script>
let files = [];
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
dropZone.addEventListener('click', () => fileInput.click());
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover'));
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
addFiles(e.dataTransfer.files);
});
fileInput.addEventListener('change', () => addFiles(fileInput.files));
function addFiles(fileList) {
for (const file of fileList) {
files.push({
file,
id: Date.now() + Math.random(),
progress: 0,
status: 'pending'
});
}
renderFileList();
document.getElementById('uploadBtn').disabled = files.length === 0;
}
function formatSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / 1048576).toFixed(1) + ' MB';
}
function renderFileList() {
const list = document.getElementById('fileList');
list.innerHTML = files.map(f => `
<li class="file-item">
<div class="file-icon"></div>
<div class="file-info">
<div class="file-name">${f.file.name}</div>
<div class="file-size">${formatSize(f.file.size)}</div>
<div class="file-progress">
<div class="file-progress-fill" style="width:${f.progress}%"></div>
</div>
</div>
<span class="file-status status-${f.status}">
${f.status === 'pending' ? '等待' :
f.status === 'uploading' ? f.progress + '%' :
f.status === 'done' ? '完成' : '失败'}
</span>
</li>
`).join('');
}
function uploadFiles() {
document.getElementById('uploadBtn').disabled = true;
files.forEach((f, index) => {
if (f.status === 'done') return;
const formData = new FormData();
formData.append('file', f.file);
const xhr = new XMLHttpRequest();
f.status = 'uploading';
renderFileList();
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
f.progress = Math.round(e.loaded / e.total * 100);
renderFileList();
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
f.status = 'done';
f.progress = 100;
} else {
f.status = 'error';
}
renderFileList();
checkAllDone();
};
xhr.onerror = () => {
f.status = 'error';
renderFileList();
checkAllDone();
};
// 模拟上传(实际使用时替换为真实URL)
xhr.open('POST', 'https://httpbin.org/post', true);
xhr.send(formData);
});
}
function checkAllDone() {
const allDone = files.every(f => f.status === 'done' || f.status === 'error');
if (allDone) {
document.getElementById('uploadBtn').disabled = false;
document.getElementById('uploadBtn').textContent = '重新上传';
}
}
</script>
</body>
</html>示例3:XHR封装与Promise化
代码示例
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>XHR封装与Promise化</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: #f0f2f5;
padding: 20px;
color: #333;
}
.container { max-width: 900px; margin: 0 auto; }
h1 { text-align: center; color: #1a73e8; margin-bottom: 24px; font-size: 28px; }
.card {
background: #fff;
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.card h2 {
font-size: 18px;
color: #1a73e8;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #e8eaed;
}
.code-block {
background: #1e1e1e;
color: #d4d4d4;
padding: 16px;
border-radius: 8px;
font-family: 'Consolas', monospace;
font-size: 13px;
line-height: 1.7;
white-space: pre-wrap;
margin-bottom: 16px;
}
.demo-btns {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.demo-btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-blue { background: #1a73e8; color: #fff; }
.btn-blue:hover { background: #1557b0; }
.btn-green { background: #34a853; color: #fff; }
.btn-green:hover { background: #2d8e47; }
.btn-orange { background: #ff9800; color: #fff; }
.btn-orange:hover { background: #e5a800; }
.result-area {
background: #f8f9fa;
padding: 16px;
border-radius: 8px;
font-family: 'Consolas', monospace;
font-size: 13px;
line-height: 1.7;
min-height: 100px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<div class="container">
<h1>XHR封装与Promise化</h1>
<div class="card">
<h2>XHR Promise封装</h2>
<div class="code-block">// 将XHR封装为Promise
function xhrRequest(options) {
return new Promise((resolve, reject) => {
const {
method = 'GET',
url,
headers = {},
body = null,
timeout = 30000,
responseType = 'json'
} = options;
const xhr = new XMLHttpRequest();
xhr.timeout = timeout;
xhr.responseType = responseType;
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
status: xhr.status,
statusText: xhr.statusText,
data: xhr.response,
headers: parseHeaders(xhr.getAllResponseHeaders())
});
} else {
reject(new HttpError(xhr.status, xhr.statusText));
}
};
xhr.onerror = () => reject(new Error('网络错误'));
xhr.ontimeout = () => reject(new Error('请求超时'));
xhr.onabort = () => reject(new Error('请求已取消'));
xhr.open(method, url, true);
Object.entries(headers).forEach(([name, value]) => {
xhr.setRequestHeader(name, value);
});
xhr.send(body ? JSON.stringify(body) : null);
});
}
class HttpError extends Error {
constructor(status, statusText) {
super(`${status} ${statusText}`);
this.status = status;
this.statusText = statusText;
}
}
function parseHeaders(headerStr) {
return headerStr.trim().split(/\\r?\\n/).reduce((acc, line) => {
const [name, ...value] = line.split(': ');
acc[name.toLowerCase()] = value.join(': ');
return acc;
}, {});
}</div>
</div>
<div class="card">
<h2>使用演示</h2>
<div class="demo-btns">
<button class="demo-btn btn-blue" onclick="demoGet()">GET请求</button>
<button class="demo-btn btn-green" onclick="demoPost()">POST请求</button>
<button class="demo-btn btn-orange" onclick="demoError()">错误处理</button>
<button class="demo-btn btn-blue" onclick="demoChain()">链式调用</button>
</div>
<div class="result-area" id="resultArea">点击按钮查看演示结果</div>
</div>
</div>
<script>
// XHR Promise封装
function xhrRequest(options) {
return new Promise((resolve, reject) => {
const {
method = 'GET', url, headers = {},
body = null, timeout = 30000, responseType = 'json'
} = options;
const xhr = new XMLHttpRequest();
xhr.timeout = timeout;
xhr.responseType = responseType;
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
status: xhr.status,
statusText: xhr.statusText,
data: xhr.response,
headers: xhr.getAllResponseHeaders()
});
} else {
reject(new Error(`HTTP ${xhr.status} ${xhr.statusText}`));
}
};
xhr.onerror = () => reject(new Error('网络错误'));
xhr.ontimeout = () => reject(new Error('请求超时'));
xhr.open(method, url, true);
Object.entries(headers).forEach(([name, value]) => {
xhr.setRequestHeader(name, value);
});
xhr.send(body ? JSON.stringify(body) : null);
});
}
async function demoGet() {
const area = document.getElementById('resultArea');
area.textContent = 'GET请求中...';
try {
const result = await xhrRequest({
method: 'GET',
url: 'https://httpbin.org/get',
headers: { 'Accept': 'application/json' }
});
area.textContent = `GET请求成功!\n状态码: ${result.status}\n\n响应数据:\n${JSON.stringify(result.data, null, 2)}`;
} catch (error) {
area.textContent = `GET请求失败: ${error.message}`;
}
}
async function demoPost() {
const area = document.getElementById('resultArea');
area.textContent = 'POST请求中...';
try {
const result = await xhrRequest({
method: 'POST',
url: 'https://httpbin.org/post',
headers: { 'Content-Type': 'application/json' },
body: { name: '张三', email: 'zhangsan@example.com' }
});
area.textContent = `POST请求成功!\n状态码: ${result.status}\n\n响应数据:\n${JSON.stringify(result.data, null, 2)}`;
} catch (error) {
area.textContent = `POST请求失败: ${error.message}`;
}
}
async function demoError() {
const area = document.getElementById('resultArea');
area.textContent = '测试错误处理...';
try {
await xhrRequest({
url: 'https://httpbin.org/status/404',
timeout: 5000
});
} catch (error) {
area.textContent = `错误被捕获!\n错误信息: ${error.message}\n\nPromise化后可以用try/catch优雅地处理错误`;
}
}
async function demoChain() {
const area = document.getElementById('resultArea');
area.textContent = '链式调用演示...\n\n1. 先获取数据...';
try {
const result1 = await xhrRequest({
url: 'https://httpbin.org/uuid'
});
area.textContent += `\n2. 获取到UUID: ${result1.data.uuid}\n3. 用UUID获取详情...`;
const result2 = await xhrRequest({
url: 'https://httpbin.org/get',
headers: { 'X-Request-ID': result1.data.uuid }
});
area.textContent += `\n4. 链式调用完成!\n\nPromise化后可以用async/await实现优雅的链式调用,\n避免了回调地狱。`;
} catch (error) {
area.textContent += `\n链式调用失败: ${error.message}`;
}
}
</script>
</body>
</html>浏览器兼容性
注意事项与最佳实践
1. 优先使用Fetch API
Fetch API更现代,基于Promise设计
XHR的API设计老旧,不符合现代编程范式
新项目应使用Fetch,旧项目可逐步迁移
2. 避免同步请求
同步XHR在主线程上已被废弃
会导致页面冻结,用户体验极差
如必须同步,请在Worker中使用
3. 正确处理错误
同时监听onerror和ontimeout
检查HTTP状态码(4xx/5xx)
网络错误时responseText可能为空
4. 超时设置
始终设置合理的超时时间
默认无超时,可能永远等待
建议API请求设置10-30秒超时
5. 内存管理
及时abort不再需要的请求
清除事件监听器
大文件下载使用Blob避免内存溢出
代码规范示例
完整的XHR工具类
代码示例
class XhrClient {
constructor(baseURL = '', defaultHeaders = {}) {
this.baseURL = baseURL;
this.defaultHeaders = {
'Accept': 'application/json',
...defaultHeaders
};
}
request(method, path, options = {}) {
return new Promise((resolve, reject) => {
const url = this.baseURL + path;
const xhr = new XMLHttpRequest();
xhr.timeout = options.timeout || 30000;
xhr.responseType = options.responseType || 'json';
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve({
status: xhr.status,
data: xhr.response,
headers: xhr.getAllResponseHeaders()
});
} else {
reject(new Error(`HTTP ${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('网络错误'));
xhr.ontimeout = () => reject(new Error('请求超时'));
if (options.onProgress) {
xhr.onprogress = options.onProgress;
}
if (options.onUploadProgress) {
xhr.upload.onprogress = options.onUploadProgress;
}
xhr.open(method, url, true);
const headers = { ...this.defaultHeaders, ...options.headers };
Object.entries(headers).forEach(([name, value]) => {
xhr.setRequestHeader(name, value);
});
xhr.send(options.body || null);
});
}
get(path, options) { return this.request('GET', path, options); }
post(path, body, options) { return this.request('POST', path, { ...options, body: JSON.stringify(body) }); }
put(path, body, options) { return this.request('PUT', path, { ...options, body: JSON.stringify(body) }); }
delete(path, options) { return this.request('DELETE', path, options); }
}常见问题与解决方案
问题1:CORS请求被拒绝
解决方案:确保服务器设置了正确的CORS头部,XHR跨域请求需要服务器配合。
问题2:中文乱码
解决方案:确保服务器返回正确的Content-Type头部(charset=utf-8),或使用overrideMimeType:
代码示例
xhr.overrideMimeType('text/html; charset=utf-8');问题3:IE兼容性
解决方案:IE9及以下使用ActiveXObject:
代码示例
const xhr = window.XMLHttpRequest
? new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP');问题4:重复请求
解决方案:在发送新请求前abort上一个请求:
代码示例
let lastXHR = null;
function search(query) {
if (lastXHR) lastXHR.abort();
lastXHR = new XMLHttpRequest();
// ...配置和发送
}总结
XMLHttpRequest是Ajax技术的核心实现,虽然Fetch API已成为现代开发的首选,但理解XHR仍然重要:
Ajax原理:异步通信,局部更新,无需刷新整个页面
XHR对象:readyState状态机、事件驱动模型
请求配置:open/send/setRequestHeader的基本用法
响应处理:responseText/responseXML/responseType
进度事件:upload.onprogress和onprogress实现上传下载进度
超时处理:timeout属性和ontimeout事件
Promise封装:将回调式XHR封装为Promise,配合async/await使用
XHR局限:API老旧、回调地狱、无流式读取,已被Fetch API取代
掌握XHR的工作原理,不仅有助于维护旧项目,也能更深入理解浏览器网络请求的底层机制。
常见问题
什么是Ajax基础与XMLHttpRequest?
Ajax基础与XMLHttpRequest是HTML5开发中的重要技术,本教程详细介绍了其核心概念和实践方法。
如何学习Ajax基础与XMLHttpRequest的实际应用?
教程中提供了完整的代码示例和实践指导,建议结合示例代码动手练习。
Ajax基础与XMLHttpRequest有哪些注意事项?
常见注意事项包括兼容性、性能优化等,教程的注意事项与最佳实践部分有详细说明。
Ajax基础与XMLHttpRequest适合初学者吗?
本教程从基础概念讲起,循序渐进,适合有一定HTML和JavaScript基础的初学者。
Ajax基础与XMLHttpRequest的核心要点是什么?
核心要点包括教程简介等内容,建议按教程顺序逐步学习。
本文由小确幸生活整理发布,转载请注明出处
本文涉及AI创作
内容由AI创作,请仔细甄别