Ajax 请求方法 GET POST PUT DELETE 详解与实战 前端开发者必看

说实话,很多刚入行的小伙伴对 Ajax 请求的理解还停留在”能用就行”的层面。今天咱们就把它掰开了、揉碎了讲清楚,让你不仅会用,还能明白为什么这么用。


一、为什么要有这四种方法?

先把背景说清楚。HTTP 协议定义了多种请求方法,其中最常用的就是 GET、POST、PUT、DELETE,它们合称 RESTful 风格的操作。你可以把它们想象成对数据库里某条数据的四种基本操作:

  • GET —— 查
  • POST —— 增
  • PUT —— 改(整体替换)
  • DELETE —— 删

听起来很简单对吧?但真正用的时候,90% 的人都在用错。接下来我们一个个拆解。


二、GET 请求:最熟悉也最容易踩坑

基本概念

GET 是用来获取数据的,它会把参数拼在 URL 后面。比如:

https://api.example.com/users?page=1&size=10

参数就是 page=1&size=10,直接暴露在 URL 里,所以敏感信息千万别用 GET 传

代码实战

我们用浏览器自带的 fetch API 来演示(现在几乎没人用原始的 XMLHttpRequest 了,除非你要兼容 IE):

// 最简单的 GET 请求
fetch('https://api.example.com/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('请求失败:', error));

带参数的 GET 请求,有两种写法,我推荐第二种:

// 写法一:手动拼接 URL(容易出错,不推荐)
const url = 'https://api.example.com/users?page=' + page + '&size=' + size;
fetch(url);

// 写法二:用 URLSearchParams(清晰、安全)
const params = new URLSearchParams({
  page: 1,
  size: 10,
  keyword: '张三'
});
fetch(`https://api.example.com/users?${params}`);

实际项目中的常见用法

假设你在做一个搜索列表页,用户输入关键词后触发请求:

async function searchUsers(keyword) {
  // 防抖处理,避免用户每敲一个字就发一次请求
  const params = new URLSearchParams({ keyword });
  
  try {
    const response = await fetch(`https://api.example.com/users?${params}`);
    
    if (!response.ok) {
      throw new Error(`HTTP 错误!状态码:${response.status}`);
    }
    
    const result = await response.json();
    return result.data; // 假设接口返回 { code: 200, data: [...], total: 100 }
  } catch (error) {
    console.error('搜索失败', error);
    return [];
  }
}

// 在搜索框的 input 事件中使用
searchInput.addEventListener('input', debounce(async (e) => {
  const results = await searchUsers(e.target.value);
  renderList(results);
}, 300));

GET 请求的三个重要特性

  1. 幂等 —— 多次执行 GET 请求,结果完全一样,不会产生副作用。这是 HTTP 规范的要求,后端也必须遵守。
  2. 有缓存 —— 浏览器会对 GET 请求做缓存,这就是为什么刷新页面时 GET 请求有时候不会真的发网络请求。
  3. 长度限制 —— URL 长度有限制(不同浏览器不一样,一般 2KB~8KB),所以别试图用 GET 传大段数据。

三、POST 请求:提交数据的主战场

基本概念

POST 用来提交数据,参数放在请求体(Request Body)里,不会暴露在 URL 中。适合传敏感信息、大段数据、文件上传等场景。

代码实战

// 提交表单数据
async function createUser(userData) {
  try {
    const response = await fetch('https://api.example.com/users', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        // 如果有鉴权,加上 token
        // 'Authorization': 'Bearer ' + token
      },
      body: JSON.stringify(userData)
    });

    if (!response.ok) {
      // 统一处理错误
      const errorData = await response.json().catch(() => ({}));
      throw new Error(errorData.message || `请求失败:${response.status}`);
    }

    const result = await response.json();
    return result.data; // 返回创建的用户信息
  } catch (error) {
    console.error('创建用户失败', error);
    throw error; // 把错误抛出去,让调用方决定怎么处理
  }
}

// 使用示例
createUser({
  name: '小明',
  email: 'xiaoming@example.com',
  age: 25
}).then(user => {
  console.log('创建成功', user);
});

POST 的两种常见数据格式

很多新手分不清 application/jsonapplication/x-www-form-urlencoded 的区别,这里详细说:

1. application/json(现代项目主流)

fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: '小明', age: 25 })
});

请求体长这样:

{"name":"小明","age":25}

2. application/x-www-form-urlencoded(传统表单提交)

const formData = new URLSearchParams();
formData.append('name', '小明');
formData.append('age', '25');

fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: formData.toString()
});

请求体长这样:

name=%E5%B0%8F%E6%98%8E&age=25

💡 选哪个? 前端项目几乎全选 JSON 格式。除非你要兼容一些老的后端接口,或者做表单提交且不想引入额外逻辑,才用 form 格式。

文件上传的 POST

async function uploadAvatar(file) {
  const formData = new FormData();
  formData.append('avatar', file); // 第一个参数是后端字段名

  const response = await fetch('https://api.example.com/upload', {
    method: 'POST',
    body: formData
    // 注意:用 FormData 时不要手动设置 Content-Type
    // 浏览器会自动设置,并加上 boundary 参数
  });

  return await response.json();
}

四、PUT 请求:整体更新

基本概念

PUT 用来更新资源,但它的核心语义是”整体替换”。也就是说,你 PUT 过去什么,服务端就把那条记录完整覆盖成什么样。如果某字段你没传,服务端可能会把它置空。

这点非常重要,也是 PUT 和 PATCH 的区别所在。

代码实战

// 用 PUT 更新一个用户的完整信息
async function updateUser(userId, fullUserData) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(fullUserData)
      // fullUserData 应该包含该用户的所有字段
      // 比如 { name: '小明', email: 'xxx@.com', age: 26, role: 'admin' }
    });

    if (!response.ok) {
      throw new Error(`更新失败:${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error('更新用户失败', error);
    throw error;
  }
}

// 调用示例
updateUser('123', {
  name: '小明',
  email: 'xiaoming_new@example.com',
  age: 26,
  role: 'user'
});

PUT vs PATCH 怎么选?

这是面试常考题,也是实际开发中最容易混淆的地方:

特性 PUT PATCH
语义 整体替换 部分更新
必需字段 需要传完整数据 只传要改的字段
幂等性 ✅ 幂等 ❌ 不保证幂等
适用场景 表单完整编辑页 只改某一个字段

举个例子:

// PUT:用户编辑整个表单,提交完整数据
PUT /api/users/123
Body: { "name": "小明", "email": "a@b.com", "age": 26, "role": "user" }

// PATCH:用户只改了昵称
PATCH /api/users/123
Body: { "name": "小明同学" }

📌 实际项目中,很多后端不太严格区分这两个,所以你们对接的时候最好先确认一下接口文档。别想当然。


五、DELETE 请求:删数据

基本概念

DELETE 用来删除资源,通常只需要传要删除的 ID,参数放在 URL 里。比如 DELETE /api/users/123

代码实战

// 删除单个用户
async function deleteUser(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`, {
      method: 'DELETE',
      headers: {
        'Content-Type': 'application/json'
      }
      // DELETE 请求一般不需要 body,如果后端需要可以传
    });

    if (!response.ok) {
      throw new Error(`删除失败:${response.status}`);
    }

    // 有些后端删除成功返回 204 No Content,没有 body
    // 这时候 response.json() 会报错,需要特殊处理
    const contentType = response.headers.get('content-type');
    if (contentType && contentType.includes('json')) {
      return await response.json();
    }
    return { success: true };
  } catch (error) {
    console.error('删除用户失败', error);
    throw error;
  }
}

// 删除多个用户
async function deleteUsers(userIds) {
  const response = await fetch('https://api.example.com/users/batch', {
    method: 'DELETE',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ids: userIds })
  });
  return await response.json();
}

DELETE 请求的安全提示

删除操作是不可逆的,前端一定要加确认:

async function handleDelete(userId, userName) {
  const confirmed = confirm(`确定要删除用户「${userName}」吗?此操作不可恢复。`);
  
  if (!confirmed) return;

  try {
    await deleteUser(userId);
    showToast('删除成功', 'success');
    refreshList(); // 删除后刷新列表
  } catch (error) {
    showToast('删除失败,请重试', 'error');
  }
}

六、封装一个通用的请求工具

实际项目中,你不会在每个地方都写一遍 fetch。通常会封装一个统一的工具函数,处理拦截、错误、超时等公共逻辑。

// request.js
const BASE_URL = 'https://api.example.com';
const TIMEOUT = 10000;

class Request {
  constructor() {
    this.pendingRequests = new Set();
  }

  // 生成唯一请求 ID,用于取消重复请求
  generateRequestId(method, url) {
    return `${method}_${url}`;
  }

  // 核心请求方法
  async request(options) {
    const {
      url,
      method = 'GET',
      data,        // 用于 GET/DELETE 的 query 参数
      body,        // 用于 POST/PUT/PATCH 的请求体
      headers = {},
      timeout = TIMEOUT,
      cancelToken  // 用于取消请求
    } = options;

    // 拼接 URL 和参数
    let fullUrl = `${BASE_URL}${url}`;
    if (data && (method === 'GET' || method === 'DELETE')) {
      const params = new URLSearchParams(data);
      fullUrl += `?${params}`;
    }

    // 构建请求配置
    const config = {
      method,
      headers: {
        'Content-Type': 'application/json',
        ...headers
      }
    };

    // POST/PUT/PATCH 才需要 body
    if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
      config.body = JSON.stringify(body);
    }

    // 超时控制
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), timeout);
    config.signal = controller.signal;

    try {
      const response = await fetch(fullUrl, config);
      clearTimeout(timer);

      // 统一响应处理
      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}));
        throw {
          status: response.status,
          message: errorData.message || `请求失败:${response.status}`,
          data: errorData
        };
      }

      // 处理 204 No Content
      if (response.status === 204) {
        return { code: 204, data: null };
      }

      return await response.json();
    } catch (error) {
      clearTimeout(timer);
      
      // 区分超时和网络错误
      if (error.name === 'AbortError') {
        throw { status: 0, message: '请求超时,请稍后重试' };
      }
      throw error;
    }
  }

  // 便捷方法
  get(url, data, options = {}) {
    return this.request({ url, method: 'GET', data, ...options });
  }

  post(url, body, options = {}) {
    return this.request({ url, method: 'POST', body, ...options });
  }

  put(url, body, options = {}) {
    return this.request({ url, method: 'PUT', body, ...options });
  }

  delete(url, data, options = {}) {
    return this.request({ url, method: 'DELETE', data, ...options });
  }
}

// 导出单例
export default new Request();

使用方式:

import request from './request';

// GET
const users = await request.get('/users', { page: 1, size: 10 });

// POST
const newUser = await request.post('/users', { name: '小明', age: 25 });

// PUT
const updatedUser = await request.put('/users/123', { name: '小明', age: 26 });

// DELETE
await request.delete('/users/123');

七、常见坑点总结

这里把实际开发中经常遇到的坑整理一下:

1. 跨域问题(CORS)

Access to fetch at 'https://api.example.com' from origin 'https://your-site.com' 
has been blocked by CORS policy

这是前端最常遇到的报错。解决方案在后端配置响应头:

Access-Control-Allow-Origin: https://your-site.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

前端这边可以用代理解决开发环境问题,Vite 配置:

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

2. POST 请求变成了 OPTIONS(预检请求)

当请求的 Content-Type 不是简单类型(比如用了 JSON),浏览器会先发一个 OPTIONS 请求做预检。如果后端没处理,你就会看到 403 错误。确保后端正确处理了 OPTIONS 请求。

3. PUT 和 PATCH 搞混

再次强调:PUT 是整体替换,PATCH 是部分更新。别用 PUT 只传一个字段,除非你确定后端支持。

4. DELETE 请求带 body

有些后端框架(比如 Spring Boot)默认不支持 DELETE 请求的 body。如果需要传,可以考虑用 POST 或者换用查询参数。

5. 重复提交

表单提交、列表删除等场景,用户可能连续点击多次。前端要做防抖/节流,或者提交后禁用按钮:

async function handleSubmit() {
  if (isSubmitting) return; // 防止重复提交
  setIsSubmitting(true);
  try {
    await request.post('/api/users', formData);
    showToast('提交成功');
  } finally {
    setIsSubmitting(false);
  }
}

八、一句话记住它们

  • GET → 查数据,参数放 URL,可缓存,不传敏感信息
  • POST → 创建数据,参数放 Body,最灵活,前端主力
  • PUT → 整体替换更新,参数放 Body,幂等
  • DELETE → 删除数据,ID 放 URL,务必加确认

把这四种方法记牢了,Ajax 请求这块你就基本过关了。后续进阶可以去看看 GraphQL、WebSocket 这些,但基础永远是最重要的。有任何问题随时来问,祝编码愉快!