AJAX 请求方法详解:GET 获取数据 POST 提交数据 跨域问题如何解决?一文看懂常见使用场景与错误排查
一、AJAX 到底是什么?先别被名字唬住
AJAX 的全称是 Asynchronous JavaScript and XML(异步 JavaScript 和 XML)。听起来很高大上对吧?但说白了,它做的事只有一个:在网页不刷新的情况下,偷偷跟服务器要数据或者送数据回去。
想象一下,你在刷微博,下拉一下就能看到新内容,页面并没有重新加载,对吧?这就是 AJAX 在悄悄干活。
在 JavaScript 里,发出 AJAX 请求主要有三种方式:
- XMLHttpRequest(XHR) —— 老牌选手,兼容性最好,但语法比较啰嗦
- fetch API —— 现代写法,基于 Promise,简洁优雅
- axios —— 第三方库,功能强大,社区广泛使用
下面我逐一详解。
二、GET 请求:从服务器”拿”数据
GET 是最常用的 HTTP 方法,它的作用就是从服务器获取数据。它的核心特点是:
- 数据通过 URL 参数传递(比如
?id=1&name=zs) - 请求内容放在 URL 里,有长度限制
- 可以被缓存、收藏、记录在浏览器历史中
- 不适合传递敏感信息(因为参数可见)
2.1 用原生 XHR 发 GET 请求
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/users?id=1001', true);
xhr.onreadystatechange = function () {
// readyState 为 4 表示请求完成
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 请求成功,解析 JSON 数据
var data = JSON.parse(xhr.responseText);
console.log('获取到的用户信息:', data);
document.getElementById('userName').innerText = data.name;
} else {
console.error('请求失败,状态码:', xhr.status);
}
}
};
xhr.send(); // GET 请求不需要传 body,send() 直接调用即可
解读一下这段代码:
new XMLHttpRequest()创建请求对象xhr.open('GET', url, true)初始化请求,第三个参数true表示异步onreadystatechange监听请求状态变化,readyState === 4表示完成xhr.status === 200表示服务器正常响应JSON.parse()把服务器返回的字符串转成对象send()发送请求,GET 不需要参数
2.2 用 fetch 发 GET 请求(现代写法)
fetch('https://api.example.com/users?id=1001')
.then(response => {
// 先检查 HTTP 状态码是否正常
if (!response.ok) {
throw new Error(`HTTP 错误!状态码:${response.status}`);
}
// 解析 JSON
return response.json();
})
.then(data => {
console.log('用户信息:', data);
document.getElementById('userName').innerText = data.name;
})
.catch(error => {
console.error('请求出错:', error);
});
fetch 写法更简洁,但它有一个坑:即使服务器返回 404 或 500,fetch 的 Promise 也不会 reject,而是正常 resolve。所以我们必须手动检查 response.ok。
2.3 用 axios 发 GET 请求
axios.get('https://api.example.com/users', {
params: { id: 1001 } // 参数会自动拼接到 URL
})
.then(response => {
console.log('用户信息:', response.data);
})
.catch(error => {
console.error('请求出错:', error.message);
});
axios 的好处是:参数写在 params 里,它会自动处理 URL 编码,不需要手动拼字符串。而且它会自动解析 JSON,response.data 直接就是对象。
2.4 GET 的典型使用场景
| 场景 | 说明 |
|---|---|
| 获取用户列表 | 分页、筛选、搜索 |
| 加载文章详情 | 点击文章跳转到详情页 |
| 获取商品数据 | 电商网站的首页商品推荐 |
| 下拉加载更多 | 无限滚动场景 |
| 搜索联想 | 输入关键词实时返回建议 |
| 获取天气数据 | 输入城市名返回天气信息 |
三、POST 请求:往服务器”送”数据
POST 用于向服务器提交数据,比如登录、注册、提交表单、上传文件等。它的特点是:
- 数据放在请求体(body)中,不暴露在 URL 里
- 没有长度限制(理论上)
- 不被缓存,不会出现在浏览器历史中
- 适合传递敏感信息(但仍建议配合 HTTPS 使用)
3.1 用原生 XHR 发 POST 请求
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.example.com/users', true);
// 必须设置请求头,告诉服务器发送的数据格式
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 201) { // 201 表示资源创建成功
var data = JSON.parse(xhr.responseText);
console.log('创建成功,新用户 ID:', data.id);
} else if (xhr.status === 400) {
console.error('参数错误,请检查提交的数据');
} else {
console.error('请求失败,状态码:', xhr.status);
}
}
};
// 请求体必须是 JSON 字符串,不能用对象直接传
var postData = JSON.stringify({
name: '张三',
email: 'zhangsan@example.com',
age: 25
});
xhr.send(postData);
注意几个细节:
setRequestHeader必须放在open()和send()之间Content-Type设置为application/json,表示发送的是 JSON 数据send()的参数必须是字符串,所以要用JSON.stringify()序列化
3.2 用 fetch 发 POST 请求
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
// 如果需要认证,可以加 token
// 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIs...'
},
body: JSON.stringify({
name: '张三',
email: 'zhangsan@example.com',
age: 25
})
})
.then(response => {
if (!response.ok) {
throw new Error(`创建失败,状态码:${response.status}`);
}
return response.json();
})
.then(data => {
console.log('新用户创建成功,ID:', data.id);
})
.catch(error => {
console.error('请求出错:', error);
});
3.3 用 axios 发 POST 请求
axios.post('https://api.example.com/users', {
name: '张三',
email: 'zhangsan@example.com',
age: 25
})
.then(response => {
console.log('新用户创建成功,ID:', response.data.id);
})
.catch(error => {
// 可以区分是网络错误还是服务器返回了错误
if (error.response) {
console.error('服务器错误:', error.response.status, error.response.data);
} else {
console.error('网络或请求配置错误:', error.message);
}
});
axios 发送 POST 时,第二个参数直接传对象,它会自动序列化成 JSON 并设置正确的 Content-Type,非常省心。
3.4 POST 的典型使用场景
| 场景 | 说明 |
|---|---|
| 用户登录 | 提交账号密码 |
| 用户注册 | 提交新用户信息 |
| 提交表单 | 评论、留言、反馈 |
| 上传文件 | 头像上传、附件提交 |
| 修改数据 | 更新用户信息 |
| 删除数据 | 虽然 RESTful 规范建议用 DELETE,但很多后端也用 POST |
| 批量操作 | 一次删除多个商品、批量审核等 |
3.5 POST 和 GET 的关键区别
┌─────────────┬──────────────────────┬──────────────────────┐
│ 对比项 │ GET │ POST │
├─────────────┼──────────────────────┼──────────────────────┤
│ 数据位置 │ URL 查询参数 │ 请求体 (body) │
│ 数据长度 │ 受 URL 长度限制 │ 理论上无限制 │
│ 可见性 │ 参数暴露在 URL │ 参数在请求体中 │
│ 缓存 │ 可被缓存 │ 默认不缓存 │
│ 安全性 │ 较低 │ 相对较高 │
│ 幂等性 │ 是(重复请求结果相同)│ 否(重复提交可能重复)│
│ 典型用途 │ 获取数据 │ 提交/修改数据 │
└─────────────┴──────────────────────┴──────────────────────┘
四、跨域问题:浏览器”门神”背后的故事
4.1 什么是跨域?
跨域是指:一个页面发起的请求,目标地址和当前页面的地址不同源。
所谓”同源”,就是协议、域名、端口三者完全一致。只要有一个不一样,就是跨域。
当前页面:https://www.example.com:8080/page/index.html
以下请求是跨域的:
❌ http://www.example.com/api → 协议不同(http vs https)
❌ https://api.example.com/api → 域名不同(www vs 无 www)
❌ https://www.example.com:3000/api → 端口不同(8080 vs 3000)
❌ https://admin.example.com/api → 域名不同
以下请求不跨域:
✅ https://www.example.com/api → 协议、域名、端口完全一致
浏览器出于安全考虑(同源策略),会拦截跨域请求的响应。但请求本身还是发出去了,只是你拿不到返回的数据。这就是我们常说的”跨域报错”。
4.2 跨域报错长什么样?
在浏览器控制台里,你会看到类似的错误:
Access to XMLHttpRequest at 'https://api.example.com/users'
from origin 'https://www.example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
这段报错的意思是:目标服务器没有返回 CORS 相关的响应头,浏览器拦截了响应。
4.3 解决跨域的常见方法
方法一:后端配置 CORS 响应头(最推荐)
CORS(Cross-Origin Resource Sharing,跨源资源共享)是浏览器的标准方案。核心思路是:后端在响应头里告诉浏览器”我允许这个域名的请求”。
服务端需要返回以下响应头:
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Node.js (Express) 示例:
const express = require('express');
const app = express();
// 中间件:允许所有请求通过(生产环境建议限制具体域名)
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'https://www.example.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// 预检请求直接返回 204
if (req.method === 'OPTIONS') {
return res.sendStatus(204);
}
next();
});
app.get('/users', (req, res) => {
res.json({ id: 1, name: '张三' });
});
app.listen(3000);
更简洁的方式——使用 cors 中间件:
const cors = require('cors');
app.use(cors({
origin: 'https://www.example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true // 允许携带 Cookie
}));
Python (Flask) 示例:
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app, origins=['https://www.example.com'],
methods=['GET', 'POST'],
supports_credentials=True)
@app.route('/users')
def get_users():
return jsonify({'id': 1, 'name': '张三'})
Java (Spring Boot) 示例:
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("https://www.example.com");
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return new CorsFilter(source);
}
}
nginx 反向代理配置:
location /api/ {
# 代理到后端服务
proxy_pass http://backend:3000/;
# CORS 响应头
add_header 'Access-Control-Allow-Origin' 'https://www.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
# 处理预检请求
if ($request_method = 'OPTIONS') {
return 204;
}
}
方法二:前端开发环境配置代理(开发阶段最常用)
在前端开发时,前后端通常不在同一个域名/端口。这时可以在前端项目配置一个代理,让所有请求都先发到前端服务器,再由前端服务器转发到后端。
Vue CLI(vue.config.js):
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
}
}
}
};
配置后,前端代码中请求 /api/users 会自动转发到 https://api.example.com/users,浏览器看到的是同源请求,不存在跨域问题。
React(package.json):
{
"proxy": "https://api.example.com"
}
Vite(vite.config.js):
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
};
Webpack Dev Server(webpack.config.js):
module.exports = {
devServer: {
proxy: {
'/api': 'https://api.example.com'
}
}
};
注意: 代理方案只在开发环境有效,生产环境还是需要后端配置 CORS。
方法三:JSONP(老项目常见,不推荐新项目使用)
JSONP 利用了 <script> 标签不受同源策略限制的特点。后端返回一段 JavaScript 代码,前端定义一个回调函数来接收数据。
// 前端
function handleUserData(data) {
console.log('用户数据:', data);
}
var script = document.createElement('script');
script.src = 'https://api.example.com/users?callback=handleUserData';
document.body.appendChild(script);
// 后端返回的响应(注意是 JS 代码,不是 JSON)
handleUserData({"id": 1, "name": "张三"});
局限性:
- 只支持 GET 请求
- 需要后端特殊支持
- 存在 XSS 风险
- 现代项目基本不再使用
方法四:后端 Nginx 反向代理(生产环境部署方案)
如果后端不方便改代码,可以在 Nginx 层面做统一代理。前端请求 /api/*,Nginx 转发到后端,浏览器完全感知不到跨域。
server {
listen 80;
server_name www.example.com;
# 前端静态资源
location / {
root /var/www/frontend;
index index.html;
}
# 后端 API 代理
location /api/ {
proxy_pass http://backend-server:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
4.4 跨域问题排查清单
当遇到跨域报错时,按以下步骤检查:
1. 确认跨域类型
├── 简单请求(GET/POST,Content-Type 为 application/x-www-form-urlencoded/multipart/form-data/text/plain)
└── 非简单请求(PUT/DELETE、自定义 Header、Content-Type 为 application/json 等)
2. 检查响应头
└── 是否有 Access-Control-Allow-Origin
3. 如果是预检请求(OPTIONS)
└── 后端是否正确响应了 204
4. 是否需要携带 Cookie/认证信息
└── 前端:credentials: 'include' 或 withCredentials: true
└── 后端:Access-Control-Allow-Credentials: true
⚠️ 注意:允许凭证时,Allow-Origin 不能设置为 *
携带凭证(Cookie)的完整配置:
// 前端
fetch('https://api.example.com/users', {
method: 'GET',
credentials: 'include' // 关键:允许携带 Cookie
});
// 后端响应头必须同时设置:
Access-Control-Allow-Origin: https://www.example.com // 不能是 *
Access-Control-Allow-Credentials: true
// axios 方式
axios.get('https://api.example.com/users', {
withCredentials: true // 关键
});
五、常见错误排查:AJAX 请求出问题怎么办?
5.1 网络层面错误
错误:ERR_NAME_NOT_RESOLVED
意思:域名无法解析
原因:域名拼写错误,或 DNS 问题
排查:
1. 检查 URL 的域名是否正确
2. 在浏览器直接访问该 URL,看能否打开
3. 检查网络连接是否正常
错误:ERR_CONNECTION_REFUSED
意思:连接被拒绝
原因:后端服务没有启动,或端口不对
排查:
1. 确认后端服务是否正常运行
2. 检查端口号是否正确
3. 用 curl 或 Postman 直接请求后端,排除前端问题
错误:ERR_CONNECTION_TIMED_OUT
意思:连接超时
原因:防火墙拦截、服务器宕机、网络不通
排查:
1. 检查服务器是否在运行
2. 检查防火墙/安全组规则
3. 确认网络是否能访问目标服务器
5.2 HTTP 状态码错误
| 状态码 | 含义 | 常见原因 | 解决方案 |
|---|---|---|---|
| 400 | 请求参数错误 | 参数缺失、格式不对 | 检查请求参数,对照 API 文档 |
| 401 | 未认证 | token 过期或缺失 | 检查认证信息,重新登录 |
| 403 | 无权限 | 当前用户无权访问 | 检查用户权限 |
| 404 | 资源不存在 | URL 拼写错误、接口下线 | 检查接口路径是否正确 |
| 405 | 方法不允许 | 用 POST 请求了只支持 GET 的接口 | 检查 HTTP 方法 |
| 422 | 参数校验失败 | 后端校验不通过 | 检查请求体数据格式 |
| 500 | 服务器内部错误 | 后端代码 bug | 查看后端日志 |
| 502/503/504 | 网关/服务不可用 | 后端服务挂了或超时 | 联系后端运维 |
401 未认证的处理示例:
// 全局请求拦截:自动附加 token
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, error => {
return Promise.reject(error);
});
// 全局响应拦截:统一处理 401
axios.interceptors.response.use(
response => response,
error => {
if (error.response && error.response.status === 401) {
// token 失效,跳转到登录页
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
}
);
5.3 JSON 解析错误
// 错误写法:服务器返回的不是 JSON,但代码强制解析
fetch('/api/data')
.then(res => res.json()) // 如果返回的是 HTML 或纯文本,这里会报错
.then(data => console.log(data));
// 正确做法:先判断 Content-Type
fetch('/api/data')
.then(response => {
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return response.json();
}
return response.text();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('数据解析失败:', error);
});
常见原因:后端接口返回了错误页面(如 500 时的 HTML 错误页),而不是 JSON。检查 response.headers.get('content-type') 可以提前判断。
5.4 请求挂起/无响应
可能原因:
1. 后端服务处理慢,没有设置超时
2. 防火墙/安全组拦截了请求
3. 跨域预检请求(OPTIONS)被拦截
4. 网络不稳定
解决方案:
// 1. 设置超时(fetch 原生不支持,需要用 AbortController)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 秒超时
fetch('https://api.example.com/users', {
signal: controller.signal
})
.then(res => res.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.error('请求超时');
} else {
console.error('请求失败:', error);
}
})
.finally(() => clearTimeout(timeoutId));
// 2. axios 自带超时配置
axios.get('https://api.example.com/users', {
timeout: 5000 // 5 秒超时
});
5.5 中文乱码
原因:编码不一致,后端返回的字符编码和前端解析的编码不匹配
解决方案:
1. 确保后端设置正确的 Content-Type
Content-Type: application/json; charset=utf-8
2. 前端指定编码
fetch('/api/data', {
headers: {
'Accept': 'application/json; charset=utf-8'
}
});
3. 文件保存编码:前端 JS 文件保存为 UTF-8 无 BOM
5.6 请求成功但数据为空
// 常见原因:响应结构判断错误
fetch('/api/users')
.then(res => res.json())
.then(data => {
// 有些接口返回 { code: 200, data: [...], message: 'success' }
// 直接取 data 是 undefined
console.log(data); // 先打印看看结构
console.log(data.list); // 可能数据在 list 字段
});
// 调试技巧:打印完整响应
fetch('/api/users')
.then(res => {
console.log('状态码:', res.status);
console.log('响应头:', res.headers);
return res.json();
})
.then(data => {
console.log('完整数据:', data);
});
六、最佳实践:让你的 AJAX 请求更可靠
6.1 统一的请求封装
// request.js
import axios from 'axios';
const request = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器
request.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
console.log(`🚀 发起请求:${config.method.toUpperCase()} ${config.url}`);
return config;
},
error => Promise.reject(error)
);
// 响应拦截器
request.interceptors.response.use(
response => {
const res = response.data;
// 根据业务状态码判断
if (res.code === 200) {
return res.data;
}
// 业务错误
return Promise.reject(new Error(res.message || '请求失败'));
},
error => {
const status = error.response?.status;
let message = '网络异常';
switch (status) {
case 400: message = '请求参数错误'; break;
case 401: message = '登录已过期,请重新登录'; break;
case 403: message = '没有权限'; break;
case 404: message = '请求的资源不存在'; break;
case 500: message = '服务器内部错误'; break;
default: message = error.message || '未知错误';
}
console.error(`❌ 请求失败 [${status}]:${message}`);
return Promise.reject(new Error(message));
}
);
export default request;
// 使用封装后的请求
import request from './request';
// GET 请求
async function getUserList(page = 1) {
return request.get('/users', { params: { page } });
}
// POST 请求
async function createUser(userData) {
return request.post('/users', userData);
}
// 调用
getUserList(1).then(list => {
console.log('用户列表:', list);
});
6.2 开发环境代理配置(完整示例)
package.json(简单项目):
{
"name": "my-app",
"proxy": "https://api.example.com"
}
vue.config.js(Vue 项目):
module.exports = {
devServer: {
port: 3000,
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
pathRewrite: { '^/api': '' }
},
'/upload': {
target: 'https://cdn.example.com',
changeOrigin: true
}
}
}
};
vite.config.js(Vite 项目):
import { defineConfig } from 'vite';
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
6.3 错误监控和日志
// 记录所有请求和响应,方便调试
request.interceptors.request.use(config => {
console.group(`📤 ${config.method.toUpperCase()} ${config.url}`);
console.log('请求头:', config.headers);
console.log('请求体:', config.data);
console.groupEnd();
return config;
});
request.interceptors.response.use(response => {
console.group(`📥 ${response.config.method.toUpperCase()} ${response.config.url}`);
console.log('状态码:', response.status);
console.log('响应数据:', response.data);
console.groupEnd();
return response;
}, error => {
console.group(`❌ ${error.config?.method?.toUpperCase()} ${error.config?.url}`);
console.log('错误状态:', error.response?.status);
console.log('错误信息:', error.message);
console.groupEnd();
return Promise.reject(error);
});
七、三种请求方式的对比总结
| 特性 | XHR | fetch | axios |
|---|---|---|---|
| 语法复杂度 | 较高,回调嵌套 | 简洁,Promise | 简洁,Promise |
| JSON 自动解析 | ❌ 需要手动 JSON.parse | ❌ 需要手动 .json() | ✅ 自动解析 |
| 请求拦截 | ❌ 不支持 | ❌ 不支持 | ✅ 支持 |
| 响应拦截 | ❌ 不支持 | ❌ 不支持 | ✅ 支持 |
| 超时控制 | ✅ 支持 | ❌ 需 AbortController | ✅ 原生支持 |
| 取消请求 | ✅ abort() | ✅ AbortController | ✅ CancelToken/AbortController |
| 自动转换数据 | ❌ | ❌ | ✅ |
| 文件上传进度 | ✅ | ❌ | ✅ |
| 浏览器兼容 | ✅ 所有浏览器 | ✅ IE 不支持 | ✅ 所有浏览器 |
| 是否需要引入 | ❌ 原生 | ❌ 原生 | ✅ 需要安装 |
八、给初学者的学习路径建议
如果你刚开始接触 AJAX,建议按这个顺序学习:
第 1 步:理解 HTTP 基础
└── 了解 GET/POST 的区别,知道状态码的含义
第 2 步:学会用浏览器开发者工具
└── 打开 Network 面板,观察请求和响应
第 3 步:掌握原生 XMLHttpRequest
└── 理解回调、readyState、status 等概念
第 4 步:学习 fetch API
└── 理解 Promise 和 async/await
第 5 步:使用 axios
└── 掌握拦截器、封装请求
第 6 步:理解跨域问题
└── 学会配置代理和 CORS
一个小练习,帮你巩固知识:
// 写一个完整的搜索功能:输入关键词,实时搜索并展示结果
async function searchUsers(keyword) {
try {
const response = await fetch(`/api/users/search?q=${encodeURIComponent(keyword)}`, {
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`搜索失败:${response.status}`);
}
const result = await response.json();
renderResults(result.list);
} catch (error) {
console.error('搜索出错:', error);
showError(error.message);
}
}
function renderResults(users) {
const container = document.getElementById('results');
container.innerHTML = users.map(user => `
<div class="user-card">
<h3>${user.name}</h3>
<p>${user.email}</p>
</div>
`).join('');
}
function showError(message) {
document.getElementById('results').innerHTML = `<p class="error">${message}</p>`;
}
// 防抖:避免每次按键都发送请求
let searchTimer;
document.getElementById('searchInput').addEventListener('input', (e) => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
if (e.target.value.trim()) {
searchUsers(e.target.value.trim());
}
}, 300); // 300ms 后发送请求
});
九、最后的小贴士
- 调试时先打开浏览器的 Network 面板,一切问题都能从这里找到线索
- 生产环境永远不要使用
*作为 CORS 的 Allow-Origin,这会带来安全风险 - 敏感操作(登录、支付)一定要用 POST + HTTPS
- 处理 401 时记得清除本地存储的 token,避免无限循环
- 请求超时时间根据业务设置,一般 5-10 秒,文件上传可以适当延长
- 开发时善用代理,可以避开跨域问题,让开发更高效
AJAX 是现代 Web 开发的基石,掌握了 GET、POST 和跨域处理,你就已经迈出了关键的一步。多练多调,遇到问题多看 Network 面板,很快就能游刃有余了。加油!🚀
