Ajax 基础技术深度解析:XHR 从入门到跨域

XMLHttpRequest 五步流程GET/POST 请求体FormData 上传JSON 响应超时与进度 ,并衔接 CORS / JSONP 跨域。配套 server.js 可本地跑通全部 HTML 案例。

参考:MDN XMLHttpRequest | MDN CORS

目录

  • 零、导读与学习价值
    • [0.5 本章在学什么(知识地图)](#0.5 本章在学什么(知识地图))
    • [0.1 案例覆盖清单](#0.1 案例覆盖清单)
    • [0.2.1 与前后续章节衔接](#0.2.1 与前后续章节衔接)
    • [0.2 核心名词速查](#0.2 核心名词速查)
    • [0.3 本地运行配套服务](#0.3 本地运行配套服务)
    • [0.4 建议练习路线](#0.4 建议练习路线)
  • [1. Ajax 技术概述](#1. Ajax 技术概述)
    • [1.0 名词解释](#1.0 名词解释)
    • [1.1 什么是 Ajax?](#1.1 什么是 Ajax?)
    • [1.2 Ajax 技术优势](#1.2 Ajax 技术优势)
    • [1.3 Ajax 应用场景](#1.3 Ajax 应用场景)
    • [1.4 配套可运行示例(概念演示)](#1.4 配套可运行示例(概念演示))
  • [2. XMLHttpRequest 对象详解](#2. XMLHttpRequest 对象详解)
    • [2.1 XHR 对象基础](#2.1 XHR 对象基础)
    • [2.2 创建 XHR 对象](#2.2 创建 XHR 对象)
    • [2.3 XHR 对象的重要属性](#2.3 XHR 对象的重要属性)
    • [2.4 配套可运行示例(状态机观察)](#2.4 配套可运行示例(状态机观察))
  • [3. Ajax 基本使用流程](#3. Ajax 基本使用流程)
    • [3.1 标准Ajax流程](#3.1 标准Ajax流程)
    • [3.2 事件监听的完整实现](#3.2 事件监听的完整实现)
    • [3.3 配套可运行示例(01-ajax基本流程)](#3.3 配套可运行示例(01-ajax基本流程))
    • [3.4 配套可运行示例(onload / onerror)](#3.4 配套可运行示例(onload / onerror))
  • [4. 发送请求携带数据](#4. 发送请求携带数据)
    • [4.1 GET 请求携带数据](#4.1 GET 请求携带数据)
    • [4.2 POST 请求携带数据](#4.2 POST 请求携带数据)
    • [4.3 FormData 对象使用](#4.3 FormData 对象使用)
    • [4.4 请求头详解](#4.4 请求头详解)
    • [4.5 配套可运行示例(02 传参 + 03 FormData)](#4.5 配套可运行示例(02 传参 + 03 FormData))
  • [5. 响应处理与数据解析](#5. 响应处理与数据解析)
    • [5.1.1 配套可运行示例(04 解析响应报文)](#5.1.1 配套可运行示例(04 解析响应报文))
    • [5.1 响应报文结构](#5.1 响应报文结构)
    • [5.2 处理 JSON 响应](#5.2 处理 JSON 响应)
    • [5.2.1 配套示例:05-响应json数据.html](#5.2.1 配套示例:05-响应json数据.html)
    • [5.3 处理其他格式响应](#5.3 处理其他格式响应)
    • [5.4 响应状态码处理](#5.4 响应状态码处理)
    • [5.5 配套可运行示例(05 JSON 列表)](#5.5 配套可运行示例(05 JSON 列表))
  • [6. 进度事件与超时控制](#6. 进度事件与超时控制)
    • [6.1 进度事件详解](#6.1 进度事件详解)
    • [6.1.1 配套示例:07-进度事件.html](#6.1.1 配套示例:07-进度事件.html)
    • [6.2 响应超时控制](#6.2 响应超时控制)
    • [6.2.1 配套示例:06-响应超时.html](#6.2.1 配套示例:06-响应超时.html)
    • [6.3 完整的进度监控示例](#6.3 完整的进度监控示例)
    • [6.4 配套可运行示例(06 超时 + 07 进度)](#6.4 配套可运行示例(06 超时 + 07 进度))
  • [7. 同步与异步请求](#7. 同步与异步请求)
    • [7.1 异步请求详解](#7.1 异步请求详解)
    • [7.2 同步请求详解](#7.2 同步请求详解)
    • [7.3 同步与异步对比](#7.3 同步与异步对比)
    • [7.2.1 配套示例:08-同步和异步.html](#7.2.1 配套示例:08-同步和异步.html)
    • [7.4 使用建议](#7.4 使用建议)
    • [7.5 配套可运行示例(08 同步/异步)](#7.5 配套可运行示例(08 同步/异步))
  • [8. Ajax 基础实战案例](#8. Ajax 基础实战案例)
    • [8.1 用户名验证示例](#8.1 用户名验证示例)
    • [8.2 级联选择器示例](#8.2 级联选择器示例)
    • [8.3 搜索建议示例](#8.3 搜索建议示例)
  • [9. 跨域:同源策略、CORS 与 JSONP](#9. 跨域:同源策略、CORS 与 JSONP)
    • [9.0 配套跨域页面(02-跨域)](#9.0 配套跨域页面(02-跨域))
    • [9.1 同源策略](#9.1 同源策略)
    • [9.2 CORS 跨域资源共享](#9.2 CORS 跨域资源共享)
    • [9.3 JSONP 实现思路](#9.3 JSONP 实现思路)
  • 总结

零、导读与学习价值

0.5 本章在学什么(知识地图)

#mermaid-svg-TVqh1TQ7gj1SRwsB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TVqh1TQ7gj1SRwsB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TVqh1TQ7gj1SRwsB .error-icon{fill:#552222;}#mermaid-svg-TVqh1TQ7gj1SRwsB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TVqh1TQ7gj1SRwsB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TVqh1TQ7gj1SRwsB .marker.cross{stroke:#333333;}#mermaid-svg-TVqh1TQ7gj1SRwsB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TVqh1TQ7gj1SRwsB p{margin:0;}#mermaid-svg-TVqh1TQ7gj1SRwsB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TVqh1TQ7gj1SRwsB .cluster-label text{fill:#333;}#mermaid-svg-TVqh1TQ7gj1SRwsB .cluster-label span{color:#333;}#mermaid-svg-TVqh1TQ7gj1SRwsB .cluster-label span p{background-color:transparent;}#mermaid-svg-TVqh1TQ7gj1SRwsB .label text,#mermaid-svg-TVqh1TQ7gj1SRwsB span{fill:#333;color:#333;}#mermaid-svg-TVqh1TQ7gj1SRwsB .node rect,#mermaid-svg-TVqh1TQ7gj1SRwsB .node circle,#mermaid-svg-TVqh1TQ7gj1SRwsB .node ellipse,#mermaid-svg-TVqh1TQ7gj1SRwsB .node polygon,#mermaid-svg-TVqh1TQ7gj1SRwsB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TVqh1TQ7gj1SRwsB .rough-node .label text,#mermaid-svg-TVqh1TQ7gj1SRwsB .node .label text,#mermaid-svg-TVqh1TQ7gj1SRwsB .image-shape .label,#mermaid-svg-TVqh1TQ7gj1SRwsB .icon-shape .label{text-anchor:middle;}#mermaid-svg-TVqh1TQ7gj1SRwsB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TVqh1TQ7gj1SRwsB .rough-node .label,#mermaid-svg-TVqh1TQ7gj1SRwsB .node .label,#mermaid-svg-TVqh1TQ7gj1SRwsB .image-shape .label,#mermaid-svg-TVqh1TQ7gj1SRwsB .icon-shape .label{text-align:center;}#mermaid-svg-TVqh1TQ7gj1SRwsB .node.clickable{cursor:pointer;}#mermaid-svg-TVqh1TQ7gj1SRwsB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TVqh1TQ7gj1SRwsB .arrowheadPath{fill:#333333;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TVqh1TQ7gj1SRwsB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TVqh1TQ7gj1SRwsB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TVqh1TQ7gj1SRwsB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TVqh1TQ7gj1SRwsB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TVqh1TQ7gj1SRwsB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TVqh1TQ7gj1SRwsB .cluster text{fill:#333;}#mermaid-svg-TVqh1TQ7gj1SRwsB .cluster span{color:#333;}#mermaid-svg-TVqh1TQ7gj1SRwsB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TVqh1TQ7gj1SRwsB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TVqh1TQ7gj1SRwsB rect.text{fill:none;stroke-width:0;}#mermaid-svg-TVqh1TQ7gj1SRwsB .icon-shape,#mermaid-svg-TVqh1TQ7gj1SRwsB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TVqh1TQ7gj1SRwsB .icon-shape p,#mermaid-svg-TVqh1TQ7gj1SRwsB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TVqh1TQ7gj1SRwsB .icon-shape .label rect,#mermaid-svg-TVqh1TQ7gj1SRwsB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TVqh1TQ7gj1SRwsB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TVqh1TQ7gj1SRwsB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TVqh1TQ7gj1SRwsB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} XHR 五步
传参 GET/POST/FormData
响应 JSON 与报文
超时与进度
同步异步
CORS / JSONP

【代码注释】

  • 左到右对应配套 01~0802-跨域 练习顺序(§0.4)。
  • 全部基于 HTTP,为后续 Promise、Fetch 打底。

0.1 案例覆盖清单

页面 / 服务 知识点 本文章节
01-ajax基本流程.html XHR 创建、opensendonload §3
02-发送请求携带数据.html GET 查询串、POST urlencoded、PUT §4
03-FormData.html 文件上传、multipart/form-data §4.3
04-解析响应报文.html statusgetResponseHeaderresponseText §5.1
05-响应json数据.html JSON.parse / responseType:'json' §5.2
06-响应超时.html xhr.timeoutontimeout §6.2
07-进度事件.html onprogressxhr.upload §6.1
08-同步和异步.html open 第三参数 true/false §7
01-CORS.html(02-跨域) XHR 请求 /getdata01,依赖 Access-Control-Allow-Origin §9.1、§9.2
02-jsonp解决跨域.html <script src=".../getdata02?cb=parseData"> 回调渲染列表 §9.3
server.js(02-跨域) /page01 /page02/getdata01 /getdata02 §0.3、§9
server.js(01-原生Ajax) /page0X 与 API 路由 §0.3
package.json(01 / 02) expressbody-parsermulter(01)等依赖 §0.3

0.2.1 与前后续章节衔接

  • 前置:HTTP、Express 静态服务、表单提交。
  • 本章:原生 XHR + CORS / JSONP。
  • 后续:ajax 封装 → Promise → Fetch/Axios。

0.2 核心名词速查

术语 一句话解释
Ajax 异步 JS 与数据交换,不刷新整页更新局部
XHR XMLHttpRequest,浏览器发 HTTP 请求的对象
请求体 send() 的参数:字符串、FormData 等
同源策略 协议+域名+端口一致才允许读响应
CORS 服务端响应头放行跨域
JSONP 利用 <script> 跨域,仅 GET

0.3 本地运行配套服务

01-原生Ajax 目录:

bash 复制代码
npm install
node server.js

【代码注释】

  • 默认监听 8080 ;通过 /page01/page08 分别打开 01~08 各 HTML(sendFile 同源访问,HTML 内 URL 多为相对路径如 /getData)。
  • 01-原生Ajax 路由与页面对照见下表;须先 node server.js,否则 onerror 或控制台报网络错误。
  • 02-跨域 目录单独 node server.js(同样 8080),与 01 不要同时启动,避免端口冲突。

01-原生Ajax server.js 路由对照:

路由 打开的 HTML / 接口作用
GET /page01 01-ajax基本流程.html
GET /getData 基本流程接口,返回文本
GET /page02 02-发送请求携带数据.html
GET/POST/PUT /addData GET 查询串、POST/PUT 请求体
GET /page03 03-FormData.html
POST /upload multer 接收文件上传
GET /page04 04-解析响应报文.html(页面内请求 /getData 读报文)
GET /page05 05-响应json数据.html
GET /getUsers 返回 application/json 用户列表
GET /page06 06-响应超时.html
GET /getInfo 随机延迟响应,配合 xhr.timeout
GET /page07 07-进度事件.html
GET /getMoreData 大段文本,配合 onprogress
GET /page08 08-同步和异步.html

跨域练习目录:

bash 复制代码
cd 02-跨域
npm install
node server.js

【代码注释】(跨域服务启动)

  • 01-原生Ajax 共用 8080 ,须先结束上一个 node server.js 再启动本目录服务。
  • npm install 安装 02-跨域/package.json 中的 express
  • 启动后访问 /page01/page02 打开 CORS / JSONP 配套页。

02-跨域 server.js 路由对照:

路由 打开的 HTML / 接口作用
GET /page01 01-CORS.html
GET /getdata01 设置 Access-Control-Allow-Origin,供 CORS 演示
GET /page02 02-jsonp解决跨域.html
GET /getdata02?cb=parseData 返回 parseData([...]) 形式 JS,JSONP 回调

【代码注释】

  • CORS 页请求 完整 URL http://127.0.0.1:8080/getdata01;若用 file:// 直接打开 HTML 也会跨域,更直观看到无 CORS 头时的失败。
  • JSONP 页回调函数名为 parseData ,与 getdata02?cb=parseData 一致;后端从 req.query.cb 拼响应体。
  • 访问 http://127.0.0.1:8080/page01/page02 即可对照 01-CORS.html02-jsonp解决跨域.html 源码联调。

依赖安装(package.json):

目录 主要依赖 用途
01-原生Ajax expressbody-parsermulter 静态页路由、解析 POST/PUT 体、03-FormData 上传
02-跨域 express CORS / JSONP 演示路由

两个目录均需 npm install 后再 node server.js

0.4 建议练习路线

顺序 配套 端口 验证点
0101-ajax基本流程.html 8080 /page01/getData、五步流程
010204 8080 传参、FormData、响应报文
0105-响应json数据.html 8080 /getUsers + responseType:'json'
0106 / 07 8080 /getInfo 超时、/getMoreData 进度
0108-同步和异步.html 8080 open(..., false) 与控制台打印顺序
02-跨域 → CORS + JSONP 8080 先停 01 服务,再启 02;/page01/page02

Ajax 进阶篇 衔接:进阶目录的 01-Ajax演示 复习进度/JSON,02-跨域 中 JSONP 页可能命名为 02-jsonp.html,机制相同。


1. Ajax 技术概述

1.0 名词解释

  • 异步(Asynchronous):发起请求后 JS 继续执行,结果通过回调处理。
  • XHR:浏览器内置的 HTTP 客户端对象,Ajax 时代核心 API。
  • DOM 更新 :用返回数据改页面节点,而非整页 location.reload

1.1 什么是 Ajax?

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

名词解析:

  • Ajax:异步 JavaScript 和 XML,一种 Web 开发技术
  • XMLHttpRequest:Ajax 的核心技术对象,用于发送 HTTP 请求
  • 异步请求:不阻塞页面执行的请求方式
  • 回调函数:在特定条件下执行的函数
  • DOM 操作:通过 JavaScript 操作页面元素

1.2 Ajax 技术优势

传统页面交互 vs Ajax 交互:
#mermaid-svg-HzrCpnRcBUQUXdtY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HzrCpnRcBUQUXdtY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HzrCpnRcBUQUXdtY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HzrCpnRcBUQUXdtY .error-icon{fill:#552222;}#mermaid-svg-HzrCpnRcBUQUXdtY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HzrCpnRcBUQUXdtY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HzrCpnRcBUQUXdtY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HzrCpnRcBUQUXdtY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HzrCpnRcBUQUXdtY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HzrCpnRcBUQUXdtY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HzrCpnRcBUQUXdtY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HzrCpnRcBUQUXdtY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HzrCpnRcBUQUXdtY .marker.cross{stroke:#333333;}#mermaid-svg-HzrCpnRcBUQUXdtY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HzrCpnRcBUQUXdtY p{margin:0;}#mermaid-svg-HzrCpnRcBUQUXdtY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HzrCpnRcBUQUXdtY .cluster-label text{fill:#333;}#mermaid-svg-HzrCpnRcBUQUXdtY .cluster-label span{color:#333;}#mermaid-svg-HzrCpnRcBUQUXdtY .cluster-label span p{background-color:transparent;}#mermaid-svg-HzrCpnRcBUQUXdtY .label text,#mermaid-svg-HzrCpnRcBUQUXdtY span{fill:#333;color:#333;}#mermaid-svg-HzrCpnRcBUQUXdtY .node rect,#mermaid-svg-HzrCpnRcBUQUXdtY .node circle,#mermaid-svg-HzrCpnRcBUQUXdtY .node ellipse,#mermaid-svg-HzrCpnRcBUQUXdtY .node polygon,#mermaid-svg-HzrCpnRcBUQUXdtY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HzrCpnRcBUQUXdtY .rough-node .label text,#mermaid-svg-HzrCpnRcBUQUXdtY .node .label text,#mermaid-svg-HzrCpnRcBUQUXdtY .image-shape .label,#mermaid-svg-HzrCpnRcBUQUXdtY .icon-shape .label{text-anchor:middle;}#mermaid-svg-HzrCpnRcBUQUXdtY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HzrCpnRcBUQUXdtY .rough-node .label,#mermaid-svg-HzrCpnRcBUQUXdtY .node .label,#mermaid-svg-HzrCpnRcBUQUXdtY .image-shape .label,#mermaid-svg-HzrCpnRcBUQUXdtY .icon-shape .label{text-align:center;}#mermaid-svg-HzrCpnRcBUQUXdtY .node.clickable{cursor:pointer;}#mermaid-svg-HzrCpnRcBUQUXdtY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HzrCpnRcBUQUXdtY .arrowheadPath{fill:#333333;}#mermaid-svg-HzrCpnRcBUQUXdtY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HzrCpnRcBUQUXdtY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HzrCpnRcBUQUXdtY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HzrCpnRcBUQUXdtY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HzrCpnRcBUQUXdtY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HzrCpnRcBUQUXdtY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HzrCpnRcBUQUXdtY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HzrCpnRcBUQUXdtY .cluster text{fill:#333;}#mermaid-svg-HzrCpnRcBUQUXdtY .cluster span{color:#333;}#mermaid-svg-HzrCpnRcBUQUXdtY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HzrCpnRcBUQUXdtY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HzrCpnRcBUQUXdtY rect.text{fill:none;stroke-width:0;}#mermaid-svg-HzrCpnRcBUQUXdtY .icon-shape,#mermaid-svg-HzrCpnRcBUQUXdtY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HzrCpnRcBUQUXdtY .icon-shape p,#mermaid-svg-HzrCpnRcBUQUXdtY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HzrCpnRcBUQUXdtY .icon-shape .label rect,#mermaid-svg-HzrCpnRcBUQUXdtY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HzrCpnRcBUQUXdtY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HzrCpnRcBUQUXdtY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HzrCpnRcBUQUXdtY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 传统方式
用户操作
发送HTTP请求
服务器处理
返回完整HTML页面
浏览器重新渲染
Ajax方式
用户操作
发送Ajax请求
服务器处理
返回数据
JavaScript更新DOM

【代码注释】

  • 图示对比「整页刷新」与「Ajax 局部更新」两条链路,理解用户体验差异。
  • 传统方式:服务器返回完整 HTML,浏览器重新解析渲染,白屏或闪烁明显。
  • Ajax 方式:只返回数据(JSON/文本),由 JS 更新 DOM 节点,流量更小、交互更顺滑。
  • 搜索建议、分页加载、表单无刷新提交均属右侧模式。

Ajax 的优势:

  • 用户体验更好:无需刷新整个页面
  • 减少服务器负载:只传输需要的数据
  • 响应速度更快:减少网络传输量
  • 界面更加流畅:实现局部更新效果

1.3 Ajax 应用场景

经典应用场景:

应用场景 技术实现 用户体验
表单验证 实时验证用户名、邮箱 即时反馈,减少等待
自动完成 搜索框输入建议 智能提示,提升效率
数据加载 分页加载、无限滚动 按需加载,节省流量
实时更新 聊天室、股票行情 信息实时同步
地图应用 拖拽地图加载区域 流畅的地图浏览体验

1.4 配套可运行示例(概念演示)

01-原生Ajax 启动服务后,下面页面请求 /getData 并在 DOM 中展示结果,体现「无刷新更新」:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Ajax 概念演示</title></head>
<body>
  <button id="btn">加载数据</button>
  <p id="out">(未请求)</p>
  <script>
    document.getElementById('btn').onclick = () => {
      const xhr = new XMLHttpRequest();
      xhr.onload = () => {
        if (xhr.status === 200) out.textContent = xhr.responseText;
        else out.textContent = '失败:' + xhr.status;
      };
      xhr.open('GET', '/getData');
      xhr.send();
    };
    const out = document.getElementById('out');
  </script>
</body>
</html>

【代码注释】

  • 01-ajax基本流程.html 同源思路;访问 /page01 可对照课堂完整版。
  • 仅更新 #out 节点,不刷新整页,即 Ajax 核心价值。

【实战要点】

  • 经典应用场景:微博刷新、购物车数量角标、表单失焦校验------均为「局部更新」。
  • 常见坑 :只判断 onload 不判断 xhr.status,404/500 响应也会进 onload
  • 性能与最佳实践:能用 GET 缓存的接口用 GET;POST 注意防重复提交。

【面试考点】

Q1:Ajax 和传统表单提交有什么区别?

A:传统提交整页刷新;Ajax 用 XHR/fetch 异步拿数据,JS 改 DOM。追问优缺点:体验好、流量小,但要自己处理错误态与 SEO(首屏)。

【本章小结】

概念 说明
Ajax 异步数据交换 + DOM 局部更新
价值 无刷新、体验好、减少整页流量

2. XMLHttpRequest 对象详解

2.1 XHR 对象基础

XMLHttpRequest 对象是 Ajax 技术的核心,提供了在网页中发送 HTTP 请求的能力。

XHR 对象的状态机:

javascript 复制代码
const XHR_STATES = {
    0: 'UNSET',           // 对象已创建或已被 abort() 重置
    1: 'OPENED',          // open() 方法已被调用
    2: 'HEADERS_RECEIVED', // send() 方法被调用,响应头可获取
    3: 'LOADING',         // 响应体下载中,responseText 包含部分数据
    4: 'DONE'             // 所有响应数据接收完毕
};

console.log('XHR 状态:', XHR_STATES);

【代码注释】

  • readyState 从 0→4 变化;4(DONE)且 status===200 才表示成功拿到响应。
  • 现代开发更常用 onload 代替仅依赖 onreadystatechange,逻辑更清晰。
  • 调试时在 onreadystatechange 打印状态,可观察请求各阶段。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

2.2 创建 XHR 对象

创建 XHR 对象的多种方式:

javascript 复制代码
// 方式一:标准方式(推荐)
const xhr = new XMLHttpRequest();

// 方式二:兼容旧浏览器(IE5/IE6)
let xhr;
if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
} else if (window.ActiveXObject) {
    xhr = new ActiveXObject("Microsoft.XMLHTTP");
}

// 方式三:简化的兼容性检查
const xhr = window.XMLHttpRequest ? 
    new XMLHttpRequest() : 
    new ActiveXObject("Microsoft.XMLHTTP");

【代码注释】

  • IE5/6 使用 ActiveXObject('Microsoft.XMLHTTP');现代浏览器只需 new XMLHttpRequest()
  • 兼容性代码了解即可,新项目不必再写 ActiveX 分支。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

2.3 XHR 对象的重要属性

核心属性详解:

javascript 复制代码
const xhr = new XMLHttpRequest();

// 响应相关属性
console.log('XHR 核心属性:', {
    // readyState: 请求状态
    readyState: '0-4的数字值,表示请求状态',
    
    // status: HTTP 状态码
    status: '如 200, 404, 500 等',
    
    // statusText: HTTP 状态描述
    statusText: '如 "OK", "Not Found" 等',
    
    // responseText: 响应文本(字符串格式)
    responseText: '服务器返回的文本数据',
    
    // responseXML: 响应 XML 文档
    responseXML: '服务器返回的 XML DOM 对象',
    
    // response: 响应数据(根据 responseType 设置)
    response: '可以是 ArrayBuffer、Blob、Document、JSON 等',
    
    // responseType: 期望的响应类型
    responseType: '可以是 text、json、blob、arraybuffer 等',
    
    // timeout: 超时时间(毫秒)
    timeout: '设置请求超时时间',
    
    // upload: 上传进度对象
    upload: '用于监控上传进度的对象'
});

【代码注释】

  • responseType 可为 document(XML)、textarraybuffer(二进制)、blob 等。
  • 图片二进制可转 Blob + URL.createObjectURL 显示。
  • 日常接口 90% 为 json 或默认 text
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

【本章小结】

要点 内容
状态 readyState 0→4
属性 statusresponseTypeupload

【实战要点】

  • 经典应用场景:jQuery.ajax、早期库均基于 XHR 属性与事件。
  • 常见坑readyState===4status 为 404 仍进 onload
  • 性能与最佳实践 :调试时打印 readyState 理解各阶段。

【面试考点】

Q1:readyState 为 4 是否等于成功?

A:4 仅表示传输结束,须结合 status 2xx 判断业务成功。

2.4 配套可运行示例(状态机观察)

示例 A:打印 readyState 变化

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>readyState</title></head>
<body>
  <button id="btn">GET /getData</button>
  <ul id="log"></ul>
  <script>
    const log = document.getElementById('log');
    document.getElementById('btn').onclick = () => {
      const xhr = new XMLHttpRequest();
      xhr.onreadystatechange = () => {
        const li = document.createElement('li');
        li.textContent = `readyState=${xhr.readyState} status=${xhr.status}`;
        log.appendChild(li);
      };
      xhr.open('GET', '/getData');
      xhr.send();
    };
  </script>
</body>
</html>

【代码注释】

  • 一般看到 1→2→3→4 四次变化;4status 才稳定可读。
  • 教学用;项目里更推荐只绑 onload / onerror

示例 B:responseType 与 JSON

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>responseType json</title></head>
<body>
  <button id="btn">GET /getUsers</button>
  <pre id="out"></pre>
  <script>
    document.getElementById('btn').onclick = () => {
      const xhr = new XMLHttpRequest();
      xhr.responseType = 'json';
      xhr.onload = () => {
        out.textContent = JSON.stringify(xhr.response, null, 2);
      };
      xhr.open('GET', '/getUsers');
      xhr.send();
    };
    const out = document.getElementById('out');
  </script>
</body>
</html>

【代码注释】

  • 对应 05-响应json数据.html 核心;须服务端 Content-Type: application/json
  • 失败时 xhr.response 可能为 null,要先判断 status

3. Ajax 基本使用流程

3.1 标准Ajax流程

完整的 Ajax 请求流程:

javascript 复制代码
// 第一步:创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();

// 第二步:监听响应完成的事件
xhr.onload = function() {
    // 请求成功完成
    if (xhr.status === 200) {
        console.log('请求成功:', xhr.responseText);
        // 处理响应数据
        handleResponse(xhr.responseText);
    } else {
        console.error('请求失败:', xhr.status, xhr.statusText);
    }
};

// 第三步:监听错误事件
xhr.onerror = function() {
    console.error('网络错误或请求被阻止');
};

// 第四步:请求初始化
/*
参数说明:
- method: HTTP 请求方法(GET、POST、PUT、DELETE等)
- url: 请求的 URL 地址
- async: 是否异步(默认为 true)
*/
xhr.open('GET', '/api/data');

// 第五步:发送请求
/*
参数说明:
- send() 方法的参数是请求体,GET 请求通常为 null
- POST 请求可以发送字符串、FormData、Blob 等数据
*/
xhr.send();

【代码注释】

  • 标准五步new XMLHttpRequest → 绑定 onload/onerroropen(method, url) →(可选)setRequestHeadersend(body)
  • GET 请求 send() 无参或 null;POST 在 send 传入请求体字符串或 FormData
  • 对应页面 01-ajax基本流程.html ,服务路由 GET /getData(需先 node server.js)。
  • onloadreadyState===4 且成功时触发,比只写 onreadystatechange 更直观。

3.2 事件监听的完整实现

全事件监听的 Ajax 请求:

javascript 复制代码
function createAjaxRequest() {
    const xhr = new XMLHttpRequest();
    
    // 监听所有状态变化
    xhr.onreadystatechange = function() {
        console.log(`状态变化: readyState = ${xhr.readyState}`);
        
        if (xhr.readyState === 4) { // 请求完成
            if (xhr.status === 200) {
                console.log('请求成功');
                handleSuccess(xhr.responseText);
            } else {
                console.log('请求失败');
                handleError(xhr.status);
            }
        }
    };
    
    // 现代事件监听器
    xhr.onload = function() {
        console.log('请求完成');
    };
    
    xhr.onerror = function() {
        console.log('请求错误');
    };
    
    xhr.onabort = function() {
        console.log('请求被中断');
    };
    
    xhr.ontimeout = function() {
        console.log('请求超时');
    };
    
    xhr.onloadstart = function() {
        console.log('请求开始');
    };
    
    xhr.onloadend = function() {
        console.log('请求结束');
    };
    
    return xhr;
}

// 使用示例
const xhr = createAjaxRequest();
xhr.open('GET', '/api/data');
xhr.send();

【代码注释】

  • onreadystatechange 在状态变化时触发;onload/onerror 为 XHR2 推荐事件。
  • loadstartload/errorloadend 顺序与进度事件配合使用。
  • 返回 xhr 对象便于复用,实际项目可封装为 ajax(url, options) 工具函数。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

3.3 配套可运行示例(01-ajax基本流程)

保存为 ajax-basic.html,与 01-原生Ajax 同目录并 node server.js 后访问 /page01 或直接改 URL 为 http://127.0.0.1:8080/getData

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>Ajax 五步</title></head>
<body>
  <button id="btn">请求 /getData</button>
  <pre id="out"></pre>
  <script>
    const xhr = new XMLHttpRequest();
    xhr.onload = () => {
      if (xhr.status === 200) document.getElementById('out').textContent = xhr.responseText;
    };
    document.getElementById('btn').onclick = () => {
      xhr.open('GET', '/getData');
      xhr.send();
    };
  </script>
</body>
</html>

【代码注释】

  • 最小五步:onload 判断 200 后读 responseText
  • 同源访问 /getData 无需 CORS;跨域须服务端响应头。
  • 对应配套 01-ajax基本流程.html 与 §0.3 路由 /page01

3.4 配套可运行示例(onload / onerror)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>成功与失败分支</title></head>
<body>
  <button id="ok">GET /getData</button>
  <button id="bad">GET /not-exist</button>
  <p id="msg"></p>
  <script>
    function req(url) {
      const xhr = new XMLHttpRequest();
      xhr.onload = () => {
        msg.textContent = xhr.status === 200
          ? '成功:' + xhr.responseText.slice(0, 40)
          : 'HTTP 错误:' + xhr.status;
      };
      xhr.onerror = () => { msg.textContent = '网络错误(断网或未启动服务)'; };
      xhr.open('GET', url);
      xhr.send();
    }
    document.getElementById('ok').onclick = () => req('/getData');
    document.getElementById('bad').onclick = () => req('/not-exist');
    const msg = document.getElementById('msg');
  </script>
</body>
</html>

【代码注释】

  • 404 仍走 onload,须在回调里判断 status;断网或未启动服务才常走 onerror
  • 与 §1 中「不要只信 onload」的实战要点一致。

【本章小结】

步骤 API
1 new XMLHttpRequest()
2 onload / onerror
3 open(method, url)
4 setRequestHeader(可选)
5 send(body)

【实战要点】

  • 经典应用场景:列表局部刷新、按钮提交不跳转。
  • 常见坑open 写在 send 之后;忘记判断 status
  • 性能与最佳实践 :配套 01-ajax基本流程 + /page01 先跑通。

【面试考点】

Q1:标准 Ajax 五步?

A:创建 XHR → 绑事件 → open →(可选)请求头 → send。


4. 发送请求携带数据

4.1 GET 请求携带数据

通过 URL 携带数据:

javascript 复制代码
// 方式一:手动拼接查询字符串
const name = '张三';
const age = 28;
const xhr = new XMLHttpRequest();

// 构建查询字符串
const queryString = `name=${encodeURIComponent(name)}&age=${age}`;
xhr.open('GET', `/api/user?${queryString}`);
xhr.send();

// 方式二:使用 URLSearchParams 对象(推荐)
const params = new URLSearchParams();
params.append('name', '李四');
params.append('age', '30');
params.append('city', '北京');

const xhr2 = new XMLHttpRequest();
xhr2.open('GET', `/api/user?${params.toString()}`);
xhr2.send();

// 方式三:构建复杂查询条件
const searchParams = {
    keyword: 'JavaScript',
    category: '编程',
    page: 1,
    limit: 20,
    sort: 'created_at',
    order: 'desc'
};

const queryString = Object.keys(searchParams)
    .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(searchParams[key])}`)
    .join('&');

const xhr3 = new XMLHttpRequest();
xhr3.open('GET', `/api/articles?${queryString}`);
xhr3.send();

【代码注释】

  • GET 数据放在 URL 查询字符串:?key=value&key2=value2
  • encodeURIComponent 防止中文与 &= 破坏 query 结构。
  • URLSearchParams 自动编码,比手写拼接更安全(推荐)。
  • 对应 02-发送请求携带数据.html ;服务端用 req.query 读取。

4.2 POST 请求携带数据

发送 JSON 格式数据:

javascript 复制代码
// 准备要发送的数据
const userData = {
    username: '王五',
    email: 'wangwu@example.com',
    age: 25,
    address: {
        city: '上海',
        district: '浦东新区'
    },
    interests: ['编程', '阅读', '旅游']
};

const xhr = new XMLHttpRequest();

// 初始化请求
xhr.open('POST', '/api/users');

// 设置请求头(必须)
xhr.setRequestHeader('Content-Type', 'application/json');

// 发送请求(将对象转换为 JSON 字符串)
xhr.send(JSON.stringify(userData));

// 监听响应
xhr.onload = function() {
    if (xhr.status === 201) {
        const response = JSON.parse(xhr.responseText);
        console.log('用户创建成功:', response);
    }
};

【代码注释】

  • POST + Content-Type: application/json 时,请求体必须是 JSON 字符串JSON.stringify)。
  • setRequestHeader 须在 open 之后、send 之前设置。
  • 成功创建资源常返回 201;客户端用 JSON.parseresponseType:'json' 解析。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

发送表单数据:

javascript 复制代码
// 方式一:发送 URL 编码的表单数据
const formData = 'username=zhangsan&password=123456&email=zhangsan@example.com';

const xhr1 = new XMLHttpRequest();
xhr1.open('POST', '/api/login');
xhr1.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr1.send(formData);

// 方式二:使用 URLSearchParams 构建表单数据
const params = new URLSearchParams();
params.append('username', 'lisi');
params.append('password', '654321');
params.append('email', 'lisi@example.com');

const xhr2 = new XMLHttpRequest();
xhr2.open('POST', '/api/login');
xhr2.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr2.send(params.toString());

【代码注释】

  • GET 数据放在 URL 查询字符串:?key=value&key2=value2
  • encodeURIComponent 防止中文与 &= 破坏 query 结构。
  • URLSearchParams 自动编码,比手写拼接更安全(推荐)。
  • 对应 02-发送请求携带数据.html ;服务端用 req.query 读取。

4.3 FormData 对象使用

FormData 基础使用:

javascript 复制代码
// 创建 FormData 对象
const formData = new FormData();

// 添加字段
formData.append('username', 'zhangsan');
formData.append('password', '123456');
formData.append('avatar', fileInput.files[0]); // 添加文件

// 发送 FormData
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload');
xhr.send(formData); // 浏览器会自动设置合适的 Content-Type

// 监听上传进度
xhr.upload.onprogress = function(event) {
    if (event.lengthComputable) {
        const percentComplete = (event.loaded / event.total) * 100;
        console.log(`上传进度: ${percentComplete.toFixed(2)}%`);
        updateProgressBar(percentComplete);
    }
};

xhr.onload = function() {
    if (xhr.status === 200) {
        console.log('上传成功');
    }
};

【代码注释】

  • new FormData()new FormData(form) 作为 send() 参数。
  • 浏览器自动设置 Content-Type: multipart/form-data 及 boundary,不要手动设该头。
  • append(name, file) 可上传文件;对应 03-FormData.html + POST /upload
  • 上传进度用 xhr.upload.onprogress,注意监听对象在 upload 上而非 xhr 本身。

从表单元素创建 FormData:

javascript 复制代码
// HTML 表单
/*
<form id="userForm">
    <input type="text" name="username" />
    <input type="email" name="email" />
    <input type="file" name="avatar" />
    <textarea name="bio"></textarea>
</form>
*/

// JavaScript 代码
const form = document.getElementById('userForm');
const formData = new FormData(form);

// 添加额外字段
formData.append('userId', '12345');

// 发送请求
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/users');
xhr.send(formData);

【代码注释】

  • new FormData()new FormData(form) 作为 send() 参数。
  • 浏览器自动设置 Content-Type: multipart/form-data 及 boundary,不要手动设该头。
  • append(name, file) 可上传文件;对应 03-FormData.html + POST /upload
  • 上传进度用 xhr.upload.onprogress,注意监听对象在 upload 上而非 xhr 本身。

4.4 请求头详解

常用请求头设置:

javascript 复制代码
const xhr = new XMLHttpRequest();

// 初始化请求
xhr.open('POST', '/api/data');

// 设置各种请求头
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Authorization', 'Bearer token123');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.setRequestHeader('Accept', 'application/json');
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.setRequestHeader('X-Custom-Header', 'custom-value');

// 发送请求
xhr.send(JSON.stringify({ key: 'value' }));

【代码注释】

  • POST + Content-Type: application/json 时,请求体必须是 JSON 字符串JSON.stringify)。
  • setRequestHeader 须在 open 之后、send 之前设置。
  • 成功创建资源常返回 201;客户端用 JSON.parseresponseType:'json' 解析。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

Content-Type 详解:

javascript 复制代码
// 1. application/json - JSON 格式数据
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ name: '张三' }));

// 2. application/x-www-form-urlencoded - 表单编码数据
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('name=张三&age=28');

// 3. multipart/form-data - 多部分表单数据(文件上传)
// 注意:使用 FormData 时浏览器会自动设置,不需要手动设置
const formData = new FormData();
formData.append('file', file);
xhr.send(formData);

// 4. text/plain - 纯文本
xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.send('这是一段纯文本');

【代码注释】

  • POST + Content-Type: application/json 时,请求体必须是 JSON 字符串JSON.stringify)。
  • setRequestHeader 须在 open 之后、send 之前设置。
  • 成功创建资源常返回 201;客户端用 JSON.parseresponseType:'json' 解析。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

4.5 配套可运行示例(02 传参 + 03 FormData)

02-发送请求携带数据.html/page02 + /addData)精简版:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>GET/POST 传参</title></head>
<body>
  <button id="g">GET 查询串</button>
  <button id="p">POST JSON</button>
  <pre id="out"></pre>
  <script>
    const show = () => { out.textContent = xhr.responseText; };
    const xhr = new XMLHttpRequest();
    xhr.onload = show;
    document.getElementById('g').onclick = () => {
      xhr.open('GET', '/addData?name=张三&age=20');
      xhr.send();
    };
    document.getElementById('p').onclick = () => {
      xhr.open('POST', '/addData');
      xhr.setRequestHeader('Content-Type', 'application/json');
      xhr.send(JSON.stringify({ name: '李四', age: 22 }));
    };
    const out = document.getElementById('out');
  </script>
</body>
</html>

【代码注释】

  • GET 参数在 URL;POST JSON 须 setRequestHeader + JSON.stringify
  • 服务端 01-原生Ajax/server.js/addData 会回显 query 或 body。

03-FormData.html/page03 + POST /upload)精简版:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>FormData 上传</title></head>
<body>
  <input type="file" id="file" />
  <button id="up">上传</button>
  <div id="msg"></div>
  <script>
    document.getElementById('up').onclick = () => {
      const fd = new FormData();
      fd.append('avatar', file.files[0]);
      const xhr = new XMLHttpRequest();
      xhr.upload.onprogress = e => {
        if (e.lengthComputable)
          msg.textContent = (e.loaded / e.total * 100).toFixed(0) + '%';
      };
      xhr.onload = () => { msg.textContent = xhr.responseText; };
      xhr.open('POST', '/upload');
      xhr.send(fd);
    };
    const file = document.getElementById('file');
    const msg = document.getElementById('msg');
  </script>
</body>
</html>

【代码注释】

  • 不要 手动设置 multipart/form-datamulter 在服务端解析字段名 avatar
  • 上传进度监听 xhr.upload ,不是 xhr 本身。

【本章小结】

方式 数据位置 Content-Type
GET URL 查询串 ---
POST JSON body application/json
FormData body 浏览器自动 multipart

【实战要点】

  • 经典应用场景:列表筛选(GET)、登录(urlencoded/JSON)、头像上传(FormData)。
  • 常见坑 :中文未 encodeURIComponent;FormData 又手写 Content-Type 导致 boundary 错误。
  • 性能与最佳实践:大文件分片上传属进阶;本章掌握单文件 + 进度即可。

【面试考点】

Q1:GET 和 POST 传参区别?

A:GET 在 URL,有长度与缓存语义;POST 在 body,适合提交与修改。追问:PUT 与 POST?------语义上 PUT 常做全量更新,课堂 /addData 也演示 PUT body。


5. 响应处理与数据解析

5.1.1 配套可运行示例(04 解析响应报文)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>响应报文</title></head>
<body>
  <button id="btn">GET /getData</button>
  <pre id="out"></pre>
  <script>
    const xhr = new XMLHttpRequest();
    xhr.onload = () => {
      out.textContent =
        'status: ' + xhr.status + '\n' +
        'Content-Type: ' + xhr.getResponseHeader('Content-type') + '\n' +
        'body: ' + xhr.responseText;
    };
    document.getElementById('btn').onclick = () => {
      xhr.open('GET', '/getData');
      xhr.send();
    };
    const out = document.getElementById('out');
  </script>
</body>
</html>

【代码注释】

  • 练习 statusgetResponseHeaderresponseText 三者关系。
  • 对应 04-解析响应报文.html/page04 同源打开。

5.1 响应报文结构

HTTP 响应报文详解:

javascript 复制代码
function analyzeResponse(xhr) {
    // 响应行
    console.log('HTTP 版本:', xhr.httpVersion);
    console.log('状态码:', xhr.status);
    console.log('状态文本:', xhr.statusText);
    
    // 响应头
    console.log('Content-Type:', xhr.getResponseHeader('Content-Type'));
    console.log('Content-Length:', xhr.getResponseHeader('Content-Length'));
    console.log('Date:', xhr.getResponseHeader('Date'));
    console.log('Server:', xhr.getResponseHeader('Server'));
    
    // 获取所有响应头
    const allHeaders = xhr.getAllResponseHeaders();
    console.log('所有响应头:', allHeaders);
    
    // 响应体
    console.log('响应文本:', xhr.responseText);
    console.log('响应对象:', xhr.response);
}

// 使用示例
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.onload = function() {
    analyzeResponse(xhr);
};
xhr.send();

【代码注释】

  • 标准五步new XMLHttpRequest → 绑定 onload/onerroropen(method, url) →(可选)setRequestHeadersend(body)
  • GET 请求 send() 无参或 null;POST 在 send 传入请求体字符串或 FormData
  • 对应页面 01-ajax基本流程.html ,服务路由 GET /getData(需先 node server.js)。
  • onloadreadyState===4 且成功时触发,比只写 onreadystatechange 更直观。

5.2 处理 JSON 响应

方式一:使用 JSON.parse()

javascript 复制代码
const xhr = new XMLHttpRequest();

xhr.open('GET', '/api/users');

xhr.onload = function() {
    if (xhr.status === 200) {
        try {
            // 将 JSON 字符串解析为对象
            const users = JSON.parse(xhr.responseText);
            console.log('用户数据:', users);
            
            // 处理用户数据
            displayUsers(users);
        } catch (error) {
            console.error('JSON 解析失败:', error);
        }
    }
};

xhr.send();

【代码注释】

  • JSON.parse(xhr.responseText) 将 JSON 字符串转对象;须 try/catch 防非法 JSON。
  • 非 200 状态也可能有响应体,应先判断 xhr.status 再解析。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

方式二:设置 responseType 为 'json'

javascript 复制代码
const xhr = new XMLHttpRequest();

// 设置响应类型为 JSON
xhr.responseType = 'json';

xhr.open('GET', '/api/users');

xhr.onload = function() {
    if (xhr.status === 200) {
        // xhr.response 自动解析为 JavaScript 对象
        const users = xhr.response;
        console.log('用户数据:', users);
        displayUsers(users);
    }
};

xhr.send();

【代码注释】

  • 设置 xhr.responseType = 'json' 后,xhr.response 直接为对象(浏览器自动解析)。
  • 须在 open 之前或之后、send 之前设置;失败时 response 可能为 null
  • 服务端需返回 Content-Type: application/json,对应 05-响应json数据.html
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

5.2.1 配套示例:05-响应json数据.html

页面请求地址: GET /getUsers(通过 http://127.0.0.1:8080/page05 打开页面后点击按钮)。

javascript 复制代码
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';   // 须在 send 前设置

xhr.onload = () => {
    console.log(xhr.response);  // 已是对象,无需 JSON.parse
};

btn.onclick = () => {
    xhr.open('GET', '/getUsers');
    xhr.send();
};

【代码注释】(05-响应json 页面)

  • responseType:'json' 须在 send 前设置;onloadxhr.response 已是对象。
  • 通过 /page05 同源打开页面,请求写相对路径 /getUsers
  • 对比 JSON.parse(responseText):浏览器自动解析,依赖正确 Content-Type

服务端(01-原生Ajax/server.js 节选):

javascript 复制代码
app.get('/getUsers', (req, res) => {
    const users = [
        { name: '安妮0', age: 90, address: '上海' },
        // ... 共 5 条
    ];
    res.setHeader('Content-type', 'application/json;charset=utf-8');
    res.send(JSON.stringify(users));
});

【代码注释】

  • 设置 responseType:'json' 后不要用 responseTextparse,否则可能得到 null
  • 服务端 Content-Type: application/jsonresponseType 配合,浏览器自动解析为数组。
  • 联调路径:先 node server.js,访问 /page05,Network 中确认 /getUsers 状态 200。

5.3 处理其他格式响应

处理不同类型的响应:

javascript 复制代码
// 处理 XML 响应
function handleXMLResponse() {
    const xhr = new XMLHttpRequest();
    xhr.responseType = 'document';
    
    xhr.open('GET', '/api/data.xml');
    
    xhr.onload = function() {
        if (xhr.status === 200) {
            const xmlDoc = xhr.responseXML;
            const titles = xmlDoc.getElementsByTagName('title');
            console.log('XML 数据:', titles);
        }
    };
    
    xhr.send();
}

// 处理文本响应
function handleTextResponse() {
    const xhr = new XMLHttpRequest();
    xhr.responseType = 'text';
    
    xhr.open('GET', '/api/content.txt');
    
    xhr.onload = function() {
        if (xhr.status === 200) {
            const textContent = xhr.responseText;
            console.log('文本内容:', textContent);
        }
    };
    
    xhr.send();
}

// 处理二进制数据
function handleBinaryResponse() {
    const xhr = new XMLHttpRequest();
    xhr.responseType = 'arraybuffer';
    
    xhr.open('GET', '/api/image');
    
    xhr.onload = function() {
        if (xhr.status === 200) {
            const arrayBuffer = xhr.response;
            const blob = new Blob([arrayBuffer]);
            const imageUrl = URL.createObjectURL(blob);
            
            // 显示图片
            const img = document.createElement('img');
            img.src = imageUrl;
            document.body.appendChild(img);
        }
    };
    
    xhr.send();
}

【代码注释】

  • responseType 可为 document(XML)、textarraybuffer(二进制)、blob 等。
  • 图片二进制可转 Blob + URL.createObjectURL 显示。
  • 日常接口 90% 为 json 或默认 text
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

5.4 响应状态码处理

完整的响应状态码处理:

javascript 复制代码
function handleResponseStatus(xhr) {
    switch (xhr.status) {
        // 成功响应
        case 200:
            console.log('OK - 请求成功');
            break;
        case 201:
            console.log('Created - 资源创建成功');
            break;
        case 204:
            console.log('No Content - 请求成功,无返回内容');
            break;
            
        // 重定向
        case 301:
            console.log('Moved Permanently - 资源已永久移动');
            break;
        case 302:
            console.log('Found - 资源临时移动');
            break;
            
        // 客户端错误
        case 400:
            console.log('Bad Request - 请求参数错误');
            break;
        case 401:
            console.log('Unauthorized - 未授权,需要登录');
            break;
        case 403:
            console.log('Forbidden - 权限不足');
            break;
        case 404:
            console.log('Not Found - 资源不存在');
            break;
            
        // 服务器错误
        case 500:
            console.log('Internal Server Error - 服务器内部错误');
            break;
        case 502:
            console.log('Bad Gateway - 网关错误');
            break;
        case 503:
            console.log('Service Unavailable - 服务不可用');
            break;
            
        default:
            console.log('未知状态码:', xhr.status);
    }
}

// 使用示例
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.onload = function() {
    handleResponseStatus(xhr);
};
xhr.send();

【代码注释】

  • onload 在传输结束(含 4xx/5xx)时仍会触发,须用 xhr.status 分支处理。
  • 401/403 常配合跳转登录;204 无 body 时不要 JSON.parse
  • 04-解析响应报文.html05-响应json数据.html 配套对照 Network 面板。

5.5 配套可运行示例(05 JSON 列表)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>JSON 用户列表</title></head>
<body>
  <button id="btn">GET /getUsers</button>
  <ul id="list"></ul>
  <script>
    document.getElementById('btn').onclick = () => {
      const xhr = new XMLHttpRequest();
      xhr.responseType = 'json';
      xhr.onload = () => {
        list.innerHTML = '';
        if (xhr.status !== 200) return;
        xhr.response.forEach(u => {
          const li = document.createElement('li');
          li.textContent = u.name + ' · ' + u.age;
          list.appendChild(li);
        });
      };
      xhr.open('GET', '/getUsers');
      xhr.send();
    };
    const list = document.getElementById('list');
  </script>
</body>
</html>

【代码注释】

  • 访问 /page05 可打开课堂完整版;本页演示 responseType:'json' 直接得到对象数组。
  • 也可用 JSON.parse(xhr.responseText),须 try/catch 防脏数据。

【本章小结】

能力 API / 字段
报文 statusgetResponseHeader
JSON responseType:'json'JSON.parse(responseText)
二进制 arraybuffer + Blob

【实战要点】

  • 经典应用场景 :列表渲染 /getUsers、下载图片 arraybuffer
  • 常见坑responseType 为 json 仍读 responseText;404 也进 onload
  • 性能与最佳实践 :先判断 status 再解析;大列表考虑虚拟滚动(属 UI 层)。

【面试考点】

Q1:responseType: 'json' 要注意什么?

A:响应 Content-Type 宜为 application/json;非 2xx 时 response 可能为 null。追问与 fetch().json() 关系?------Fetch 在 Promise 链里解析,XHR 在 onload 里读 xhr.response


6. 进度事件与超时控制

6.1 进度事件详解

下载进度监控:

javascript 复制代码
function downloadWithProgress() {
    const xhr = new XMLHttpRequest();
    
    // 监听下载进度
    xhr.onprogress = function(event) {
        if (event.lengthComputable) {
            const percentComplete = (event.loaded / event.total) * 100;
            
            console.log(`下载进度: ${percentComplete.toFixed(2)}%`);
            console.log(`已下载: ${event.loaded} 字节`);
            console.log(`总大小: ${event.total} 字节`);
            
            // 更新进度条
            updateProgressBar(percentComplete);
            updateProgressInfo(event.loaded, event.total);
        } else {
            console.log('下载数据大小未知');
        }
    };
    
    // 其他进度事件
    xhr.onloadstart = function() {
        console.log('下载开始');
        showProgressBar();
    };
    
    xhr.onloadend = function() {
        console.log('下载结束');
        hideProgressBar();
    };
    
    xhr.onload = function() {
        if (xhr.status === 200) {
            console.log('下载完成');
        }
    };
    
    xhr.open('GET', '/api/large-file');
    xhr.send();
}

【代码注释】

  • 下载进度:xhr.onprogressevent.loaded / event.total 计算百分比。
  • 对应 07-进度事件.htmlloadstart/loadend 控制进度条显隐。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

上传进度监控:

javascript 复制代码
function uploadWithProgress(file) {
    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('file', file);
    
    // 监听上传进度
    xhr.upload.onprogress = function(event) {
        if (event.lengthComputable) {
            const percentComplete = (event.loaded / event.total) * 100;
            
            console.log(`上传进度: ${percentComplete.toFixed(2)}%`);
            console.log(`已上传: ${event.loaded} 字节`);
            console.log(`总大小: ${event.total} 字节`);
            
            // 更新进度条
            updateUploadProgressBar(percentComplete);
        }
    };
    
    // 上传相关事件
    xhr.upload.onloadstart = function() {
        console.log('上传开始');
    };
    
    xhr.upload.onloadend = function() {
        console.log('上传结束');
    };
    
    xhr.onload = function() {
        if (xhr.status === 200) {
            console.log('上传成功');
        }
    };
    
    xhr.onerror = function() {
        console.error('上传失败');
    };
    
    xhr.open('POST', '/api/upload');
    xhr.send(formData);
}

// 使用示例
const fileInput = document.querySelector('#fileInput');
fileInput.addEventListener('change', function() {
    const file = this.files[0];
    if (file) {
        uploadWithProgress(file);
    }
});

【代码注释】

  • new FormData()new FormData(form) 作为 send() 参数。
  • 浏览器自动设置 Content-Type: multipart/form-data 及 boundary,不要手动设该头。
  • append(name, file) 可上传文件;对应 03-FormData.html + POST /upload
  • 上传进度用 xhr.upload.onprogress,注意监听对象在 upload 上而非 xhr 本身。

6.1.1 配套示例:07-进度事件.html

页面请求: GET /getMoreData(服务端返回约 10 万行重复文本,体积大,便于观察进度)。

javascript 复制代码
xhr.onprogress = event => {
    const pct = (event.loaded / event.total * 100).toFixed(2);
    box.innerHTML = '已下载:' + pct + '%';
};
xhr.onload = () => { box.innerHTML = '下载完毕!'; };
xhr.onloadstart = () => console.log('loadstart');
xhr.onloadend = () => console.log('loadend');

btn.onclick = () => {
    xhr.open('GET', '/getMoreData');
    xhr.send();
};

【代码注释】

  • 下载进度 监听 xhr.onprogress上传进度 才用 xhr.upload.onprogress(见 03-FormData)。
  • 配套页同时打印 readyState 变化,帮助理解 0→4 状态;生产更常用 onload 代替 onreadystatechange
  • event.lengthComputable 为 false 时无法算百分比,本接口可计算总量。
  • 访问 /page07 打开页面;404 不算 onerror,只有网络层失败才算(课堂注释要点)。

6.2 响应超时控制

6.2.1 配套示例:06-响应超时.html

页面逻辑: xhr.timeout = 5000,请求 GET /getInfo;服务端 随机 0~9 秒 后才 res.send,故有时 onload、有时 ontimeout

javascript 复制代码
xhr.timeout = 5000;
xhr.onload = () => { box.innerHTML = '响应成功:' + xhr.response; };
xhr.ontimeout = () => { box.innerHTML = '响应超时!'; };

btn.onclick = () => {
    xhr.open('GET', '/getInfo');
    xhr.send();
};

【代码注释】(06-响应超时 页面)

  • xhr.timeout = 5000 为客户端最长等待;超时走 ontimeout 而非 onload
  • 服务端 getInfo 随机延迟 0~9 秒,多试几次观察成功/超时分支。
  • 生产应给用户明确提示并支持重试。

服务端(节选):

javascript 复制代码
app.get('/getInfo', (req, res) => {
    const delay = Math.floor(Math.random() * 10) * 1000;
    setTimeout(() => res.send('getInfo 响应成功!'), delay);
});

【代码注释】

  • timeout 必须在 send() 之前 赋值;超时走 ontimeout,不会触发 onload
  • 多试几次按钮:延迟小于 5 秒则成功,否则触发超时,直观理解客户端 timeout 与服务端慢响应的关系。
  • 访问 /page06 打开 06-响应超时.html

方式一:兼容性超时控制

javascript 复制代码
function requestWithTimeout(url, timeout) {
    const xhr = new XMLHttpRequest();
    
    // 监听请求状态变化
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            // 请求完成,清除超时定时器
            clearTimeout(timeoutId);
            
            if (xhr.status === 200) {
                console.log('请求成功:', xhr.responseText);
            } else {
                console.error('请求失败:', xhr.status);
            }
        }
    };
    
    xhr.open('GET', url);
    xhr.send();
    
    // 设置超时定时器
    const timeoutId = setTimeout(function() {
        if (xhr.readyState !== 4) {
            // 请求未完成,中断请求
            xhr.abort();
            console.error('请求超时');
        }
    }, timeout);
}

// 使用示例
requestWithTimeout('/api/slow-endpoint', 5000); // 5秒超时

【代码注释】

  • 兼容方案:sendsetTimeout,若 readyState!==4xhr.abort()
  • 了解即可;优先使用 XHR2 的 xhr.timeout + ontimeout06-响应超时.html)。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

方式二:XHR2 标准超时控制

javascript 复制代码
function requestWithXHR2Timeout(url, timeout) {
    const xhr = new XMLHttpRequest();
    
    // 设置超时时间
    xhr.timeout = timeout;
    
    // 监听超时事件
    xhr.ontimeout = function() {
        console.error('请求超时');
    };
    
    xhr.onload = function() {
        if (xhr.status === 200) {
            console.log('请求成功:', xhr.responseText);
        }
    };
    
    xhr.onerror = function() {
        console.error('请求错误');
    };
    
    xhr.open('GET', url);
    xhr.send();
}

// 使用示例
requestWithXHR2Timeout('/api/data', 3000); // 3秒超时

【代码注释】

  • xhr.timeout = 毫秒 须在 send 之前设置;超时触发 ontimeout,不会走 onload
  • abort() 不同:timeout 为时钟到期,abort 为手动中断。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

6.3 完整的进度监控示例

文件上传完整示例:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>文件上传进度示例</title>
    <style>
        .progress-container {
            width: 100%;
            max-width: 500px;
            margin: 20px auto;
        }
        
        .progress-bar {
            width: 100%;
            height: 20px;
            background-color: #f0f0f0;
            border-radius: 10px;
            overflow: hidden;
        }
        
        .progress-fill {
            height: 100%;
            background-color: #4CAF50;
            width: 0%;
            transition: width 0.3s;
        }
        
        .progress-text {
            text-align: center;
            margin-top: 5px;
            font-size: 14px;
        }
    </style>
</head>
<body>
    <div class="upload-container">
        <input type="file" id="fileInput" accept="image/*">
        <button id="uploadBtn">上传文件</button>
        
        <div class="progress-container">
            <div class="progress-bar">
                <div class="progress-fill" id="progressFill"></div>
            </div>
            <div class="progress-text" id="progressText">0%</div>
        </div>
    </div>

    <script>
        const fileInput = document.getElementById('fileInput');
        const uploadBtn = document.getElementById('uploadBtn');
        const progressFill = document.getElementById('progressFill');
        const progressText = document.getElementById('progressText');
        
        uploadBtn.addEventListener('click', function() {
            const file = fileInput.files[0];
            if (file) {
                uploadFile(file);
            }
        });
        
        function uploadFile(file) {
            const xhr = new XMLHttpRequest();
            const formData = new FormData();
            formData.append('file', file);
            
            // 上传进度
            xhr.upload.onprogress = function(event) {
                if (event.lengthComputable) {
                    const percentComplete = (event.loaded / event.total) * 100;
                    updateProgress(percentComplete, event.loaded, event.total);
                }
            };
            
            xhr.upload.onloadstart = function() {
                console.log('上传开始');
                uploadBtn.disabled = true;
            };
            
            xhr.upload.onloadend = function() {
                console.log('上传结束');
                uploadBtn.disabled = false;
            };
            
            xhr.onload = function() {
                if (xhr.status === 200) {
                    console.log('上传成功');
                    alert('文件上传成功!');
                } else {
                    console.error('上传失败');
                    alert('文件上传失败!');
                }
            };
            
            xhr.onerror = function() {
                console.error('上传错误');
                alert('上传过程中发生错误!');
            };
            
            xhr.ontimeout = function() {
                console.error('上传超时');
                alert('上传超时,请重试!');
            };
            
            xhr.open('POST', '/api/upload');
            xhr.timeout = 60000; // 60秒超时
            xhr.send(formData);
        }
        
        function updateProgress(percent, loaded, total) {
            const percentString = percent.toFixed(2) + '%';
            progressFill.style.width = percentString;
            progressText.textContent = percentString;
            
            // 显示详细信息
            const loadedMB = (loaded / 1024 / 1024).toFixed(2);
            const totalMB = (total / 1024 / 1024).toFixed(2);
            progressText.textContent = `${percentString} (${loadedMB}MB / ${totalMB}MB)`;
        }
    </script>
</body>
</html>

【代码注释】

  • 完整页面可保存为 .html 后在安装依赖并启动 server.js 的环境下测试上传接口。
  • xhr.upload.onprogress 监听上传字节进度;lengthComputable 为 true 时才能算百分比。
  • xhr.timeoutontimeout 配合,避免大文件长时间无响应。
  • 按钮 disabled 防止重复提交,上传结束在 loadend 中恢复。

6.4 配套可运行示例(06 超时 + 07 进度)

06-响应超时.html/page06 + /getInfo):

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>超时演示</title></head>
<body>
  <button id="btn">请求 /getInfo</button>
  <div id="box"></div>
  <script>
    const xhr = new XMLHttpRequest();
    xhr.timeout = 5000;
    xhr.onload = () => { box.textContent = '成功:' + xhr.response; };
    xhr.ontimeout = () => { box.textContent = '超时(5s)'; };
    document.getElementById('btn').onclick = () => {
      xhr.open('GET', '/getInfo');
      xhr.send();
    };
    const box = document.getElementById('box');
  </script>
</body>
</html>

【代码注释】

  • 服务端随机 0~9 秒响应,多试几次观察 ontimeoutonload 分支。
  • timeout 必须在 send 前赋值。

07-进度事件.html/page07 + /getMoreData):

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>下载进度</title></head>
<body>
  <button id="btn">下载大文本</button>
  <div id="box">0%</div>
  <script>
    const xhr = new XMLHttpRequest();
    xhr.onprogress = e => {
      if (e.lengthComputable)
        box.textContent = (e.loaded / e.total * 100).toFixed(1) + '%';
    };
    xhr.onload = () => { box.textContent = '完成'; };
    document.getElementById('btn').onclick = () => {
      xhr.open('GET', '/getMoreData');
      xhr.send();
    };
    const box = document.getElementById('box');
  </script>
</body>
</html>

【代码注释】

  • 下载 进度用 xhr.onprogress上传xhr.upload.onprogress
  • /getMoreData 返回超长文本,便于观察百分比变化。

【本章小结】

主题 API / 路由
下载进度 onprogress + /getMoreData
上传进度 xhr.upload.onprogress
超时 timeoutontimeout + /getInfo

【实战要点】

  • 经典应用场景:文件上传进度条、慢接口 5s 超时提示。
  • 常见坑 :上传进度误绑在 xhr 而非 xhr.uploadtimeout 写在 send 之后无效。
  • 性能与最佳实践:大文件用分片 + 断点续传(进阶);本章掌握百分比即可。

【面试考点】

Q1:上传进度监听谁?

A:xhr.upload.onprogress,且 event.lengthComputable 为 true 才能算百分比。


7. 同步与异步请求

7.1 异步请求详解

异步请求特点:

javascript 复制代码
function asyncRequestExample() {
    console.log('1. 开始执行');
    
    const xhr = new XMLHttpRequest();
    
    // 异步请求(默认)
    xhr.open('GET', '/api/data', true); // 第三个参数 true 表示异步
    
    xhr.onload = function() {
        console.log('3. 收到响应');
        // 异步回调函数在主线程空闲时执行
    };
    
    xhr.send();
    
    console.log('2. 请求已发送,继续执行其他代码');
    
    // 执行顺序:1 -> 2 -> 3
}

// 异步请求的实际应用
function loadUserData(userId) {
    const xhr = new XMLHttpRequest();
    
    xhr.open('GET', `/api/users/${userId}`, true);
    
    xhr.onload = function() {
        if (xhr.status === 200) {
            const user = JSON.parse(xhr.responseText);
            displayUserInfo(user);
        }
    };
    
    xhr.send();
    
    // 请求发送后,页面可以继续响应用户操作
    console.log('用户数据请求已发送...');
}

【代码注释】

  • JSON.parse(xhr.responseText) 将 JSON 字符串转对象;须 try/catch 防非法 JSON。
  • 非 200 状态也可能有响应体,应先判断 xhr.status 再解析。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

7.2 同步请求详解

同步请求特点:

javascript 复制代码
function syncRequestExample() {
    console.log('1. 开始执行');
    
    const xhr = new XMLHttpRequest();
    
    // 同步请求
    xhr.open('GET', '/api/data', false); // 第三个参数 false 表示同步
    
    xhr.send(); // 会阻塞代码执行,直到收到响应
    
    // 请求完成后才会执行后续代码
    if (xhr.status === 200) {
        console.log('2. 收到响应:', xhr.responseText);
    }
    
    console.log('3. 继续执行其他代码');
    
    // 执行顺序:1 -> 2 -> 3
}

// 同步请求的实际应用(一般不推荐)
function checkUsernameSync(username) {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', `/api/check-username?username=${username}`, false);
    xhr.send();
    
    if (xhr.status === 200) {
        const response = JSON.parse(xhr.responseText);
        return response.available;
    }
    
    return false;
}

// 使用示例(会阻塞页面)
const available = checkUsernameSync('zhangsan');
console.log('用户名是否可用:', available);

【代码注释】

  • JSON.parse(xhr.responseText) 将 JSON 字符串转对象;须 try/catch 防非法 JSON。
  • 非 200 状态也可能有响应体,应先判断 xhr.status 再解析。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

7.3 同步与异步对比

性能对比分析:

javascript 复制代码
// 异步请求示例
function asyncRequests() {
    const startTime = Date.now();
    
    const xhr1 = new XMLHttpRequest();
    xhr1.open('GET', '/api/data1', true);
    xhr1.onload = function() {
        console.log('请求1完成:', Date.now() - startTime, 'ms');
    };
    xhr1.send();
    
    const xhr2 = new XMLHttpRequest();
    xhr2.open('GET', '/api/data2', true);
    xhr2.onload = function() {
        console.log('请求2完成:', Date.now() - startTime, 'ms');
    };
    xhr2.send();
    
    const xhr3 = new XMLHttpRequest();
    xhr3.open('GET', '/api/data3', true);
    xhr3.onload = function() {
        console.log('请求3完成:', Date.now() - startTime, 'ms');
    };
    xhr3.send();
    
    // 异步请求可以同时发送,总时间约为最慢请求的时间
}

// 同步请求示例
function syncRequests() {
    const startTime = Date.now();
    
    const xhr1 = new XMLHttpRequest();
    xhr1.open('GET', '/api/data1', false);
    xhr1.send();
    console.log('请求1完成:', Date.now() - startTime, 'ms');
    
    const xhr2 = new XMLHttpRequest();
    xhr2.open('GET', '/api/data2', false);
    xhr2.send();
    console.log('请求2完成:', Date.now() - startTime, 'ms');
    
    const xhr3 = new XMLHttpRequest();
    xhr3.open('GET', '/api/data3', false);
    xhr3.send();
    console.log('请求3完成:', Date.now() - startTime, 'ms');
    
    // 同步请求必须等待前一个完成,总时间是所有请求时间的总和
}

【代码注释】

  • open 第三参数 false 表示同步请求,会阻塞主线程直到响应返回。
  • 页面会卡死无响应,生产禁止;仅了解或 Worker 内特殊场景。
  • 对应 08-同步和异步.html ;默认应使用 true 或不写(默认异步)。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

7.2.1 配套示例:08-同步和异步.html

同一 xhr 对象,两个按钮对比 异步同步 (均请求 /getMoreData):

javascript 复制代码
btn1.onclick = () => {
    xhr.open('GET', '/getMoreData');
    xhr.send();
    console.log('异步请求发出!');
};

btn2.onclick = () => {
    xhr.open('GET', '/getMoreData', false);
    xhr.send();
    console.log('同步请求发出!');
};

【代码注释】

  • 异步:先打印「异步请求发出!」,稍后 onload 才触发;同步:send 阻塞主线程,大响应时页面卡死。
  • 通过 http://127.0.0.1:8080/page08 打开;依赖 07 同款 /getMoreData 大文本接口。

7.4 使用建议

何时使用异步或同步:

javascript 复制代码
// ✅ 推荐:使用异步请求
async function fetchDataAsync() {
    try {
        const data = await fetch('/api/data');
        return await data.json();
    } catch (error) {
        console.error('请求失败:', error);
    }
}

// ❌ 不推荐:使用同步请求(会阻塞页面)
function fetchDataSync() {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', '/api/data', false);
    xhr.send();
    
    if (xhr.status === 200) {
        return JSON.parse(xhr.responseText);
    }
    
    return null;
}

// 特殊场景:Worker 中可以使用同步请求
// Web Worker 不会阻塞主线程
const workerCode = `
    self.onmessage = function(e) {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', e.data.url, false);
        xhr.send();
        
        if (xhr.status === 200) {
            self.postMessage({ success: true, data: xhr.responseText });
        } else {
            self.postMessage({ success: false, error: '请求失败' });
        }
    };
`;

const blob = new Blob([workerCode], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));

worker.postMessage({ url: '/api/data' });

worker.onmessage = function(e) {
    if (e.data.success) {
        console.log('Worker 请求数据:', e.data.data);
    }
};

【代码注释】

  • JSON.parse(xhr.responseText) 将 JSON 字符串转对象;须 try/catch 防非法 JSON。
  • 非 200 状态也可能有响应体,应先判断 xhr.status 再解析。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。
  • 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。

7.5 配套可运行示例(08 同步/异步)

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>同步 vs 异步</title></head>
<body>
  <button id="a">异步 GET</button>
  <button id="s">同步 GET(会卡)</button>
  <script>
    const xhr = new XMLHttpRequest();
    xhr.onload = () => console.log('响应结束');
    document.getElementById('a').onclick = () => {
      xhr.open('GET', '/getMoreData');
      xhr.send();
      console.log('异步:send 后立即打印');
    };
    document.getElementById('s').onclick = () => {
      xhr.open('GET', '/getMoreData', false);
      xhr.send();
      console.log('同步:响应后才打印');
    };
  </script>
</body>
</html>

【代码注释】

  • 打开控制台对比两次点击的打印顺序;同步模式大响应会卡住页面。
  • 对应 08-同步和异步.html ,访问 /page08

【本章小结】

模式 open 第三参数
异步 true(默认)
同步 false(阻塞,生产禁用)

【实战要点】

  • 经典应用场景:仅教学演示;生产一律异步 XHR 或 Fetch。
  • 常见坑 :同步 + /getMoreData 导致页面长时间无响应。
  • 性能与最佳实践 :顺序任务用 Promise 链,勿用 open(..., false)

【面试考点】

Q1:为什么不推荐同步 XHR?

A:阻塞主线程,用户无法交互;异步 + 回调/Promise 可完成同样逻辑。


8. Ajax 基础实战案例

8.1 用户名验证示例

实时用户名验证:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>用户名验证示例</title>
    <style>
        .form-group {
            margin: 20px 0;
        }
        
        .input-group {
            position: relative;
        }
        
        .validation-message {
            position: absolute;
            right: 10px;
            top: 50%;
            transform: translateY(-50%);
            font-size: 12px;
        }
        
        .valid {
            color: green;
        }
        
        .invalid {
            color: red;
        }
        
        .loading {
            color: blue;
        }
    </style>
</head>
<body>
    <div class="form-container">
        <div class="form-group">
            <label for="username">用户名:</label>
            <div class="input-group">
                <input type="text" id="username" name="username" placeholder="请输入用户名">
                <span class="validation-message" id="validationMessage"></span>
            </div>
        </div>
    </div>

    <script>
        const usernameInput = document.getElementById('username');
        const validationMessage = document.getElementById('validationMessage');
        let debounceTimer;
        
        // 防抖处理
        usernameInput.addEventListener('input', function() {
            clearTimeout(debounceTimer);
            
            const username = this.value.trim();
            
            if (username.length === 0) {
                validationMessage.textContent = '';
                validationMessage.className = 'validation-message';
                return;
            }
            
            if (username.length < 3) {
                validationMessage.textContent = '用户名至少3个字符';
                validationMessage.className = 'validation-message invalid';
                return;
            }
            
            // 显示验证中状态
            validationMessage.textContent = '验证中...';
            validationMessage.className = 'validation-message loading';
            
            // 延迟发送验证请求
            debounceTimer = setTimeout(() => {
                validateUsername(username);
            }, 500);
        });
        
        function validateUsername(username) {
            const xhr = new XMLHttpRequest();
            
            xhr.open('GET', `/api/check-username?username=${encodeURIComponent(username)}`, true);
            
            xhr.onload = function() {
                if (xhr.status === 200) {
                    const response = JSON.parse(xhr.responseText);
                    
                    if (response.available) {
                        validationMessage.textContent = '✓ 用户名可用';
                        validationMessage.className = 'validation-message valid';
                    } else {
                        validationMessage.textContent = '✗ 用户名已存在';
                        validationMessage.className = 'validation-message invalid';
                    }
                }
            };
            
            xhr.onerror = function() {
                validationMessage.textContent = '验证失败,请重试';
                validationMessage.className = 'validation-message invalid';
            };
            
            xhr.send();
        }
    </script>
</body>
</html>

【代码注释】

  • 演示 输入防抖setTimeout 延迟 500ms 再发请求,减少无效校验次数。
  • GET /api/check-username?username= 携带查询参数,响应用 JSON.parse 解析。
  • 本地联调可将 URL 改为 http://127.0.0.1:8080/... 与课堂服务一致。
  • 状态文案通过 class 切换 valid / invalid / loading 提升反馈感。

8.2 级联选择器示例

省市区三级联动:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>级联选择器示例</title>
</head>
<body>
    <div class="form-container">
        <label for="province">省份:</label>
        <select id="province">
            <option value="">请选择省份</option>
        </select>
        
        <label for="city">城市:</label>
        <select id="city" disabled>
            <option value="">请选择城市</option>
        </select>
        
        <label for="district">区县:</label>
        <select id="district" disabled>
            <option value="">请选择区县</option>
        </select>
    </div>

    <script>
        const provinceSelect = document.getElementById('province');
        const citySelect = document.getElementById('city');
        const districtSelect = document.getElementById('district');
        
        // 加载省份数据
        loadProvinces();
        
        function loadProvinces() {
            const xhr = new XMLHttpRequest();
            
            xhr.open('GET', '/api/provinces', true);
            
            xhr.onload = function() {
                if (xhr.status === 200) {
                    const provinces = JSON.parse(xhr.responseText);
                    populateSelect(provinceSelect, provinces);
                }
            };
            
            xhr.send();
        }
        
        // 省份选择变化
        provinceSelect.addEventListener('change', function() {
            const provinceId = this.value;
            
            // 重置城市和区县选择
            citySelect.innerHTML = '<option value="">请选择城市</option>';
            citySelect.disabled = true;
            districtSelect.innerHTML = '<option value="">请选择区县</option>';
            districtSelect.disabled = true;
            
            if (provinceId) {
                loadCities(provinceId);
            }
        });
        
        function loadCities(provinceId) {
            const xhr = new XMLHttpRequest();
            
            xhr.open('GET', `/api/cities?provinceId=${provinceId}`, true);
            
            xhr.onload = function() {
                if (xhr.status === 200) {
                    const cities = JSON.parse(xhr.responseText);
                    populateSelect(citySelect, cities);
                    citySelect.disabled = false;
                }
            };
            
            xhr.send();
        }
        
        // 城市选择变化
        citySelect.addEventListener('change', function() {
            const cityId = this.value;
            
            // 重置区县选择
            districtSelect.innerHTML = '<option value="">请选择区县</option>';
            districtSelect.disabled = true;
            
            if (cityId) {
                loadDistricts(cityId);
            }
        });
        
        function loadDistricts(cityId) {
            const xhr = new XMLHttpRequest();
            
            xhr.open('GET', `/api/districts?cityId=${cityId}`, true);
            
            xhr.onload = function() {
                if (xhr.status === 200) {
                    const districts = JSON.parse(xhr.responseText);
                    populateSelect(districtSelect, districts);
                    districtSelect.disabled = false;
                }
            };
            
            xhr.send();
        }
        
        function populateSelect(selectElement, items) {
            items.forEach(item => {
                const option = document.createElement('option');
                option.value = item.id;
                option.textContent = item.name;
                selectElement.appendChild(option);
            });
        }
    </script>
</body>
</html>

【代码注释】

  • 省市区三级联动:上级 change 触发下级 Ajax 拉取,未选上级时禁用子级 <select>
  • 每次切换先清空下级选项,避免残留错误数据。
  • 接口返回 JSON 数组,用 populateSelect 动态创建 <option>
  • 三个页面可改为同一 server.js 模拟接口做本地实验。

8.3 搜索建议示例

自动完成搜索功能:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>搜索建议示例</title>
    <style>
        .search-container {
            position: relative;
            width: 300px;
            margin: 20px auto;
        }
        
        .search-input {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
        }
        
        .suggestions-list {
            position: absolute;
            width: 100%;
            border: 1px solid #ccc;
            border-top: none;
            display: none;
        }
        
        .suggestions-list.show {
            display: block;
        }
        
        .suggestion-item {
            padding: 8px;
            cursor: pointer;
        }
        
        .suggestion-item:hover {
            background-color: #f0f0f0;
        }
    </style>
</head>
<body>
    <div class="search-container">
        <input type="text" id="searchInput" class="search-input" placeholder="搜索关键词...">
        <div id="suggestionsList" class="suggestions-list"></div>
    </div>

    <script>
        const searchInput = document.getElementById('searchInput');
        const suggestionsList = document.getElementById('suggestionsList');
        let debounceTimer;
        
        searchInput.addEventListener('input', function() {
            clearTimeout(debounceTimer);
            
            const keyword = this.value.trim();
            
            if (keyword.length === 0) {
                hideSuggestions();
                return;
            }
            
            // 延迟发送搜索请求
            debounceTimer = setTimeout(() => {
                fetchSuggestions(keyword);
            }, 300);
        });
        
        // 点击其他地方隐藏建议列表
        document.addEventListener('click', function(e) {
            if (!searchInput.contains(e.target) && !suggestionsList.contains(e.target)) {
                hideSuggestions();
            }
        });
        
        function fetchSuggestions(keyword) {
            const xhr = new XMLHttpRequest();
            
            xhr.open('GET', `/api/suggestions?keyword=${encodeURIComponent(keyword)}`, true);
            
            xhr.onload = function() {
                if (xhr.status === 200) {
                    const suggestions = JSON.parse(xhr.responseText);
                    displaySuggestions(suggestions);
                }
            };
            
            xhr.onerror = function() {
                hideSuggestions();
            };
            
            xhr.send();
        }
        
        function displaySuggestions(suggestions) {
            if (suggestions.length === 0) {
                hideSuggestions();
                return;
            }
            
            suggestionsList.innerHTML = '';
            
            suggestions.forEach(suggestion => {
                const item = document.createElement('div');
                item.className = 'suggestion-item';
                item.textContent = suggestion;
                
                item.addEventListener('click', function() {
                    searchInput.value = suggestion;
                    hideSuggestions();
                    performSearch(suggestion);
                });
                
                suggestionsList.appendChild(item);
            });
            
            showSuggestions();
        }
        
        function showSuggestions() {
            suggestionsList.classList.add('show');
        }
        
        function hideSuggestions() {
            suggestionsList.classList.remove('show');
        }
        
        function performSearch(keyword) {
            console.log('执行搜索:', keyword);
            // 这里可以执行实际的搜索操作
        }
    </script>
</body>
</html>

【代码注释】

  • 搜索建议:输入防抖 300ms 后请求 /api/suggestions?keyword=
  • 点击文档其他区域 hideSuggestions,避免列表遮挡。
  • 选中建议项后写入输入框并触发 performSearch(可再接列表页请求)。
  • 与百度/Google 下拉联想同属 Ajax 典型场景。

【本章小结】

案例 技术点
用户名验证 防抖 + XHR
搜索建议 输入联想

【实战要点】

  • 经典应用场景:注册查重、搜索下拉、无刷新表单。
  • 常见坑 :未 encodeURIComponent;无 loading/错误 UI。
  • 性能与最佳实践:输入类请求必须防抖。

【面试考点】

Q1:实时用户名查重如何实现?

A:监听 input,防抖后 GET,根据响应更新提示文案。


9. 跨域:同源策略、CORS 与 JSONP

9.0 配套跨域页面(02-跨域)

02-跨域 目录启动 node server.js 后:

访问地址 本地文件 演示点
http://127.0.0.1:8080/page01 01-CORS.html 按钮触发 XHR → /getdata01,服务端返回 CORS 头后 onload 可读到响应
http://127.0.0.1:8080/page02 02-jsonp解决跨域.html 动态插入 <script src=".../getdata02?cb=parseData">,全局函数 parseData 接收数组

01-CORS.html 核心逻辑(与配套一致):

javascript 复制代码
xhr.onload = () => {
    box.innerHTML += xhr.response + '<br>';
};
xhr.onerror = () => {
    console.log('请求失败(未启动服务或缺少 CORS 头)');
};
btn.onclick = () => {
    xhr.open('GET', 'http://127.0.0.1:8080/getdata01');
    xhr.send();
};

【代码注释】

  • 使用绝对 URL 请求接口,便于理解「页面来源」与「接口来源」不一致时的跨域行为。
  • 服务端 res.set('Access-Control-Allow-Origin', '*') 后,浏览器才允许脚本读取 xhr.response
  • 若去掉 CORS 头,请求仍可能 200,但 onload 里读响应会报错,或走 onerror(视浏览器而定)。

02-jsonp解决跨域.html 核心逻辑:

javascript 复制代码
function parseData(data) {
    box.innerHTML = data.map(item =>
        item.name + ':' + item.age + ':' + item.address
    ).join('<br>');
}

btn.onclick = () => {
    const scriptBox = document.createElement('script');
    scriptBox.src = 'http://127.0.0.1:8080/getdata02?cb=parseData';
    document.body.appendChild(scriptBox);
    document.body.removeChild(scriptBox);
};

【代码注释】

  • cb=parseData 必须与全局函数名一致;后端返回 parseData([{name,age,address},...]) 字符串。
  • 插入后立即 removeChild,避免重复点击堆积 script 节点;JSONP 仅 GET,无标准错误回调。
  • 与 §9.3 内联示例同源,配套页用 parseData 命名,便于对照 server.jsreq.query.cb

01-CORS.html 可运行精简版:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>CORS 演示</title></head>
<body>
  <button id="btn">跨域 GET getdata01</button>
  <div id="box"></div>
  <script>
    const xhr = new XMLHttpRequest();
    xhr.onload = () => { box.innerHTML += xhr.response + '<br>'; };
    xhr.onerror = () => { box.textContent = '失败:检查 CORS 头或服务'; };
    document.getElementById('btn').onclick = () => {
      xhr.open('GET', 'http://127.0.0.1:8080/getdata01');
      xhr.send();
    };
    const box = document.getElementById('box');
  </script>
</body>
</html>

【代码注释】

  • 须在 02-跨域 目录 node server.js 且响应带 Access-Control-Allow-Origin
  • file:// 打开本页更能体会「无 CORS 头则读不到 body」。

02-jsonp解决跨域.html 可运行精简版:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>JSONP 演示</title></head>
<body>
  <button id="btn">JSONP 拉数据</button>
  <div id="box"></div>
  <script>
    function parseData(data) {
      box.innerHTML = data.map(i => i.name + ':' + i.age).join('<br>');
    }
    document.getElementById('btn').onclick = () => {
      const s = document.createElement('script');
      s.src = 'http://127.0.0.1:8080/getdata02?cb=parseData';
      document.body.appendChild(s);
      s.remove();
    };
    const box = document.getElementById('box');
  </script>
</body>
</html>

【代码注释】

  • 全局函数名 parseData 必须与 cb 查询参数一致。
  • JSONP 仅 GET;现代项目优先 CORS + Fetch。

9.1 同源策略

同源 要求 Ajax 页面 URL 与请求 URL 的 协议、主机、端口 三者一致。http://localhost:3000 的页面请求 http://127.0.0.1:8080 即跨域,浏览器会拦截读取响应(控制台可见 CORS 错误)。
接口 origin B 浏览器 页面 origin A 接口 origin B 浏览器 页面 origin A #mermaid-svg-h4S93e5DUsb3hNXv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-h4S93e5DUsb3hNXv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-h4S93e5DUsb3hNXv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-h4S93e5DUsb3hNXv .error-icon{fill:#552222;}#mermaid-svg-h4S93e5DUsb3hNXv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-h4S93e5DUsb3hNXv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-h4S93e5DUsb3hNXv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-h4S93e5DUsb3hNXv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-h4S93e5DUsb3hNXv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-h4S93e5DUsb3hNXv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-h4S93e5DUsb3hNXv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-h4S93e5DUsb3hNXv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-h4S93e5DUsb3hNXv .marker.cross{stroke:#333333;}#mermaid-svg-h4S93e5DUsb3hNXv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-h4S93e5DUsb3hNXv p{margin:0;}#mermaid-svg-h4S93e5DUsb3hNXv .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-h4S93e5DUsb3hNXv text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-h4S93e5DUsb3hNXv .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-h4S93e5DUsb3hNXv .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-h4S93e5DUsb3hNXv .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-h4S93e5DUsb3hNXv .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-h4S93e5DUsb3hNXv #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-h4S93e5DUsb3hNXv .sequenceNumber{fill:white;}#mermaid-svg-h4S93e5DUsb3hNXv #sequencenumber{fill:#333;}#mermaid-svg-h4S93e5DUsb3hNXv #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-h4S93e5DUsb3hNXv .messageText{fill:#333;stroke:none;}#mermaid-svg-h4S93e5DUsb3hNXv .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-h4S93e5DUsb3hNXv .labelText,#mermaid-svg-h4S93e5DUsb3hNXv .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-h4S93e5DUsb3hNXv .loopText,#mermaid-svg-h4S93e5DUsb3hNXv .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-h4S93e5DUsb3hNXv .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-h4S93e5DUsb3hNXv .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-h4S93e5DUsb3hNXv .noteText,#mermaid-svg-h4S93e5DUsb3hNXv .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-h4S93e5DUsb3hNXv .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-h4S93e5DUsb3hNXv .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-h4S93e5DUsb3hNXv .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-h4S93e5DUsb3hNXv .actorPopupMenu{position:absolute;}#mermaid-svg-h4S93e5DUsb3hNXv .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-h4S93e5DUsb3hNXv .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-h4S93e5DUsb3hNXv .actor-man circle,#mermaid-svg-h4S93e5DUsb3hNXv line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-h4S93e5DUsb3hNXv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} xhr.open GET origin B 实际会发出请求 200 + 响应体 若无 CORS 头则 JS 读不到 body

【代码注释】(跨域拦截示意)

  • 请求能发出 ,差异在 JS 能否访问 responseText
  • 解决方向:服务端 CORS 响应头,或 JSONP(仅 GET)、或开发代理。

9.2 CORS 跨域资源共享

服务端添加响应头(Express 示例):

javascript 复制代码
// 允许指定来源
res.set('Access-Control-Allow-Origin', 'http://localhost:8080');

// 课堂演示:允许任意来源(生产应限制域名)
res.set('Access-Control-Allow-Origin', '*');

res.send('hello ajax');

【代码注释】(CORS 响应头最小示例)

  • Access-Control-Allow-Origin 声明允许读取响应的源;带 Cookie 时不能为 *
  • 加此头后 01-CORS.html 的跨域 XHR 才能在 onload 中读到正文。
  • 非简单请求还需处理 OPTIONS 预检。

02-跨域/server.js 完整路由(与配套 HTML 文件名一致):

javascript 复制代码
app.get('/page01', (req, res) => {
    res.sendFile(path.join(__dirname, '01-CORS.html'));
});
app.get('/getdata01', (req, res) => {
    res.set('Access-Control-Allow-Origin', '*');
    res.send('hello ajax');
});
app.get('/page02', (req, res) => {
    res.sendFile(path.join(__dirname, '02-jsonp解决跨域.html'));
});
app.get('/getdata02', (req, res) => {
    const users = [{ name: '安妮0', age: 90, address: '上海' } /* ... */];
    const fn = req.query.cb;
    res.send(`${fn}(${JSON.stringify(users)})`);
});

【代码注释】

  • page02 对应 02-jsonp解决跨域.htmlgetdata02cb 须与页面全局函数 parseData 一致。
  • Access-Control-Allow-Origin 只能写一个源,或 *(不能带凭证 Cookie)。
  • 带 Cookie 跨域需 Access-Control-Allow-Credentials: true 且 Origin 不能为 *
  • 预检请求 OPTIONS 在复杂 POST/自定义头时出现,服务端需处理 OPTIONS。

9.3 JSONP 实现思路

<script src="跨域URL?cb=函数名"> 不受同源限制,浏览器把响应当 JS 执行。

前端:

javascript 复制代码
function parseData(data) {
    console.log('JSONP 数据', data);
}

const script = document.createElement('script');
script.src = 'http://127.0.0.1:8080/getdata02?cb=parseData';
document.body.appendChild(script);
script.remove();

【代码注释】

  • cb 查询参数名由前后端约定;后端返回 parseData([{...}]) 形式字符串(与 02-jsonp解决跨域.html 一致)。
  • 插入 script 瞬间发起 GET,收到响应后执行全局函数 parseData
  • 用完 remove 节点,避免 DOM 堆积;仅支持 GET,无法 POST JSON。

后端(节选):

javascript 复制代码
app.get('/getdata02', (req, res) => {
    const users = [{ name: '安妮0', age: 90, address: '上海' }];
    const fn = req.query.cb;
    res.send(`${fn}(${JSON.stringify(users)})`);
});

【代码注释】

  • req.query.cb 取回调名,必须校验为合法标识符,防 XSS(了解即可)。
  • JSONP 缺点:只 GET、错误处理弱、安全性差;现代优先 CORS + fetch。

【面试考点】

Q1:CORS 和 JSONP 区别?

A:CORS 是标准 HTTP 机制,支持各种方法与响应;JSONP 利用 script 标签仅 GET,返回函数调用。追问:为何 JSONP 能跨域?------script 标签加载不受同源读响应限制。

【实战要点】

  • 经典应用场景 :前后端分离(前端 5173 调 API 3000)用 CORS 或 devServer proxy
  • 常见坑 :只加 Access-Control-Allow-Origin: * 仍失败------自定义头或非简单请求需 OPTIONS 预检。
  • 性能与最佳实践:生产白名单具体域名;JSONP 仅遗留场景,新项目用 CORS + Fetch。

【本章小结】

方案 机制 限制
同源策略 协议+域名+端口 浏览器默认
CORS Access-Control-Allow-Origin 需服务端
JSONP ?cb=fn 返回 JS 仅 GET

总结

高频面试题速查

要点
Ajax vs 表单提交 无刷新、XHR 改 DOM
五步流程 创建→事件→open→头→send
GET/POST 传参 URL vs body
responseType json Content-Type + response
上传进度 xhr.upload.onprogress
同步 XHR 阻塞,禁用
CORS vs JSONP 标准头 vs script GET

#mermaid-svg-MpIRbnxRBizVFsjo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MpIRbnxRBizVFsjo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MpIRbnxRBizVFsjo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MpIRbnxRBizVFsjo .error-icon{fill:#552222;}#mermaid-svg-MpIRbnxRBizVFsjo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MpIRbnxRBizVFsjo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MpIRbnxRBizVFsjo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MpIRbnxRBizVFsjo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MpIRbnxRBizVFsjo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MpIRbnxRBizVFsjo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MpIRbnxRBizVFsjo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MpIRbnxRBizVFsjo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MpIRbnxRBizVFsjo .marker.cross{stroke:#333333;}#mermaid-svg-MpIRbnxRBizVFsjo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MpIRbnxRBizVFsjo p{margin:0;}#mermaid-svg-MpIRbnxRBizVFsjo .edge{stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .section--1 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section--1 path,#mermaid-svg-MpIRbnxRBizVFsjo .section--1 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section--1 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section--1 text{fill:#ffffff;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth--1{stroke-width:17;}#mermaid-svg-MpIRbnxRBizVFsjo .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-0 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-0 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-0 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-0 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-0 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-0{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-0{stroke-width:14;}#mermaid-svg-MpIRbnxRBizVFsjo .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-1 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-1 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-1 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-1 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-1 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-1{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-1{stroke-width:11;}#mermaid-svg-MpIRbnxRBizVFsjo .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-2 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-2 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-2 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-2 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-2 text{fill:#ffffff;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-2{stroke-width:8;}#mermaid-svg-MpIRbnxRBizVFsjo .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-3 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-3 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-3 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-3 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-3 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-3{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-3{stroke-width:5;}#mermaid-svg-MpIRbnxRBizVFsjo .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-4 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-4 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-4 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-4 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-4 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-4{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-4{stroke-width:2;}#mermaid-svg-MpIRbnxRBizVFsjo .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-5 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-5 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-5 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-5 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-5 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-5{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-5{stroke-width:-1;}#mermaid-svg-MpIRbnxRBizVFsjo .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-6 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-6 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-6 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-6 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-6 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-6{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-6{stroke-width:-4;}#mermaid-svg-MpIRbnxRBizVFsjo .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-7 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-7 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-7 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-7 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-7 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-7{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-7{stroke-width:-7;}#mermaid-svg-MpIRbnxRBizVFsjo .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-8 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-8 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-8 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-8 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-8 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-8{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-8{stroke-width:-10;}#mermaid-svg-MpIRbnxRBizVFsjo .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-9 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-9 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-9 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-9 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-9 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-9{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-9{stroke-width:-13;}#mermaid-svg-MpIRbnxRBizVFsjo .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-10 rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-10 path,#mermaid-svg-MpIRbnxRBizVFsjo .section-10 circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-10 polygon,#mermaid-svg-MpIRbnxRBizVFsjo .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-10 text{fill:black;}#mermaid-svg-MpIRbnxRBizVFsjo .node-icon-10{font-size:40px;color:black;}#mermaid-svg-MpIRbnxRBizVFsjo .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .edge-depth-10{stroke-width:-16;}#mermaid-svg-MpIRbnxRBizVFsjo .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled,#mermaid-svg-MpIRbnxRBizVFsjo .disabled circle,#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:lightgray;}#mermaid-svg-MpIRbnxRBizVFsjo .disabled text{fill:#efefef;}#mermaid-svg-MpIRbnxRBizVFsjo .section-root rect,#mermaid-svg-MpIRbnxRBizVFsjo .section-root path,#mermaid-svg-MpIRbnxRBizVFsjo .section-root circle,#mermaid-svg-MpIRbnxRBizVFsjo .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-MpIRbnxRBizVFsjo .section-root text{fill:#ffffff;}#mermaid-svg-MpIRbnxRBizVFsjo .section-root span{color:#ffffff;}#mermaid-svg-MpIRbnxRBizVFsjo .section-2 span{color:#ffffff;}#mermaid-svg-MpIRbnxRBizVFsjo .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-MpIRbnxRBizVFsjo .edge{fill:none;}#mermaid-svg-MpIRbnxRBizVFsjo .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-MpIRbnxRBizVFsjo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Ajax 基础
XHR
五步流程
事件
数据
GET查询串
POST JSON
FormData
响应
status
JSON
进阶
超时
进度
跨域
CORS
JSONP

【代码注释】(回顾图)

  • 先掌握 XHR 五步,再扩展请求体类型与响应解析,最后处理跨域。
  • 后续 Fetch / Axios 在本章之上封装 Promise,原理仍属 HTTP。

本文详细介绍了 Ajax 基础技术的核心内容,包括:

  1. Ajax 技术概述:理解 Ajax 的基本概念和应用场景
  2. XMLHttpRequest 对象:掌握 XHR 对象的属性和方法
  3. Ajax 基本流程:学习标准的 Ajax 请求处理流程
  4. 发送请求数据:掌握 GET、POST 请求的数据发送方式
  5. 响应数据处理:学习各种格式响应的解析方法
  6. 进度事件监控:实现文件上传下载的进度显示
  7. 同步异步控制:理解并正确使用同步和异步请求
  8. 实战应用案例:通过实际项目巩固 Ajax 基础知识
  9. 跨域:同源策略、CORS 响应头、JSONP 回调

Ajax 是现代 Web 开发的重要技术。建议按 §0.4 顺序跑通:01~08 原生 Ajax02-跨域 CORS/JSONP ,再进入 ajax 封装、记账本 REST、Fetch/Axios 等进阶主题。


相关资源:

相关推荐
怕浪猫1 小时前
Electron 开发实战(十四):实战项目|从零搭建轻量化桌面代码编辑器
前端·electron·node.js
放下华子我只抽RuiKe51 小时前
FastAPI 全栈后端(七):测试与自动化
运维·前端·人工智能·react.js·前端框架·自动化·fastapi
晓13131 小时前
【Cocos Creator 3.x】篇——第五章 项目实战优化技术
前端·javascript·游戏引擎
有梦想的程序星空1 小时前
【环境配置】使用 Vue CLI 构建 Vue 项目脚手架完整指南
前端·javascript·vue.js
之歆1 小时前
Ajax 进阶:跨域、CORS、JSONP 与请求封装实战
前端·javascript·ajax
杨先生哦1 小时前
【2026 热端攻防系列 2/12】DOM 型 XSS 深度实战:AI 多态变形免杀 + 全维度防御
前端·人工智能·笔记·安全·web安全·xss
sugar__salt1 小时前
前端Ajax核心原理与实战:从异步机制到接口请求全解析
前端·javascript·ajax
问心无愧05131 小时前
ctf show web入门115
android·前端·笔记
難釋懷1 小时前
Nginx缓冲区
前端·javascript·nginx