嘿,我是Agnes。说到AJAX里的这四个“大佬”——GET、POST、PUT、DELETE,很多初学者甚至工作几年的开发同学,都容易把它们混为一谈,或者用得不规范。今天咱们不聊枯燥的RFC文档,我就用最通俗的大白话,结合真实的开发坑点,帮你把这事儿彻底理清楚。

先搞懂一个核心概念:HTTP动词的本质

你得先明白,GET、POST、PUT、DELETE不仅仅是AJAX的方法,它们是RESTful风格里对资源操作的四种基本动作。

你可以把服务器想象成一个巨大的图书馆,而每个资源(比如一篇文章、一个用户)就是书架上的一本书。

  • GET:我去借书看,看完还回去,不改动书的内容。
  • POST:我写了一新篇章,要添加到书里(创建新资源)。
  • PUT:我把书里错别字改正确,或者重写某一章(完整更新资源)。
  • DELETE:我把这本书从书架上扔进碎纸机(删除资源)。

搞懂这个比喻,后面的区别你就明白一半了。

GET请求:只读,别想太多

特点

  • 幂等性:查10次和查1次,结果完全一样。
  • 安全性:不会修改服务器数据。
  • 参数:通过URL问号(?)传递,比如 api/users?page=1。
  • 缓存:浏览器会对GET请求结果进行缓存,再次访问同URL时速度更快。
  • 长度限制:受限于浏览器和服务器的URL长度,一般几千字符以内。

典型场景

  • 搜索商品列表
  • 获取用户信息
  • 分页加载数据
  • 查询订单状态

代码示例

// 使用 fetch API
fetch('/api/products?category=books&sort=price_asc')
  .then(response => response.json())
  .then(data => {
    console.log('商品列表:', data);
    renderProductList(data);
  })
  .catch(error => {
    console.error('获取商品失败:', error);
  });

// 使用 jQuery(老项目常见)
$.ajax({
  url: '/api/products',
  method: 'GET',
  data: { category: 'books', sort: 'price_asc' },
  success: function(data) {
    console.log('商品列表:', data);
  }
});

常见错误排查

错误1:用GET提交敏感数据

// ❌ 错误!密码不能放在URL里
fetch('/api/login?username=admin&password=123456')

原因:URL会被记录在浏览器历史、服务器日志、代理缓存中,极度不安全。

错误2:GET请求参数编码问题

// ❌ 错误!中文参数未编码
fetch('/api/search?key=你好')

// ✅ 正确!使用 encodeURIComponent
const key = encodeURIComponent('你好');
fetch(`/api/search?key=${key}`)

错误3:把GET当作POST用(提交表单)

// ❌ 错误!表单提交修改数据用GET
// ✅ 正确!使用POST

POST请求:创建,别省力气

特点

  • 非幂等:多次POST可能创建多个资源(比如多次下单)。
  • 数据在请求体:参数放在body里,不暴露在URL中。
  • 无长度限制:理论上可以发送大量数据。
  • 不被缓存:每次都是新请求。

典型场景

  • 用户注册
  • 提交评论
  • 上传文件
  • 创建订单

代码示例

// 提交用户注册信息
const userData = {
  username: 'zhangsan',
  password: 'securePass123!',
  email: 'zhangsan@example.com'
};

fetch('/api/users/register', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json', // 重要!告诉服务器发的是JSON
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify(userData) // 对象转字符串
})
.then(response => {
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  return response.json();
})
.then(data => {
  console.log('注册成功:', data);
  // 跳转首页
  window.location.href = '/dashboard';
})
.catch(error => {
  console.error('注册失败:', error);
  // 显示错误提示
  showError(error.message);
});

常见错误排查

错误1:忘记设置Content-Type

// ❌ 错误!服务器收到的是undefined或乱码
fetch('/api/users', {
  method: 'POST',
  body: JSON.stringify({ name: 'test' })
  // 缺少 headers
})

// ✅ 正确!必须设置
fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'test' })
})

错误2:直接发送对象而不是字符串

// ❌ 错误!body必须是字符串
fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: { name: 'test' } // 直接传对象,会被自动转为"[object Object]"
})

// ✅ 正确!用JSON.stringify()
body: JSON.stringify({ name: 'test' })

错误3:使用表单数据却忘了编码

// ❌ 错误!中文乱码
const formData = new FormData();
formData.append('name', '张三');

// ✅ 正确!FormData会自动处理编码
const formData = new FormData();
formData.append('name', '张三');
formData.append('file', fileInput.files[0]); // 上传文件用FormData

fetch('/api/upload', {
  method: 'POST',
  body: formData // 不需要设置Content-Type,浏览器会自动设置
})

PUT请求:更新,覆盖式修改

特点

  • 幂等性:多次PUT同一资源,结果相同(最后一次生效)。
  • 完整替换:通常需要发送完整的资源数据,服务器会用新数据完全覆盖旧数据。
  • 数据在请求体:同POST。

典型场景

  • 修改用户资料(头像、昵称、邮箱)
  • 更新商品信息
  • 同步配置项

代码示例

// 更新用户资料
const userUpdateData = {
  id: 1001,
  username: 'zhangsan_new',
  email: 'zhangsan_new@example.com',
  avatar: 'https://example.com/avatar.jpg',
  bio: '这是一个新的个人简介'
};

fetch('/api/users/1001', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token123'
  },
  body: JSON.stringify(userUpdateData)
})
.then(response => {
  if (response.status === 200) {
    console.log('更新成功');
    // 刷新页面数据
    loadUserProfile();
  } else {
    console.error('更新失败');
  }
})
.catch(error => {
  console.error('网络错误:', error);
});

常见错误排查

错误1:用PUT做部分更新

// ❌ 错误!PUT应该发送完整数据,但这里只发了一个字段
fetch('/api/users/1001', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ nickname: '新昵称' }) // 只发一个字段
})
// 服务器可能会把其他字段清空!

错误2:混淆PUT和PATCH

// PUT:完整替换,发送所有字段
// PATCH:部分更新,只发送需要修改的字段

// ✅ 如果只改昵称,用PATCH
fetch('/api/users/1001', {
  method: 'PATCH', // 不是PUT
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ nickname: '新昵称' })
})

错误3:PUT请求幂等性理解错误 PUT应该是幂等的,但如果你前端不做去重,用户快速点击多次提交,可能会导致多次请求。建议前端做防抖或禁用按钮。

// ✅ 正确!添加防重复提交
let isSubmitting = false;

function updateUserProfile(data) {
  if (isSubmitting) return; // 防止重复提交
  isSubmitting = true;
  
  fetch('/api/users/1001', {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  })
  .then(res => res.json())
  .then(data => {
    console.log('更新成功');
  })
  .catch(error => {
    console.error('更新失败', error);
  })
  .finally(() => {
    isSubmitting = false; // 请求完成后恢复
  });
}

DELETE请求:删除,小心驶得万年船

特点

  • 幂等性:多次删除同一资源,结果相同(删除后就是不存在)。
  • 无请求体:通常不需要发送数据,资源ID在URL中指定。
  • 不可逆:删除后数据可能永久丢失。

典型场景

  • 删除评论
  • 删除用户账号
  • 删除商品
  • 删除订单

代码示例

// 删除文章
function deleteArticle(articleId) {
  // 二次确认
  if (!confirm('确定要删除这篇文章吗?删除后无法恢复!')) {
    return;
  }
  
  fetch(`/api/articles/${articleId}`, {
    method: 'DELETE',
    headers: {
      'Authorization': 'Bearer token123'
    }
  })
  .then(response => {
    if (response.status === 200 || response.status === 204) {
      console.log('删除成功');
      // 从页面移除该文章
      removeArticleFromDOM(articleId);
      // 显示成功提示
      showToast('文章已删除', 'success');
    } else {
      throw new Error('删除失败');
    }
  })
  .catch(error => {
    console.error('删除失败:', error);
    showToast('删除失败,请重试', 'error');
  });
}

// 从DOM中移除元素
function removeArticleFromDOM(articleId) {
  const articleEl = document.querySelector(`[data-article-id="${articleId}"]`);
  if (articleEl) {
    articleEl.remove();
  }
}

常见错误排查

错误1:用GET删除数据

// ❌ 严重错误!GET不应该有副作用
fetch('/api/articles/123', { method: 'GET' }) // 这会被缓存,也可能被预加载

// ✅ 正确!用DELETE
fetch('/api/articles/123', { method: 'DELETE' })

错误2:忘记二次确认

// ❌ 错误!没有确认就删除
fetch(`/api/articles/${id}`, { method: 'DELETE' })

// ✅ 正确!添加确认
if (!confirm('确定删除?')) return;
fetch(`/api/articles/${id}`, { method: 'DELETE' })

错误3:DELETE请求携带大量数据

// ❌ 错误!DELETE不需要请求体
fetch('/api/articles', {
  method: 'DELETE',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ ids: [1, 2, 3] }) // 不推荐
})

// ✅ 正确!通过URL或查询参数指定要删除的ID
fetch('/api/articles?ids=1,2,3', { method: 'DELETE' })
// 或者使用DELETE + body(部分服务器支持,但标准不推荐)

四大方法对比总结表

特性 GET POST PUT DELETE
用途 查询数据 创建资源 完整更新资源 删除资源
幂等性 ✅ 是 ❌ 否 ✅ 是 ✅ 是
安全性 ✅ 安全 ⚠️ 不安全 ⚠️ 不安全 ⚠️ 不安全
参数位置 URL Body Body URL/无
缓存 ✅ 可缓存 ❌ 不可缓存 ❌ 不可缓存 ❌ 不可缓存
数据长度 受限 无限制 无限制 无限制
典型状态码 200 201 200 204

真实开发中的常见坑点(必看)

坑1:前后端不一致的Content-Type

// 前端发送JSON
fetch('/api/data', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'test' })
})

// 后端错误配置(Java Spring Boot示例)
@PostMapping("/data")
public ResponseEntity<?> getData(@RequestBody User user) {
  // 如果Content-Type不对,这里会报415 Unsupported Media Type错误
}

// 正确做法:前后端约定好Content-Type
// 或者后端支持多种类型:@PostMapping consumes = {"application/json", "application/x-www-form-urlencoded"}

坑2:CORS预检请求失败

当你发送复杂请求(如自定义Header、PUT/DELETE方法)时,浏览器会先发一个OPTIONS预检请求。

// 前端
fetch('/api/protected', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'X-Custom-Header': 'value' // 自定义Header会触发预检
  },
  body: JSON.stringify({ data: 'test' })
})

// 后端需要正确处理OPTIONS请求
app.options('/api/protected', (req, res) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Custom-Header');
  res.sendStatus(200);
});

坑3:URL参数编码问题

// ❌ 错误!中文参数未编码
fetch('/api/search?q=JavaScript教程')

// ✅ 正确!使用encodeURIComponent
const query = encodeURIComponent('JavaScript教程');
fetch(`/api/search?q=${query}`)

// 或者使用URL对象
const url = new URL('/api/search', window.location.origin);
url.searchParams.set('q', 'JavaScript教程');
fetch(url.toString())

坑4:POST/PUT请求体为空或格式错误

// ❌ 错误!body为null
fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: null
})

// ❌ 错误!body未序列化
fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: { name: 'test' }
})

// ✅ 正确!
fetch('/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: 'test' })
})

如何选择合适的方法?(决策树)

你需要对资源做什么操作?
├── 只是查看/查询? → 用 GET
│   └── 需要传参数? → 加在URL查询字符串里
│
├── 创建新资源? → 用 POST
│   └── 数据放哪里? → 请求体(Body)
│
├── 更新现有资源?
│   ├── 需要完整替换? → 用 PUT
│   └── 只修改部分字段? → 用 PATCH
│
└── 删除资源? → 用 DELETE
    └── 如何指定资源? → URL路径参数(如 /api/users/123)

给小朋友的比喻记忆法

想象你在学校图书馆:

  • GET = 借书看(只是看,不改动)
  • POST = 写新书交给图书馆员(创建新内容)
  • PUT = 把整本书重写一遍(完全替换)
  • DELETE = 把书扔掉(删除)

记住:查用GET,增用POST,改用PUT/PATCH,删用DELETE。

实际项目中的最佳实践

  1. 统一错误处理
function apiRequest(url, options = {}) {
  return fetch(url, {
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${getAuthToken()}`
    },
    ...options
  })
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }
    return response.json();
  });
}
  1. 使用Axios简化操作

”`javascript import axios from ‘axios’;

// GET请求 axios.get(‘/api/users’, { params: { page: 1 } }) .then(res => console.log(res.data));

// POST请求 axios.post(‘/api