Ajax入门:从原生XHR到axios实战

引言

很多刚开始接触前后端交互的同学都有过一个疑问:为什么表单一提交,整个页面就"刷新"跳转了,体验很割裂?而像搜索框输一个字就弹候选词、点赞按钮点一下数字就 +1、注册时输完用户名立刻提示"已被占用"------这些页面不刷新却能实时跟服务器交互的效果,靠的正是 Ajax。

Ajax 是前后端分离开发里最基础、最核心的一项技能。理解了它,你才能明白"前端发请求 → 后端返回数据 → 前端局部更新页面"这套流程到底是怎么跑通的。这篇文章我会从最原始的原生写法讲起,一路讲到如今工程里最常用的 fetch 和 axios,并配上一个完整的 SpringBoot 前后端联调示例。

一、什么是 Ajax

Ajax 是 Asynchronous JavaScript And XML 的缩写,直译"异步 JavaScript 和 XML"。核心思想就一句话:

在不重新加载整个页面 的前提下,通过 JavaScript 向服务器发送请求、接收数据,并局部更新页面的某一块内容。

这里的"异步"是关键------发请求的时候,页面不会卡住,用户可以继续操作,等服务器返回结果后再处理。

一个对比帮你理解:

  • 传统方式:整页刷新。提交表单 → 服务器处理 → 返回新的完整 HTML 页面 → 浏览器整个重载。
  • Ajax 方式:局部更新。JS 发请求 → 服务器只返回一小段数据(通常是 JSON)→ JS 更新页面里的某个 DOM 节点。

如今虽然 XML 已基本被 JSON 取代,但"Ajax"这个名字沿用了下来,泛指这种"异步局部刷新"的技术方案。

二、原生 XMLHttpRequest

XMLHttpRequest(简称 XHR)是 Ajax 的"鼻祖",所有浏览器的 Ajax 能力最初都由它提供。了解它有助于理解底层的请求过程。

2.1 最简示例

javascript

javascript 复制代码
// 1. 创建 XHR 对象
const xhr = new XMLHttpRequest();

// 2. 监听状态变化
xhr.onreadystatechange = function () {
    // readyState === 4 表示请求完成,status === 200 表示成功
    if (xhr.readyState === 4 && xhr.status === 200) {
        // 3. 拿到响应文本(假设服务器返回 JSON)
        const data = JSON.parse(xhr.responseText);
        console.log(data);
    }
};

// 4. 打开连接:GET 请求,目标地址
xhr.open('GET', '/api/user/1', true); // 第三个参数 true 表示异步
// 5. 发送请求
xhr.send();

readyState 的几个值:

readyState 含义
0 未初始化(open 还没调用)
1 已调用 open
2 已调用 send,收到响应头
3 正在接收响应体
4 请求完成

2.2 发送 POST 请求

javascript

javascript 复制代码
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
        if (xhr.status === 200) {
            console.log('成功', xhr.responseText);
        } else {
            console.log('失败', xhr.status);
        }
    }
};

xhr.open('POST', '/api/user/save', true);
// 设置请求头,告诉服务器发送的是 JSON
xhr.setRequestHeader('Content-Type', 'application/json');
// 发送 JSON 字符串
xhr.send(JSON.stringify({ name: '张三', age: 18 }));

2.3 封装一个简单的 ajax 函数

原生写法重复代码太多,我们简单封装一下:

javascript

javascript 复制代码
​
function ajax({ method = 'GET', url, data = null }) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        xhr.setRequestHeader('Content-Type', 'application/json');

        xhr.onreadystatechange = function () {
            if (xhr.readyState !== 4) return;
            if (xhr.status >= 200 && xhr.status < 300) {
                resolve(JSON.parse(xhr.responseText));
            } else {
                reject(new Error('请求失败:' + xhr.status));
            }
        };

        xhr.send(data ? JSON.stringify(data) : null);
    });
}

// 使用
ajax({ url: '/api/user/1' })
    .then(data => console.log(data))
    .catch(err => console.error(err));

​

三、现代推荐:fetch API

XMLHttpRequest 的回调写法比较繁琐,于是浏览器原生提供了更简洁、基于 Promise 的 fetch。

3.1 基本 GET 请求

javascript

javascript 复制代码
fetch('/api/user/1')
    .then(response => response.json()) // 把响应体解析成 JSON
    .then(data => console.log(data))
    .catch(err => console.error('请求出错', err));

3.2 发送 POST 请求

javascript

javascript 复制代码
fetch('/api/user/save', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: '张三', age: 18 })
})
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(err => console.error(err));

3.3 配合 async / await

javascript

javascript 复制代码
async function getUser(id) {
    const response = await fetch('/api/user/' + id);
    const data = await response.json();
    return data;
}

3.4 一个必须记住的坑

fetch 只有在"网络错误"(如断网、域名解析失败)时才会 reject;对于 404、500 这类 HTTP 错误状态码,fetch 并不会 reject ,仍然会正常 resolve,只是 response.ok 为 false。所以要这样判断:

javascript

javascript 复制代码
fetch('/api/user/999')
    .then(response => {
        if (!response.ok) {
            // HTTP 状态码 4xx / 5xx,手动抛错
            throw new Error('HTTP 错误:' + response.status);
        }
        return response.json();
    })
    .then(data => console.log(data))
    .catch(err => console.error(err));

这一点和 axios 不一样,很多新手在这栽过跟头。

四、工程主流:axios

实际项目里,用得最多的还是 axios,它对 fetch 和 XHR 做了更友好的封装,功能也更全(拦截器、超时、取消请求、进度、自动 JSON 转换等)。

html

html 复制代码
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

javascript

javascript 复制代码
// GET
axios.get('/api/user/1')
    .then(res => console.log(res.data))
    .catch(err => console.error(err));

// POST
axios.post('/api/user/save', { name: '张三', age: 18 })
    .then(res => console.log(res.data))
    .catch(err => console.error(err));

// 统一配置 + 拦截器(工程常用)
const http = axios.create({ baseURL: '/api', timeout: 5000 });

http.interceptors.response.use(
    res => res.data,                       // 直接返回 data
    err => {                               // 统一错误处理
        console.error('请求失败', err);
        return Promise.reject(err);
    }
);

axios 的 catch 对 HTTP 错误码(4xx/5xx)也会进入 reject,比 fetch 省心。

五、实战:配合 SpringBoot 前后端联调

下面给一个完整可运行的例子:前端用 Ajax 请求后端接口,实现"查询用户 + 新增用户"。后端沿用你熟悉的 SpringBoot。

5.1 后端接口

java

java 复制代码
@RestController
@RequestMapping("/api/user")
public class UserController {

    // 用一个 Map 简单模拟数据库
    private final Map<Long, String> users = new ConcurrentHashMap<>();

    @GetMapping("/{id}")
    public Map<String, Object> getUser(@PathVariable Long id) {
        String name = users.get(id);
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", name == null ? "未找到" : name);
        return result;
    }

    @PostMapping("/save")
    public Map<String, Object> saveUser(@RequestBody Map<String, Object> body) {
        Long id = Long.valueOf(body.get("id").toString());
        String name = body.get("name").toString();
        users.put(id, name);
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "保存成功");
        return result;
    }
}

5.2 前端页

html

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Ajax 示例</title>
</head>
<body>
    <h2>查询用户</h2>
    <input id="uid" type="number" placeholder="输入用户 ID">
    <button onclick="query()">查询</button>
    <p id="result"></p>

    <h2>新增用户</h2>
    <input id="newId" type="number" placeholder="ID">
    <input id="newName" type="text" placeholder="姓名">
    <button onclick="save()">保存</button>

    <script>
        // 查询(fetch)
        async function query() {
            const id = document.getElementById('uid').value;
            const resp = await fetch('/api/user/' + id);
            const data = await resp.json();
            document.getElementById('result').innerText = JSON.stringify(data);
        }

        // 新增(fetch POST)
        async function save() {
            const id = document.getElementById('newId').value;
            const name = document.getElementById('newName').value;
            const resp = await fetch('/api/user/save', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ id: Number(id), name: name })
            });
            const data = await resp.json();
            alert(data.msg);
        }
    </script>
</body>
</html>

把这个 index.html 放到 SpringBoot 的 src/main/resources/static/ 目录下,启动项目访问 http://localhost:8080 即可体验:点"查询"和"保存",页面不会刷新,结果却实时显示------这就是 Ajax。

六、常见问题与坑

  1. 跨域(CORS) ​:前端页面和后端接口不在同一个域名/端口时,浏览器会拦截响应。解决方式有后端加 @CrossOrigin 注解、配置 CORS、或用 Nginx 反向代理。同源部署(上面示例把 HTML 放进 static 目录)则天然无跨域问题。

  2. fetch 不 reject HTTP 错误 :上文强调过,记得用 response.ok 判断,或用 axios。

  3. GET/POST 数据格式不对 :GET 参数拼在 URL 上;POST 传 JSON 时要设置 Content-Type: application/json 并用 JSON.stringify 序列化。

  4. 回调地狱:异步结果要在回调里处理,嵌套多了可读性差。用 Promise 链式调用或 async/await 解决。

  5. 忘记 await / 顺序错乱 :异步函数里如果漏了 await,代码会继续往下执行,拿到的是 Promise 而不是数据。

总结

Ajax 是前后端分离的基石,掌握它就等于拿到了"前端如何跟后端对话"的钥匙。本文梳理了三条技术路线:

  • XMLHttpRequest:最底层、最原始,理解原理必学,但写起来繁琐。
  • fetch:浏览器原生、基于 Promise,现代开发推荐,但要注意"HTTP 错误不 reject"的坑。
  • axios:功能最全、工程最主流,自带拦截器、超时、自动 JSON 处理,团队项目首选。
相关推荐
豆角焖肉1 小时前
SpringBoot+JWT 实现前后端分离登录鉴权
java·开发语言·spring boot·jwt·前后端分离·登录认证
Wang's Blog1 小时前
Java 项目部署之 Docker工具快速入门: 自定义镜像的分层结构与写时复制
java·开发语言·docker
WeiXin_DZbishe9 小时前
基于springboot宠物领养系统-计算机毕设 附源码【课程设计】94217
vue.js·spring boot·python·django·php·课程设计
vx_bisheyuange10 小时前
基于SpringBoot的体育商品推荐系统
spring boot·毕业设计
别动我齐刘海10 小时前
C++ 工程化学习
java·linux·前端·c++·学习·机器学习·机器人
Wx-bishekaifayuan11 小时前
express皖美特色农产品网售系统53118-计算机课程设计、毕业设计
人工智能·spring boot·后端·python·课程设计·express·旅游
Wx-bishekaifayuan11 小时前
springboot汉中旅游服务平台41442-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
code2cat12 小时前
Java进阶篇之AtomicLongFieldUpdater:把累计额度留在对象字段中
java·cas·并发编程
WeiXin_DZbishe12 小时前
基于springboot城市公交实时运营与调度系统-计算机毕设 附源码【课程设计】66173
java·vue.js·spring boot·后端·python·php·课程设计