嘿,小朋友!我是 Agnes。今天我们要聊一个听起来很酷、其实超级有意思的话题——AJAX

别被这个名字吓跑啦!AJAX 其实就是“让网页动起来的小魔法”。如果你发现有些网页点一下按钮,不用刷新整个页面,就能加载出新的内容(比如刷微博、看抖音推荐),那就是 AJAX 在背后偷偷干活呢!

而且呀,今天我们要学的东西,连 6 岁的小朋友都能听懂原理。为什么?因为我们用讲故事的方式来学,而不是背枯燥的公式。

准备好了吗?我们要开始这场“网页探险”啦!


🌟 第一部分:AJAX 到底是个啥?(6 岁也能懂的故事)

想象一下,你在家里吃午饭,但是米饭还没煮熟

传统的网页(没有 AJAX)

以前的网页特别笨。你想加个菜,怎么办?全部重做

  1. 你把整张桌子掀了。
  2. 把碗、盘子、筷子全扔了。
  3. 跑去厨房重新买米、买菜、做饭。
  4. 把桌子重新摆好,饭也做好了。

这叫页面刷新。是不是很麻烦?很浪费时间?而且你吃到一半的汤都洒了!

有 AJAX 的网页

现在有魔法助手 AJAX 了!

  1. 你坐在桌子前,正在吃米饭。
  2. 你说:“我要加个红烧肉!”
  3. AJAX 说:“好的,我去帮你买红烧肉,你继续吃米饭。”
  4. AJAX 跑向厨房(服务器),买回来红烧肉,直接放在你面前的盘子里。
  5. 你全程不用动,碗也没碎,只是多了一道菜!

这就是 AJAX 的核心原理:

不刷新整个网页的情况下,偷偷去后台取数据,然后把取来的数据更新到网页的某个角落。

什么是 GET 和 POST?

AJAX 要去厨房“点菜”时,有两种方式:

1. GET 请求(点菜看菜单)

就像你问妈妈:“妈,今天有红烧肉吗?”

  • 你只是查询信息。
  • 你不会改变厨房里的任何东西。
  • 就像你用手机搜索“北京天气”,这只是获取数据。

特点:安全、只读、数据放在 URL 后面。

2. POST 请求(下单做菜)

就像你对服务员说:“我要一份红烧肉!”

  • 你要提交数据。
  • 厨房会真的做一道菜(数据被改变了)。
  • 就像你在网上注册账号发表博客转账付款,这些都要用 POST。

特点:会改变服务器数据、更安全(密码不会出现在 URL 里)。


🛠️ 第二部分:原生 JavaScript 怎么写 AJAX?(老派但有用)

以前,程序员用 XMLHttpRequest 写 AJAX。这个名字很长,我们叫它 XHR

虽然现在已经不常用了,但你要知道它的存在,因为很多老代码里还有它。

发起 GET 请求(原生写法)

// 1. 创建 AJAX 对象
var xhr = new XMLHttpRequest();

// 2. 告诉它我们要去哪里(URL),用什么方法(GET)
xhr.open('GET', 'https://api.example.com/user/1');

// 3. 告诉它:当我收到回复时,执行这个函数
xhr.onload = function() {
    // 如果成功了(状态码 200),就把数据打印出来
    if (xhr.status >= 200 && xhr.status < 300) {
        console.log('成功啦!数据是:', xhr.responseText);
    } else {
        console.log('出错了,错误码:', xhr.status);
    }
};

// 4. 如果网络断了,也要处理
xhr.onerror = function() {
    console.log('网络错误,请检查网络哦!');
};

// 5. 真正发送请求
xhr.send();

问题:你看,代码层层嵌套,像俄罗斯套娃一样,还容易写成“回调地狱”( Callback Hell )。而且,如果我要同时发 3 个请求,还得写 3 遍?太麻烦了!


🚀 第三部分:现代主角登场!Fetch API(浏览器内置)

后来,浏览器进化了!推出了一个叫 Fetch 的新技能。它更简洁,更像日常说话。

发起 GET 请求(Fetch 写法)

fetch('https://api.example.com/user/1')
    .then(response => {
        // 检查是否成功(Fetch 不会自动认为 404 是错误,需要手动判断)
        if (!response.ok) {
            throw new Error('网络响应错误:' + response.status);
        }
        // 把响应变成 JSON 格式
        return response.json();
    })
    .then(data => {
        // 拿到真正的数据
        console.log('用户数据:', data);
    })
    .catch(error => {
        // 如果上面任何一步出错,都会跑到这里
        console.log('哎呀,出错了:', error);
    });

优点

  • 代码更短,看起来更干净。
  • 返回的是 Promise(承诺),意思是:“我现在去取数据,拿到后告诉你,你不用傻等。”

发起 POST 请求(Fetch 写法)

fetch('https://api.example.com/user', {
    method: 'POST',  // 告诉服务器,这是提交数据
    headers: {
        'Content-Type': 'application/json'  // 告诉服务器:“我要给你的是 JSON 格式的数据”
    },
    body: JSON.stringify({  // 把 JavaScript 对象变成字符串
        name: '小明',
        age: 6,
        favFood: '红烧肉'
    })
})
.then(response => response.json())  // 把服务器的回复也解析成 JSON
.then(data => {
    console.log('创建成功!新用户的 ID 是:', data.id);
})
.catch(error => {
    console.log('提交失败:', error);
});

注意:Fetch 有个小脾气——如果服务器返回 404500,它不会报错!它还是觉得成功了,只是数据是空的。所以我们要手动检查 response.ok


🧰 第四部分:Axios——程序员的最爱(封装神器)

虽然 Fetch 很好,但很多程序员还是喜欢用 Axios。为什么?

因为 Axios 更“智能”:

  1. 它会自动把 JSON 数据转成字符串(不用手动 JSON.stringify)。
  2. 它会自动把服务器返回的字符串解析成 JSON(不用手动 .json())。
  3. 它有拦截器(Interceptors),可以在发送前和收到后偷偷做点事情(比如加令牌、处理全局错误)。

安装 Axios

如果你用浏览器直接引入:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

如果你用 Node.js 或 npm:

npm install axios

GET 请求(Axios 写法)

axios.get('https://api.example.com/user/1')
    .then(response => {
        // 数据在 response.data 里
        console.log('用户信息:', response.data);
    })
    .catch(error => {
        console.log('出错了:', error);
    });

POST 请求(Axios 写法)

axios.post('https://api.example.com/user', {
    name: '小明',
    age: 6
})
.then(response => {
    console.log('创建成功:', response.data);
})
.catch(error => {
    console.log('失败啦:', error);
});

哇!是不是比 Fetch 简洁多了? Axios 帮我们处理了很多繁琐的细节。


🔧 第五部分:超级封装——让请求像搭积木一样简单

现在,你是专家了!我们来自己写一个超级封装的工具。以后你只需要写一行代码,就能发请求,还能自动处理错误、显示加载动画。

步骤 1:创建 axios.js 文件

import axios from 'axios';

// 创建 Axios 实例(相当于创建了一个专属的小助手)
const request = axios.create({
    baseURL: 'https://api.example.com',  // 所有请求都自动带上这个前缀
    timeout: 5000,  // 如果 5 秒还没反应,就放弃
    headers: {
        'Content-Type': 'application/json'
    }
});

// --- 拦截器:发送请求之前,偷偷做的事 ---
request.interceptors.request.use(
    config => {
        // 比如,从 localStorage 取出用户的登录令牌
        const token = localStorage.getItem('token');
        if (token) {
            config.headers.Authorization = `Bearer ${token}`;  // 把令牌加到请求头里
        }
        
        // 显示加载动画
        console.log('正在请求数据,请稍候...');
        return config;
    },
    error => {
        return Promise.reject(error);
    }
);

// --- 拦截器:收到响应之后,偷偷做的事 ---
request.interceptors.response.use(
    response => {
        // 如果服务器返回 200,但数据里 code 不是 1,说明业务出错了
        const res = response.data;
        if (res.code === 1) {
            return res.data;  // 返回真正的数据
        } else {
            // 业务错误,弹出提示
            alert('出错了:' + res.message);
            return Promise.reject(new Error(res.message));
        }
    },
    error => {
        // 网络错误或 HTTP 错误(如 404、500)
        console.error('网络错误:', error.message);
        
        // 如果是 401,说明登录过期了,跳回登录页
        if (error.response && error.response.status === 401) {
            alert('登录已过期,请重新登录!');
            window.location.href = '/login';
        }
        
        return Promise.reject(error);
    }
);

// 导出封装好的 request
export default request;

步骤 2:在项目里使用(超级简洁!)

import request from './axios';  // 导入我们刚才封装的工具

// GET 请求:取用户信息
async function getUser() {
    try {
        // 直接拿到数据,不用 .then,不用 catch,因为 async/await 更优雅!
        const userData = await request.get('/user/1');
        console.log('用户名字:', userData.name);
    } catch (error) {
        // 这里只会捕获到封装里 reject 的错误
        console.log('获取失败');
    }
}

// POST 请求:创建用户
async function createUser() {
    try {
        const result = await request.post('/user', {
            name: '小红',
            age: 7
        });
        console.log('创建成功,新 ID:', result.id);
    } catch (error) {
        console.log('创建失败');
    }
}

看! 代码是不是像读句子一样简单?getUser() 就像是说:“去,把用户 1 的信息拿来。”


🌍 第六部分:跨域——网页的“小区门禁”

什么是跨域?

想象你住在小区 Alocalhost:3000),你想去小区 Bapi.example.com)借东西。

小区 A 的门卫说:“不行! 你没经过允许,不能随便进 B 小区。这是浏览器的安全规定,叫同源策略。”

同源的意思是:协议(http/https)、域名(xxx.com)、端口(80/3000)都完全一样,才叫同源。

只要有一个不一样,就是跨域

为什么要有跨域限制?

防止坏人偷偷访问你的银行网站,把你的钱转走。如果没有这个限制,任何网站都能偷偷读取你的数据,那世界就乱套了!

怎么解决跨域?(三种方法)

方法 1:后端设置 CORS(最推荐!)

让后端(服务器)在回复时,告诉浏览器:“我允许这个网站访问我!

后端代码(以 Node.js 为例):

app.use((req, res, next) => {
    // 允许来自 http://localhost:3000 的请求
    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
    // 允许发送 Cookie
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    next();
});

前端不需要做任何事,请求就能成功!

方法 2:使用代理(开发时用)

在你自己的电脑上,搭一个“中转站”。

前端请求:http://localhost:3000/api/user (同源,没问题!) 中转站转发:http://api.example.com/user (服务器之间没有跨域限制)

配置方法(Vite 项目 vite.config.js):

export default {
    server: {
        proxy: {
            '/api': {
                target: 'https://api.example.com',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/api/, '')
            }
        }
    }
}

方法 3:JSONP(古老方法,现在很少用)

利用 <script> 标签可以跨域加载资源的特性。

// 旧时代写法,不推荐,但有历史意义
<script>
function callback(data) {
    console.log(data);
}
</script>
<script src="https://api.example.com/data?callback=callback"></script>

缺点:只能用于 GET 请求,而且不安全。


🐛 第七部分:错误拦截——别让程序崩溃

在实际项目中,网络可能断掉,服务器可能宕机,数据可能为空。我们要预判错误,而不是等程序报错。

常见错误类型

  1. 网络错误:手机没信号,WiFi 断了。
  2. HTTP 错误:服务器返回 404(找不到)、403(没权限)、500(服务器内部错误)。
  3. 业务错误:服务器返回 200,但数据里说“密码错误”。

封装错误处理函数

// utils/api.js
import request from './axios';

// 统一的错误处理函数
function handleApiError(error) {
    if (error.response) {
        // 服务器返回了错误状态码
        switch (error.response.status) {
            case 400:
                alert('请求参数错误,请检查输入!');
                break;
            case 401:
                alert('登录已过期,请重新登录!');
                window.location.href = '/login';
                break;
            case 403:
                alert('你没有权限访问这个功能!');
                break;
            case 404:
                alert('找不到你要的数据!');
                break;
            case 500:
                alert('服务器开小差了,请稍后再试!');
                break;
            default:
                alert('未知错误:' + error.response.status);
        }
    } else if (error.request) {
        // 网络断了,请求发出去了但没收到回复
        alert('网络已断开,请检查你的网络连接!');
    } else {
        // 其他错误
        alert('发生错误:' + error.message);
    }
}

// 导出所有 API 请求
export const api = {
    getUser: (id) => request.get(`/user/${id}`).catch(handleApiError),
    createUser: (data) => request.post('/user', data).catch(handleApiError),
    deleteUser: (id) => request.delete(`/user/${id}`).catch(handleApiError)
};

使用:

import { api } from './utils/api';

async function deleteUser() {
    await api.deleteUser(1);  // 如果有错误,会自动弹窗提示
}

🎓 第八部分:给 6 岁小朋友的总结

好了,小朋友,今天我们学了好多!我们来画一张地图,把知识串起来。

🗺️ AJAX 知识地图

[我要数据] 
    |
    v
[选择方式] ----> GET(只看不改,像看书)
                |
                ----> POST(要改数据,像写作业提交)
                    |
                    v
            [发送请求] 
                |
                v
    [服务器] <-----> [浏览器] (中间有门卫:跨域)
                |
                v
        [收到数据]
                |
                v
    [处理数据] ----> 成功:显示给用户
                |
                ----> 失败:弹窗告诉用户“出错了”

📝 三个主角对比

主角 特点 适合谁
XHR 老派,代码多,像用算盘 维护老项目
Fetch 现代,简洁,但有点小脾气 简单项目,不想装库
Axios 强大,智能,有拦截器 推荐! 大多数项目

💡 记住这几点

  1. AJAX = 不刷新页面,偷偷取数据。
  2. GET = 查询,POST = 提交。
  3. 跨域 = 浏览器的小区门禁,让后端开绿灯(CORS)或自己搭桥(代理)。
  4. 错误处理 = 永远不要相信