AJAX 教程

AJAX(Asynchronous JavaScript And XML,异步 JavaScript 与 XML)是一种在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。它让网页从"整页刷新"进化到"局部更新",是构建现代富交互 Web 应用的基础。

为什么需要 AJAX

传统网页流程是"提交表单 → 整页刷新 → 等待渲染",用户操作被一次次打断。AJAX 把请求放到后台异步执行,页面保持可交互,数据返回后只更新需要的部分,体验大幅提升。

对比项传统方式AJAX
页面刷新整页刷新局部更新
用户体验频繁等待、闪烁流畅、无中断
数据传输整页 HTML仅需要的数据
带宽消耗高低

核心组成

AJAX 并非单一技术,而是多种技术的组合:

  • XMLHttpRequest(XHR):浏览器内置的异步通信对象
  • DOM:动态更新页面内容
  • CSS:改变样式
  • JSON / XML:数据交换格式(如今 JSON 占绝对主流)
  • JavaScript:把一切串联起来的胶水语言

现代替代方案

如今更推荐使用 Fetch API(基于 Promise,语法更简洁)或 axios 等库。但 XHR 仍然是理解异步请求原理的最佳切入点,本教程会同时介绍两者。

💡 提示

AJAX 的 "X" 虽然代表 XML,但现在几乎都用 JSON 传输数据,名字只是一个历史遗留。

AJAX 简介

AJAX(Asynchronous JavaScript and XML)是一种创建快速动态网页的技术。它允许网页在不重新加载整个页面的情况下,与服务器交换数据并更新部分内容。

异步的含义

"异步"意味着请求发出后,JavaScript 代码不必等待服务器响应,可以继续执行其他任务;响应返回后再通过回调函数处理。这与传统同步请求(阻塞等待)形成鲜明对比。

// 同步请求:代码阻塞等待
// 异步请求:发出后立即返回,响应到达时再处理
console.log("请求发出");
// 不阻塞,继续执行
console.log("页面仍然流畅可交互");

工作原理

一次典型的 AJAX 交互包含以下步骤:

  1. 浏览器触发事件(如点击按钮)
  2. JavaScript 创建 XMLHttpRequest 对象
  3. XHR 向服务器发送请求(异步,不阻塞 UI)
  4. 服务器处理请求并返回数据(JSON / XML / 文本)
  5. 浏览器通过回调拿到数据,用 DOM 操作更新页面

示例场景

  • 搜索框输入时实时联想(如百度、Google)
  • 登录表单校验,不用跳页
  • 点赞/关注,局部数字更新
  • 无限滚动加载更多内容
⚠️ 注意

AJAX 受同源策略限制:默认只能请求同协议、同域名、同端口的资源。跨域请求需要服务器端配合 CORS 或 JSONP。

AJAX 实例

通过一个完整可运行的实例,直观感受 AJAX 的工作流程:点击按钮,向服务器请求一个文本文件,然后把它显示到页面上,全程无需刷新页面。

HTML 页面

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>AJAX 入门实例</title>
</head>
<body>
  <div id="demo">
    <h2>点击按钮修改这段内容</h2>
    <button type="button">请求数据</button>
  </div>

  <script>
  function loadDoc() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        document.getElementById("demo").innerHTML = this.responseText;
      }
    };
    xhr.open("GET", "demo.txt", true);
    xhr.send();
  }
  </script>
</body>
</html>

服务器文件 demo.txt

这是服务器返回的新内容!
AJAX 请求成功,页面无需刷新。</code>

代码逐行解读

代码作用
new XMLHttpRequest()创建异步请求对象
onreadystatechange监听请求状态变化
readyState == 4请求已完成
status == 200响应成功(HTTP 200)
open("GET", url, true)配置请求方式与地址,true=异步
send()发送请求
innerHTML用响应内容更新页面元素
💡 提示

把 open() 的第三个参数改为 false 即为同步模式,但现代浏览器已不推荐(会阻塞主线程,影响交互)。

XHR 创建对象

XMLHttpRequest(简称 XHR)是浏览器内置的、用于与服务器交互的核心对象,所有传统 AJAX 操作都从创建 XHR 对象开始。

标准创建方式

var xhr = new XMLHttpRequest();

兼容旧版 IE

IE6/IE7 使用 ActiveX 方式创建,早期代码常写成下面这样保证兼容:

var xhr;
if (window.XMLHttpRequest) {
  // 现代浏览器
  xhr = new XMLHttpRequest();
} else {
  // IE5/IE6 兼容
  xhr = new ActiveXObject("Microsoft.XMLHTTP");
}

如今 IE 早已淘汰,标准写法即可,但理解这段历史有助于阅读老代码。

XHR 对象的常用属性

属性说明
readyState请求状态(0-4),4 表示完成
statusHTTP 状态码(200 成功 / 404 未找到等)
statusText状态文本(如 "OK")
responseText以字符串形式返回的响应数据
responseXML以 XML 文档形式返回的响应(较少用)
onreadystatechange状态变化时触发的事件处理器

常用方法

方法说明
open(method, url, async)初始化请求(方法、地址、是否异步)
send(body)发送请求,GET 传 null,POST 传数据体
setRequestHeader(key, val)设置请求头(POST 常用)
abort()中止请求
💡 提示

现代开发中更推荐 fetch(),但 XHR 支持进度事件(onprogress)和请求中止等更细粒度控制,某些场景仍是首选。

XHR 请求

创建 XHR 对象后,通过 open() 配置请求、send() 发送请求。本节介绍 GET 与 POST 两种最常用的请求方式。

GET 请求

GET 适合请求数据,参数附加在 URL 查询串中:

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200) {
    console.log(xhr.responseText);
  }
};
xhr.open("GET", "https://api.example.com/users?id=1&type=admin", true);
xhr.send();

POST 请求

POST 适合提交数据,数据放在请求体中,并显式声明 Content-Type:

var xhr = new XMLHttpRequest();
xhr.open("POST", "https://api.example.com/users", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200) {
    console.log(xhr.responseText);
  }
};
xhr.send("name=张三&age=25");

GET 与 POST 对比

对比项GETPOST
数据位置URL 查询串请求体
数据大小有限(约 2KB)较大
安全性低(URL 可见/可缓存)较高
是否可缓存可被缓存默认不缓存
典型场景查询、获取列表登录、提交表单、上传

发送 JSON 数据(POST)

var xhr = new XMLHttpRequest();
xhr.open("POST", "/api/save", true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.onload = function() {
  if (xhr.status === 200) {
    console.log(JSON.parse(xhr.responseText));
  }
};
xhr.send(JSON.stringify({ name: "李四", age: 30 }));
⚠️ 注意

用 send() 发送对象时,必须先 JSON.stringify() 序列化,否则会变成字符串 [object Object]。

XHR 响应

请求发出后,服务器返回的数据会保存在 XHR 对象的 responseText 或 responseXML 属性中,供回调函数读取使用。

使用 responseText

当服务器返回普通文本或 JSON 字符串时,用 responseText:

var xhr = new XMLHttpRequest();
xhr.open("GET", "/api/notice", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200) {
    // 直接作为文本使用
    document.getElementById("notice").innerHTML = xhr.responseText;

    // 若是 JSON 字符串,先解析再使用
    var data = JSON.parse(xhr.responseText);
    console.log(data.title);
  }
};
xhr.send();

使用 responseXML

当服务器返回 XML 文档时(响应头 Content-Type: text/xml),用 responseXML 并通过 DOM 方法解析:

var xhr = new XMLHttpRequest();
xhr.open("GET", "/data/books.xml", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200) {
    var xmlDoc = xhr.responseXML;
    var titles = xmlDoc.getElementsByTagName("title");
    for (var i = 0; i < titles.length; i++) {
      console.log(titles[i].childNodes[0].nodeValue);
    }
  }
};
xhr.send();

统一处理响应

var xhr = new XMLHttpRequest();
xhr.open("GET", "/api/user", true);
xhr.onload = function() {          // onload:请求成功完成
  if (xhr.status === 200) {
    console.log("成功:", xhr.responseText);
  } else {
    console.error("HTTP 错误:", xhr.status);
  }
};
xhr.onerror = function() {          // 网络错误
  console.error("网络请求失败");
};
xhr.send();
💡 提示

现代写法推荐用 onload/onerror 代替繁琐的 onreadystatechange 状态判断,代码更清晰。

XHR readyState

readyState 属性表示 XMLHttpRequest 请求的当前状态,取值 0-4,每次变化都会触发 onreadystatechange 事件。

五种状态

值状态含义
0UNSENT已创建对象,但 open() 尚未调用
1OPENED已调用 open(),请求未发送
2HEADERS_RECEIVED已调用 send(),响应头已接收
3LOADING响应体下载中
4DONE请求完成,响应已就绪

监听状态变化

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  console.log("当前状态:", xhr.readyState);
  if (xhr.readyState == 4) {          // 请求完成
    if (xhr.status == 200) {          // 且 HTTP 成功
      console.log("数据:", xhr.responseText);
    } else {
      console.error("错误码:", xhr.status);
    }
  }
};
xhr.open("GET", "/api/data", true);
xhr.send();

readyState 与 status 的区别

  • readyState:请求过程的状态(传输阶段)
  • status:HTTP 响应状态码(服务器返回结果)

两者缺一不可:只有 readyState == 4(传输完成)且 status == 200(服务器成功处理)才算一次成功的请求。

简化的现代写法

var xhr = new XMLHttpRequest();
xhr.open("GET", "/api/data", true);
xhr.onload = function() {
  // onload 触发时 readyState 必为 4,只需判断 status
  if (xhr.status === 200) {
    console.log(xhr.responseText);
  }
};
xhr.send();
💡 提示

调试时在 onreadystatechange 里打印 readyState,可以直观看到请求的生命周期。

AJAX ASP/PHP

AJAX 最典型的应用之一,是与服务端脚本(如 PHP、ASP、JSP)配合:前端发请求,服务端读取数据库或执行逻辑后返回结果。本节以 PHP 为例。

请求 PHP 文件

前端通过 GET 携带参数请求 PHP,PHP 处理后输出文本/JSON:

<!DOCTYPE html>
<html>
<body>
  <form>
    姓名:<input type="text" id="name">
    <button type="button">查询</button>
  </form>
  <p id="result"></p>

  <script>
  function send() {
    var name = document.getElementById("name").value;
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "hello.php?name=" + encodeURIComponent(name), true);
    xhr.onload = function() {
      if (xhr.status === 200) {
        document.getElementById("result").innerHTML = xhr.responseText;
      }
    };
    xhr.send();
  }
  </script>
</body>
</html>

服务端 hello.php

<?php
// 接收参数并做基本校验
$name = isset($_GET['name']) ? htmlspecialchars($_GET['name']) : '游客';

// 模拟查询数据库
$users = ['张三' => '北京', '李四' => '上海'];
if (isset($users[$name])) {
    echo "{$name} 所在城市:" . $users[$name];
} else {
    echo "未找到用户:{$name}";
}
?>

返回 JSON(推荐)

服务端直接输出 JSON,前端解析后灵活渲染:

<?php
header('Content-Type: application/json; charset=utf-8');
$name = $_GET['name'] ?? '';

// 模拟数据
$data = ['status' => 'ok', 'city' => '北京', 'age' => 25];

echo json_encode($data, JSON_UNESCAPED_UNICODE);
?>
// 前端解析 JSON
xhr.onload = function() {
  if (xhr.status === 200) {
    var data = JSON.parse(xhr.responseText);
    console.log(data.city);   // 北京
  }
};
⚠️ 注意

服务端输出必须只有数据,不能混入 HTML、空格或 BOM 头,否则前端 JSON.parse 会报错。

AJAX 数据库

AJAX 与数据库结合是动态网站的核心模式:前端异步请求 → 服务端查询数据库 → 返回结果 → 局部刷新页面。

完整流程

  1. 用户操作触发 AJAX 请求
  2. 服务端脚本连接数据库执行 SQL 查询
  3. 查询结果转为 JSON 返回前端
  4. 前端渲染数据,更新页面局部

前端请求示例

<select id="city">
  <option value="beijing">北京</option>
  <option value="shanghai">上海</option>
</select>
<div id="userList"></div>

<script>
function loadUsers() {
  var city = document.getElementById("city").value;
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "get_users.php?city=" + city, true);
  xhr.onload = function() {
    if (xhr.status === 200) {
      var users = JSON.parse(xhr.responseText);
      renderList(users);
    }
  };
  xhr.send();
}

function renderList(users) {
  var html = "<ul>";
  users.forEach(function(u) {
    html += "<li>" + u.name + " - " + u.phone + "</li>";
  });
  html += "</ul>";
  document.getElementById("userList").innerHTML = html;
}
</script>

服务端 PHP + MySQL

<?php
header('Content-Type: application/json; charset=utf-8');

$city = $_GET['city'] ?? '';
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4',
               'root', 'password');

// 使用预处理语句防止 SQL 注入
$stmt = $pdo->prepare('SELECT name, phone FROM users WHERE city = ?');
$stmt->execute([$city]);
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);

echo json_encode($users, JSON_UNESCAPED_UNICODE);
?>

安全要点

风险防范措施
SQL 注入必须使用预处理语句 / 参数化查询
敏感数据泄露接口只返回必要的字段
接口被恶意调用添加身份校验(Token / Session)
数据库压力加缓存、限制查询频率
💡 提示

不要把数据库账号密码写死在页面 JS 里——数据库只能由服务端连接,前端永远通过 API 间接访问。

AJAX XML

除了 JSON,AJAX 也可以传输 XML 数据。虽然 XML 如今不如 JSON 流行,但在某些遗留系统、SOAP 服务、配置文件中仍然常见。

获取 XML 并解析

<script>
var xhr = new XMLHttpRequest();
xhr.open("GET", "books.xml", true);
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200) {
    // responseXML 返回 XML 文档对象
    var doc = xhr.responseXML;
    var titles = doc.getElementsByTagName("title");
    var output = "";
    for (var i = 0; i < titles.length; i++) {
      output += titles[i].childNodes[0].nodeValue + "<br>";
    }
    document.getElementById("result").innerHTML = output;
  }
};
xhr.send();
</script>

示例 XML 文件 books.xml

<?xml version="1.0" encoding="UTF-8"?>
<books>
  <book>
    <title>JavaScript 高级程序设计</title>
    <author>Nicholas Zakas</author>
    <price>99</price>
  </book>
  <book>
    <title>CSS 权威指南</title>
    <author>Eric Meyer</author>
    <price>129</price>
  </book>
</books>

遍历 XML 节点

var books = doc.getElementsByTagName("book");
for (var i = 0; i < books.length; i++) {
  var title = books[i].getElementsByTagName("title")[0].childNodes[0].nodeValue;
  var price = books[i].getElementsByTagName("price")[0].childNodes[0].nodeValue;
  console.log(title + ":" + price + " 元");
}

XML 与 JSON 对比

对比项XMLJSON
解析复杂度需 DOM/XPathJSON.parse() 一步完成
体积较大(标签冗余)紧凑
可读性标签语义化强结构直观
类型支持纯文本原生支持数字/布尔/数组
适用场景文档型数据、SOAPWeb API 主流选择
💡 提示

若服务端返回的是 XML,需确保响应头 Content-Type: text/xml,否则 responseXML 会为 null。

AJAX JSON 实例

现代 AJAX 应用几乎都以 JSON 作为数据格式。本节演示一个完整实例:前端请求 JSON 接口,解析后渲染成 HTML 列表。

HTML 结构

<!DOCTYPE html>
<html>
<body>
  <h2>用户列表(AJAX + JSON)</h2>
  <button>加载数据</button>
  <table border="1" id="userTable">
    <tr><th>ID</th><th>姓名</th><th>城市</th></tr>
  </table>

  <script>
  function loadUsers() {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "users.json", true);
    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4 && xhr.status == 200) {
        // 1. 解析 JSON 字符串为对象数组
        var users = JSON.parse(xhr.responseText);
        // 2. 遍历渲染
        var table = document.getElementById("userTable");
        users.forEach(function(user) {
          var row = table.insertRow(-1);
          row.insertCell(0).innerHTML = user.id;
          row.insertCell(1).innerHTML = user.name;
          row.insertCell(2).innerHTML = user.city;
        });
      }
    };
    xhr.send();
  }
  </script>
</body>
</html>

数据文件 users.json

[
  { "id": 1, "name": "张三", "city": "北京" },
  { "id": 2, "name": "李四", "city": "上海" },
  { "id": 3, "name": "王五", "city": "广州" }
]

用现代 fetch 实现同样功能

async function loadUsers() {
  const res = await fetch("users.json");
  const users = await res.json();
  const table = document.getElementById("userTable");
  users.forEach(function(user) {
    const row = table.insertRow(-1);
    row.insertCell(0).innerHTML = user.id;
    row.insertCell(1).innerHTML = user.name;
    row.insertCell(2).innerHTML = user.city;
  });
}

要点回顾

  • JSON.parse():把 JSON 字符串转成 JavaScript 对象/数组
  • JSON.stringify():把 JavaScript 对象转成 JSON 字符串
  • 渲染逻辑与数据获取逻辑分离,代码更易维护
⚠️ 注意

JSON.parse 遇到格式错误的 JSON 会抛出异常,生产环境应包在 try/catch 中。

Fetch API

Fetch API 是现代浏览器提供的原生异步请求接口,基于 Promise,语法比 XHR 更简洁、可读性更强,是当前推荐的主流方案。

基本用法

fetch("https://api.example.com/users")
  .then(function(response) {
    if (!response.ok) {
      throw new Error("请求失败:" + response.status);
    }
    return response.json();       // 解析 JSON
  })
  .then(function(data) {
    console.log(data);            // 拿到数据
  })
  .catch(function(error) {
    console.error("出错:", error);
  });

与 XHR 对比

对比项XHRFetch
语法回调 + 状态判断Promise 链式/async-await
代码量繁琐简洁
流式读取支持(onprogress)支持(ReadableStream)
请求中止abort()AbortController
超时控制timeout 属性需配合 AbortController
兼容性极老浏览器现代浏览器(IE 不支持)

async/await 写法(推荐)

async function getUsers() {
  try {
    const response = await fetch("/api/users");
    if (!response.ok) throw new Error("HTTP " + response.status);
    const users = await response.json();
    return users;
  } catch (err) {
    console.error(err);
    return [];
  }
}

getUsers().then(users => console.log(users));

POST 请求

async function createUser() {
  const res = await fetch("/api/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name: "赵六", age: 28 })
  });
  const data = await res.json();
  console.log(data);
}
💡 提示

注意 fetch 只有在网络错误时才 reject;HTTP 4xx/5xx 状态不会抛异常,必须自己检查 response.ok 或 response.status。

fetch API

本节补充 Fetch API 的进阶能力:请求配置、超时控制、取消请求、表单提交与文件上传等实战技巧。

完整请求配置

const res = await fetch(url, {
  method: "GET",                        // 请求方法
  headers: {                            // 请求头
    "Content-Type": "application/json",
    "Authorization": "Bearer " + token
  },
  body: null,                           // 请求体(GET 不传)
  credentials: "same-origin",           // 携带 Cookie:"include" 跨域也带
  cache: "no-cache",                    // 缓存策略
  redirect: "follow"                    // 重定向策略
});

超时与取消(AbortController)

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

try {
  const res = await fetch("/api/slow", { signal: controller.signal });
  const data = await res.json();
  console.log(data);
} catch (err) {
  if (err.name === "AbortError") {
    console.error("请求超时已取消");
  } else {
    console.error("其他错误", err);
  }
} finally {
  clearTimeout(timeoutId);
}

提交表单数据

const form = document.getElementById("myForm");
const formData = new FormData(form);

const res = await fetch("/api/submit", {
  method: "POST",
  body: formData          // 无需手动设置 Content-Type,浏览器自动处理
});
console.log(await res.json());

上传文件

const fileInput = document.getElementById("file");
const file = fileInput.files[0];

const formData = new FormData();
formData.append("file", file);

const res = await fetch("/api/upload", {
  method: "POST",
  body: formData
});

if (res.ok) {
  console.log("上传成功");
}

并发请求

// 并行发起多个请求
const [users, posts] = await Promise.all([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/posts").then(r => r.json())
]);
console.log(users, posts);
⚠️ 注意

跨域请求(CORS)时,credentials: "include" 要求服务器响应头必须包含 Access-Control-Allow-Credentials: true,且不能使用通配符 *。

第 1 / 13 节