您现在的位置是:网站首页 > Ajax基础文章详情
Ajax基础
陈川
【
JavaScript
】
59797人已围观
13130字
Ajax是一种在无需重新加载整个页面的情况下,能够更新部分网页的技术。它通过在后台与服务器进行少量数据交换,使网页实现异步更新,从而提升用户体验。
Ajax的核心概念
Ajax全称为Asynchronous JavaScript and XML(异步JavaScript和XML),但如今JSON已经取代XML成为主流数据格式。其核心在于使用XMLHttpRequest
对象与服务器通信,实现局部刷新。
关键组成部分包括:
- XMLHttpRequest对象
- JavaScript/DOM
- CSS
- XML/JSON
XMLHttpRequest对象
这是Ajax的基础,现代浏览器都支持这个对象。创建一个XHR对象的基本方式:
const xhr = new XMLHttpRequest();
XHR对象有几种重要的方法和属性:
// 初始化请求
xhr.open('GET', 'api/data', true);
// 发送请求
xhr.send();
// 设置请求头
xhr.setRequestHeader('Content-Type', 'application/json');
// 响应状态
xhr.status
xhr.statusText
// 响应数据
xhr.responseText
xhr.responseXML
发送请求的完整流程
一个典型的Ajax请求包含以下步骤:
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
const response = JSON.parse(xhr.responseText);
document.getElementById('result').innerHTML = response.data;
}
};
xhr.open('GET', 'https://api.example.com/data', true);
xhr.send();
readyState有5个可能的值:
- 0: 请求未初始化
- 1: 服务器连接已建立
- 2: 请求已接收
- 3: 请求处理中
- 4: 请求已完成且响应已就绪
处理不同响应类型
现代Ajax应用主要处理三种响应类型:
- JSON响应:
xhr.responseType = 'json';
xhr.onload = function() {
console.log(xhr.response);
};
- XML响应:
xhr.responseType = 'document';
xhr.onload = function() {
const xmlDoc = xhr.responseXML;
const items = xmlDoc.getElementsByTagName('item');
};
- 文本响应:
xhr.responseType = 'text';
xhr.onload = function() {
console.log(xhr.responseText);
};
错误处理
完善的Ajax实现需要处理各种错误情况:
xhr.onerror = function() {
console.error('请求出错');
};
xhr.ontimeout = function() {
console.error('请求超时');
};
xhr.onabort = function() {
console.warn('请求被取消');
};
POST请求示例
发送POST请求与GET有所不同:
const xhr = new XMLHttpRequest();
xhr.open('POST', 'api/save', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = function() {
if (xhr.status === 201) {
alert('数据保存成功');
}
};
const data = {
name: '张三',
age: 25
};
xhr.send(JSON.stringify(data));
跨域请求
当请求不同源的资源时,需要处理跨域问题。CORS(跨源资源共享)是现代浏览器支持的解决方案:
// 服务器需要设置响应头
// Access-Control-Allow-Origin: *
// 客户端可以这样检测是否支持CORS
if ('withCredentials' in new XMLHttpRequest()) {
console.log('支持CORS');
}
对于简单请求,浏览器会自动处理;对于非简单请求,会先发送OPTIONS预检请求。
进度事件
XHR2规范新增了进度事件,可以监控上传和下载进度:
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
console.log(percentComplete + '% uploaded');
}
};
xhr.onprogress = function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
console.log(percentComplete + '% downloaded');
}
};
超时设置
可以设置请求超时时间(毫秒):
xhr.timeout = 5000; // 5秒超时
xhr.ontimeout = function() {
console.error('请求超时');
};
取消请求
在请求完成前可以取消:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'api/data', true);
xhr.send();
// 取消请求
xhr.abort();
现代替代方案:Fetch API
虽然XHR是Ajax的基础,但现代JavaScript提供了更简洁的Fetch API:
fetch('api/data')
.then(response => {
if (!response.ok) {
throw new Error('网络响应不正常');
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('请求失败:', error);
});
Fetch基于Promise,语法更简洁,但需要注意:
- 默认不会发送或接收cookies
- 错误处理与XHR不同
- 不支持超时设置(需要额外实现)
实际应用示例
一个完整的用户注册表单验证示例:
document.getElementById('register-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const xhr = new XMLHttpRequest();
xhr.open('POST', 'api/register', true);
xhr.onload = function() {
const response = JSON.parse(xhr.responseText);
if (xhr.status === 200) {
showSuccessMessage(response.message);
} else {
showErrors(response.errors);
}
};
xhr.onerror = function() {
showErrorMessage('网络错误,请重试');
};
xhr.send(formData);
});
function showSuccessMessage(msg) {
const msgDiv = document.createElement('div');
msgDiv.className = 'alert success';
msgDiv.textContent = msg;
document.body.appendChild(msgDiv);
}
性能优化技巧
- 节流频繁的Ajax请求:
let isRequesting = false;
function fetchData() {
if (isRequesting) return;
isRequesting = true;
// 发送请求...
xhr.onload = function() {
isRequesting = false;
// 处理响应
};
}
- 缓存响应数据:
const cache = {};
function getCachedData(url, callback) {
if (cache[url]) {
callback(cache[url]);
return;
}
fetch(url)
.then(res => res.json())
.then(data => {
cache[url] = data;
callback(data);
});
}
- 批量请求:
function batchRequests(urls) {
return Promise.all(urls.map(url =>
fetch(url).then(res => res.json())
));
}
安全性考虑
- CSRF防护:
// 从cookie中获取token
function getCSRFToken() {
return document.cookie.replace(/(?:(?:^|.*;\s*)XSRF-TOKEN\s*\=\s*([^;]*).*$)|^.*$/, '$1');
}
// 设置请求头
xhr.setRequestHeader('X-XSRF-TOKEN', getCSRFToken());
- 输入验证:
function sanitizeInput(input) {
return input.replace(/<[^>]*>/g, '');
}
const userInput = sanitizeInput(document.getElementById('comment').value);
浏览器兼容性
虽然现代浏览器都支持XHR,但需要考虑老版本IE:
let xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
// IE6及更早版本
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
调试技巧
- 使用console.time测量请求时间:
console.time('ajaxRequest');
xhr.onload = function() {
console.timeEnd('ajaxRequest');
// 处理响应
};
- 查看请求详情:
xhr.onload = function() {
console.log('状态:', xhr.status);
console.log('响应头:', xhr.getAllResponseHeaders());
console.log('响应数据:', xhr.response);
};
高级应用:长轮询
实现服务器推送的简单方式:
function longPoll() {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'api/updates', true);
xhr.onload = function() {
if (xhr.status === 200) {
processUpdates(JSON.parse(xhr.responseText));
}
// 无论成功与否都重新发起请求
setTimeout(longPoll, 1000);
};
xhr.onerror = function() {
setTimeout(longPoll, 5000);
};
xhr.send();
}
// 启动长轮询
longPoll();
文件上传示例
使用Ajax上传文件:
<input type="file" id="file-input">
<button id="upload-btn">上传</button>
document.getElementById('upload-btn').addEventListener('click', function() {
const fileInput = document.getElementById('file-input');
const file = fileInput.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
const xhr = new XMLHttpRequest();
xhr.open('POST', 'api/upload', true);
xhr.upload.onprogress = function(e) {
const percent = Math.round((e.loaded / e.total) * 100);
console.log(`上传进度: ${percent}%`);
};
xhr.onload = function() {
if (xhr.status === 200) {
console.log('上传成功');
}
};
xhr.send(formData);
});
与jQuery的比较
传统jQuery Ajax写法:
$.ajax({
url: 'api/data',
type: 'GET',
dataType: 'json',
success: function(data) {
console.log(data);
},
error: function(xhr, status, error) {
console.error(error);
}
});
现代JavaScript已经很少需要jQuery来处理Ajax,原生XHR和Fetch API已经足够强大。
实际项目中的封装
一个实用的Ajax封装示例:
class AjaxClient {
constructor(baseURL = '') {
this.baseURL = baseURL;
}
request(method, endpoint, data = null) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, `${this.baseURL}${endpoint}`, true);
xhr.setRequestHeader('Accept', 'application/json');
if (data && method !== 'GET') {
xhr.setRequestHeader('Content-Type', 'application/json');
}
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
try {
resolve(JSON.parse(xhr.responseText));
} catch (e) {
resolve(xhr.responseText);
}
} else {
reject({
status: xhr.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = () => {
reject({
status: xhr.status,
statusText: xhr.statusText
});
};
xhr.send(data ? JSON.stringify(data) : null);
});
}
get(endpoint) {
return this.request('GET', endpoint);
}
post(endpoint, data) {
return this.request('POST', endpoint, data);
}
// 可以继续添加put、delete等方法...
}
// 使用示例
const api = new AjaxClient('https://api.example.com');
api.get('/users')
.then(users => console.log(users))
.catch(error => console.error(error));
与前端框架的集成
在现代前端框架中使用Ajax:
- React示例:
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
});
}, []);
if (loading) return <div>加载中...</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
- Vue示例:
new Vue({
el: '#app',
data: {
posts: [],
isLoading: false
},
created() {
this.fetchPosts();
},
methods: {
fetchPosts() {
this.isLoading = true;
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/posts', true);
xhr.onload = () => {
this.posts = JSON.parse(xhr.responseText);
this.isLoading = false;
};
xhr.send();
}
}
});
测试Ajax请求
使用Jest测试Ajax代码:
// 模拟XHR
const mockXHR = {
open: jest.fn(),
send: jest.fn(),
setRequestHeader: jest.fn(),
readyState: 4,
status: 200,
responseText: '{"data": "test"}'
};
global.XMLHttpRequest = jest.fn(() => mockXHR);
test('fetchData成功返回数据', done => {
fetchData((data) => {
expect(data).toEqual({data: 'test'});
done();
});
// 触发onload事件
mockXHR.onload();
});
function fetchData(callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'api/data', true);
xhr.onload = function() {
if (xhr.status === 200) {
callback(JSON.parse(xhr.responseText));
}
};
xhr.send();
}
性能监控
监控Ajax请求性能:
const startTimes = {};
function startRequestTracking(requestId) {
startTimes[requestId] = performance.now();
}
function endRequestTracking(requestId) {
const duration = performance.now() - startTimes[requestId];
console.log(`请求 ${requestId} 耗时 ${duration.toFixed(2)}ms`);
// 可以发送到监控系统
sendToAnalytics({
type: 'ajax',
id: requestId,
duration: duration
});
}
// 使用示例
const reqId = 'user_data_' + Date.now();
startRequestTracking(reqId);
fetch('api/users')
.then(() => endRequestTracking(reqId));
浏览器缓存策略
控制Ajax请求的缓存行为:
// 添加时间戳防止缓存
function noCacheUrl(url) {
return url + (url.includes('?') ? '&' : '?') + '_=' + Date.now();
}
// 或者使用Fetch API的cache选项
fetch('api/data', {
cache: 'no-store' // 或 'reload', 'no-cache', 'force-cache', 'only-if-cached'
});
数据压缩
减少传输数据量:
// 服务器设置响应头
// Content-Encoding: gzip
// 客户端可以检测是否支持压缩
xhr.setRequestHeader('Accept-Encoding', 'gzip, deflate');
二进制数据传输
使用Ajax处理二进制数据:
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
const arrayBuffer = xhr.response;
if (arrayBuffer) {
const byteArray = new Uint8Array(arrayBuffer);
// 处理二进制数据...
}
};
实时通信替代方案
虽然Ajax可以实现简单的实时通信,但对于真正的实时应用,考虑:
- WebSocket:
const socket = new WebSocket('wss://example.com/ws');
socket.onmessage = function(event) {
console.log('收到消息:', event.data);
};
- Server-Sent Events (SSE):
const eventSource = new EventSource('api/events');
eventSource.onmessage = function(e) {
console.log('事件数据:', e.data);
};
移动端优化
针对移动网络的Ajax优化:
- 减少请求大小:
// 只请求必要字段
fetch('api/users?fields=id,name,avatar')
-
使用数据压缩
-
实现离线缓存:
// 使用Service Worker缓存响应
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
历史API与Ajax结合
实现无刷新页面切换:
// 加载内容
function loadPage(url) {
fetch(url)
.then(res => res.text())
.then(html => {
document.getElementById('content').innerHTML = html;
// 修改历史记录
history.pushState({url}, '', url);
});
}
// 处理后退/前进
window.onpopstate = function(event) {
if (event.state) {
loadPage(event.state.url);
}
};
认证与授权
处理需要认证的请求:
// 基本认证
xhr.open('GET', 'api/protected', true);
xhr.setRequestHeader('Authorization', 'Basic ' + btoa('username:password'));
// Bearer token
xhr.setRequestHeader('Authorization', 'Bearer ' + authToken);
内容安全策略(CSP)
当启用CSP时,可能需要调整:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; connect-src 'self' api.example.com;">
性能瓶颈分析
常见Ajax性能问题及解决方案:
- 过多小请求 → 批量请求
- 大响应数据
下一篇: JSONP原理