说到前端和网络请求,很多刚入行的小伙伴都会觉得这一坨概念有点乱。今天咱们就把 XMLHttpRequest、fetch 和 axios 这三位“选手”掰开了揉碎了讲清楚,顺便把 RESTful 里那几个常见的 HTTP 方法(GET、POST、PUT、DELETE)也理得明明白白。你不用背定义,咱们用大白话加上真实代码场景来聊。

先聊聊历史:那个古老又熟悉的 XMLHttpRequest

要理解现在的技术,得回头看看。在现代前端开发诞生之前,XMLHttpRequest(简称 XHR)是唯一的王。它是浏览器原生提供的 API,用来发送 HTTP 请求。

虽然它现在已经显得有点过时,但很多老项目里还在用它,而且理解它有助于你明白为什么后来的 fetch 要设计成那样。

XHR 的核心特点是基于事件驱动,使用回调函数来处理请求的不同阶段(比如请求发送中、响应接收中、完成等)。它的写法非常啰嗦,层级嵌套深,也就是我们常说的“回调地狱”。

看个典型的 XHR 请求例子:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/users', true);

xhr.onreadystatechange = function () {
  if (xhr.readyState === 4) { // 请求已完成
    if (xhr.status === 200) { // 请求成功
      var users = JSON.parse(xhr.responseText);
      console.log('获取用户列表成功:', users);
    } else {
      console.error('请求失败,状态码:', xhr.status);
    }
  }
};

xhr.onerror = function () {
  console.error('网络错误');
};

xhr.ontimeout = function () {
  console.error('请求超时');
};

xhr.timeout = 3000; // 设置超时时间为3秒
xhr.send();

你看,是不是特别繁琐?每次都要创建对象、绑定多种事件、手动解析 JSON。对于刚学前端的小朋友来说,这简直就是噩梦。不过,它有一个好处:兼容性极好,几乎支持所有浏览器,包括一些非常古老的 IE 版本。

现代的选择:fetch API

随着 HTML5 和 ES6 的普及,浏览器原生提供了一个更简洁、更强大的请求工具——fetch。它是基于 Promise 的,这意味着我们可以用更现代的语法来写异步代码。

fetch 最大的改变是:用法更简单,逻辑更清晰。

看同样的请求,用 fetch 怎么写:

fetch('https://api.example.com/users')
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('获取用户列表成功:', data);
  })
  .catch(error => {
    console.error('请求失败:', error);
  });

是不是简洁多了?不用手动解析 JSON,不用处理复杂的 readyState 状态。fetch 的 Promise 特性让它很容易和 async/await 配合使用:

async function getUsers() {
  try {
    const response = await fetch('https://api.example.com/users');
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const users = await response.json();
    console.log('获取用户列表成功:', users);
  } catch (error) {
    console.error('请求失败:', error);
  }
}

getUsers();

虽然 fetch 很棒,但它也不是完美的。它有几个小坑:

  1. 默认不携带 cookie:除非你明确设置 credentials: 'include'。
  2. 不会在 HTTP 错误时拒绝 Promise:比如 404、500 状态码,fetch 不会自动抛出错误,你需要手动检查 response.ok。
  3. 取消请求比较麻烦:需要借助 AbortController,而且不是所有浏览器都支持。

最受欢迎的第三方库:axios

如果你觉得原生 fetch 还不够方便,或者你的项目需要更强大的功能,那 axios 就是不错的选择。axios 不是一个浏览器 API,而是一个基于 Promise 的 HTTP 客户端库,可以在浏览器和 Node.js 环境中使用。

axios 的优点非常多:

  • 自动转换 JSON 数据:不需要手动调用 .json()。
  • 请求和响应拦截器:可以统一处理 token、错误日志等。
  • 取消请求:支持使用 CancelToken 或 AbortController 轻松取消请求。
  • 自动转换请求和响应数据:比如自动将请求数据序列化为 JSON。
  • 客户端支持防止 CSRF:内置了一些安全机制。

看一个 axios 的简单例子:

// 引入 axios(如果使用 npm)
import axios from 'axios';

// 或者在 HTML 中直接引入
// <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

// 发送 GET 请求
axios.get('https://api.example.com/users')
  .then(response => {
    console.log('获取用户列表成功:', response.data);
  })
  .catch(error => {
    console.error('请求失败:', error);
  });

// 使用 async/await
async function getUsers() {
  try {
    const response = await axios.get('https://api.example.com/users');
    console.log('获取用户列表成功:', response.data);
  } catch (error) {
    console.error('请求失败:', error);
  }
}

axios 还允许你创建一个拦截器,这在大型项目中特别有用。比如,你可以在所有请求发出前自动添加 token,在所有响应返回后统一处理错误:

// 添加请求拦截器
axios.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么,比如添加 token
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

// 添加响应拦截器
axios.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么,比如统一处理业务错误
    if (response.data.code !== 200) {
      return Promise.reject(new Error(response.data.message));
    }
    return response;
  },
  function (error) {
    // 对响应错误做点什么,比如统一处理 HTTP 错误
    if (error.response.status === 401) {
      // 未授权,跳转到登录页
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

三者怎么选?

这是一个很实际的问题。很多开发者会问:“我该用哪个?”

其实没有绝对的答案,取决于你的项目需求和团队习惯。我给你一个简单粗暴的选择指南:

  • 如果你追求零依赖、现代浏览器项目:用 fetch。它的原生支持让它成为轻量级项目的首选,不需要引入额外的库。
  • 如果你需要兼容旧浏览器(比如 IE):用 XMLHttpRequest,或者引入 fetch polyfill。
  • 如果你需要强大的功能(拦截器、取消请求、自动转换数据等):用 axios。它是目前最流行的 HTTP 客户端库,社区支持好,文档齐全。

在很多实际项目中,你会发现 axios 是更普遍的选择,因为它封装了太多实用的功能,让开发效率大大提高。而 fetch 则适合那些希望保持代码简洁、减少依赖的场景。

HTTP 方法:GET、POST、PUT、DELETE 的区别

讲完了请求工具,咱们再聊聊 HTTP 方法。在 RESTful API 设计中,这四个方法是最常用的。它们分别对应着 CRUD(创建、读取、更新、删除)操作。

GET:获取数据

GET 方法用于从服务器获取数据。它是幂等的(多次请求结果相同)且安全的(不会改变服务器状态)。

使用场景:

  • 获取用户列表
  • 查询商品详情
  • 搜索内容

GET 请求的参数通常放在 URL 中,比如:

GET /api/users?page=1&size=10

代码示例:

// fetch 示例
fetch('/api/users?page=1&size=10')
  .then(response => response.json())
  .then(data => console.log(data));

// axios 示例
axios.get('/api/users', {
  params: {
    page: 1,
    size: 10
  }
}).then(response => console.log(response.data));

POST:创建数据

POST 方法用于向服务器提交数据,通常用于创建新资源。它不是幂等的(多次提交可能创建多个资源),也不安全(会改变服务器状态)。

使用场景:

  • 用户注册
  • 提交订单
  • 上传图片

POST 请求的参数通常放在请求体(request body)中。

代码示例:

// fetch 示例
fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    username: 'zhangsan',
    email: 'zhangsan@example.com',
    password: '123456'
  })
})
  .then(response => response.json())
  .then(data => console.log('用户创建成功:', data));

// axios 示例
axios.post('/api/users', {
  username: 'zhangsan',
  email: 'zhangsan@example.com',
  password: '123456'
}).then(response => console.log('用户创建成功:', response.data));

PUT:更新数据

PUT 方法用于更新服务器上的资源。它是幂等的(多次请求结果相同),通常用于替换整个资源。

使用场景:

  • 更新用户信息
  • 修改商品详情

PUT 请求的参数也放在请求体中。注意,PUT 通常是全量更新,即你需要提供资源的所有字段。

代码示例:

// fetch 示例
fetch('/api/users/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    id: 123,
    username: 'zhangsan',
    email: 'zhangsan_new@example.com',
    password: 'newpassword'
  })
})
  .then(response => response.json())
  .then(data => console.log('用户更新成功:', data));

// axios 示例
axios.put('/api/users/123', {
  id: 123,
  username: 'zhangsan',
  email: 'zhangsan_new@example.com',
  password: 'newpassword'
}).then(response => console.log('用户更新成功:', response.data));

DELETE:删除数据

DELETE 方法用于从服务器删除资源。它是幂等的(多次删除结果相同),通常用于删除指定 ID 的资源。

使用场景:

  • 删除用户
  • 删除订单
  • 删除商品

DELETE 请求通常不需要请求体,删除的资源 ID 放在 URL 中。

代码示例:

// fetch 示例
fetch('/api/users/123', {
  method: 'DELETE'
})
  .then(response => {
    if (response.ok) {
      console.log('用户删除成功');
    }
  });

// axios 示例
axios.delete('/api/users/123')
  .then(response => console.log('用户删除成功'));

实际项目中的完整案例

光说不练假把式。咱们来模拟一个完整的前后端交互场景:一个用户管理页面。这个页面需要实现:

  1. 显示用户列表
  2. 添加新用户
  3. 修改用户信息
  4. 删除用户

我们用 axios 来实现,因为它功能更全面。

1. 显示用户列表(GET)

// 获取用户列表
async function fetchUsers() {
  try {
    const response = await axios.get('/api/users', {
      params: {
        page: 1,
        size: 10
      }
    });
    return response.data;
  } catch (error) {
    console.error('获取用户列表失败:', error);
    throw error;
  }
}

// 渲染用户列表
async function renderUserList() {
  const users = await fetchUsers();
  const userListElement = document.getElementById('user-list');
  
  userListElement.innerHTML = users.map(user => `
    <tr>
      <td>${user.id}</td>
      <td>${user.username}</td>
      <td>${user.email}</td>
      <td>
        <button onclick="editUser(${user.id})">编辑</button>
        <button onclick="deleteUser(${user.id})">删除</button>
      </td>
    </tr>
  `).join('');
}

2. 添加新用户(POST)

// 添加用户
async function addUser(userData) {
  try {
    const response = await axios.post('/api/users', userData);
    return response.data;
  } catch (error) {
    console.error('添加用户失败:', error);
    throw error;
  }
}

// 处理表单提交
document.getElementById('add-user-form').addEventListener('submit', async function(e) {
  e.preventDefault();
  
  const userData = {
    username: document.getElementById('username').value,
    email: document.getElementById('email').value,
    password: document.getElementById('password').value
  };
  
  try {
    await addUser(userData);
    alert('用户添加成功!');
    renderUserList(); // 重新加载用户列表
    this.reset(); // 清空表单
  } catch (error) {
    alert('添加用户失败,请重试。');
  }
});

3. 修改用户信息(PUT)

// 编辑用户
async function editUser(userId) {
  try {
    const response = await axios.get(`/api/users/${userId}`);
    const user = response.data;
    
    // 填充表单
    document.getElementById('edit-user-id').value = user.id;
    document.getElementById('edit-username').value = user.username;
    document.getElementById('edit-email').value = user.email;
    
    // 显示编辑模态框
    document.getElementById('edit-user-modal').style.display = 'block';
  } catch (error) {
    console.error('获取用户信息失败:', error);
    alert('获取用户信息失败,请重试。');
  }
}

// 保存修改
document.getElementById('save-user-btn').addEventListener('click', async function() {
  const userId = document.getElementById('edit-user-id').value;
  const userData = {
    id: userId,
    username: document.getElementById('edit-username').value,
    email: document.getElementById('edit-email').value
  };
  
  try {
    await axios.put(`/api/users/${userId}`, userData);
    alert('用户信息更新成功!');
    document.getElementById('edit-user-modal').style.display = 'none';
    renderUserList(); // 重新加载用户列表
  } catch (error) {
    console.error('更新用户失败:', error);
    alert('更新用户失败,请重试。');
  }
});

4. 删除用户(DELETE)

// 删除用户
async function deleteUser(userId) {
  if (!confirm('确定要删除这个用户吗?此操作不可恢复。')) {
    return;
  }
  
  try {
    await axios.delete(`/api/users/${userId}`);
    alert('用户删除成功!');
    renderUserList(); // 重新加载用户列表
  } catch (error) {
    console.error('删除用户失败:', error);
    alert('删除用户失败,请重试。');
  }
}

总结一下

好了,咱们今天聊了不少内容。回顾一下:

  1. XMLHttpRequest:古老但兼容性好,写法啰嗦,适合老项目或需要兼容旧浏览器的场景。
  2. fetch:现代原生 API,基于 Promise,简洁易用,但功能相对有限。
  3. axios:第三方库,功能强大,支持拦截器、取消请求等,是目前最流行的选择。

对于 HTTP 方法:

  • GET:获取数据,参数放 URL。
  • POST:创建数据,参数放请求体。
  • PUT:更新数据,参数放请求体,通常全量更新。
  • DELETE:删除数据,资源 ID 放 URL。

在实际项目中,我建议优先考虑 axios,因为它提供的功能能极大提升开发效率。如果你的项目对体积有严格要求,或者不想引入额外依赖,可以用 fetch。至于 XMLHttpRequest,除非你有特殊的兼容性需求,否则没必要再去用了。

希望这篇文章能帮你理清思路,下次再遇到这些问题,你就知道该怎么选了!如果有其他疑问,欢迎随时交流。