从 XMLHttpRequest 五步流程 到 GET/POST 请求体 、FormData 上传 、JSON 响应 、超时与进度 ,并衔接 CORS / JSONP 跨域。配套
server.js可本地跑通全部 HTML 案例。
目录
- 零、导读与学习价值
- [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~08 与 02-跨域 练习顺序(§0.4)。
- 全部基于 HTTP,为后续 Promise、Fetch 打底。
0.1 案例覆盖清单
| 页面 / 服务 | 知识点 | 本文章节 |
|---|---|---|
| 01-ajax基本流程.html | XHR 创建、open、send、onload |
§3 |
| 02-发送请求携带数据.html | GET 查询串、POST urlencoded、PUT | §4 |
| 03-FormData.html | 文件上传、multipart/form-data |
§4.3 |
| 04-解析响应报文.html | status、getResponseHeader、responseText |
§5.1 |
| 05-响应json数据.html | JSON.parse / responseType:'json' |
§5.2 |
| 06-响应超时.html | xhr.timeout、ontimeout |
§6.2 |
| 07-进度事件.html | onprogress、xhr.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) | express、body-parser、multer(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.html 、02-jsonp解决跨域.html 源码联调。
依赖安装(package.json):
| 目录 | 主要依赖 | 用途 |
|---|---|---|
| 01-原生Ajax | express、body-parser、multer |
静态页路由、解析 POST/PUT 体、03-FormData 上传 |
| 02-跨域 | express |
CORS / JSONP 演示路由 |
两个目录均需 npm install 后再 node server.js。
0.4 建议练习路线
| 顺序 | 配套 | 端口 | 验证点 |
|---|---|---|---|
| ① | 01 → 01-ajax基本流程.html |
8080 | /page01、/getData、五步流程 |
| ② | 01 → 02~04 |
8080 | 传参、FormData、响应报文 |
| ③ | 01 → 05-响应json数据.html |
8080 | /getUsers + responseType:'json' |
| ④ | 01 → 06 / 07 |
8080 | /getInfo 超时、/getMoreData 进度 |
| ⑤ | 01 → 08-同步和异步.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)、text、arraybuffer(二进制)、blob等。- 图片二进制可转
Blob+URL.createObjectURL显示。 - 日常接口 90% 为
json或默认text。 - 出错时先看控制台与 Network 面板中的状态码、响应体与报错信息。
【本章小结】
| 要点 | 内容 |
|---|---|
| 状态 | readyState 0→4 |
| 属性 | status、responseType、upload |
【实战要点】
- 经典应用场景:jQuery.ajax、早期库均基于 XHR 属性与事件。
- 常见坑 :
readyState===4但status为 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 四次变化;
4时status才稳定可读。 - 教学用;项目里更推荐只绑
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/onerror→open(method, url)→(可选)setRequestHeader→send(body)。 - GET 请求
send()无参或null;POST 在send传入请求体字符串或FormData。 - 对应页面 01-ajax基本流程.html ,服务路由
GET /getData(需先node server.js)。 onload在readyState===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 推荐事件。loadstart→load/error→loadend顺序与进度事件配合使用。- 返回 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.parse或responseType:'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.parse或responseType:'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.parse或responseType:'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-data;multer在服务端解析字段名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>
【代码注释】
- 练习
status、getResponseHeader、responseText三者关系。 - 对应 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/onerror→open(method, url)→(可选)setRequestHeader→send(body)。 - GET 请求
send()无参或null;POST 在send传入请求体字符串或FormData。 - 对应页面 01-ajax基本流程.html ,服务路由
GET /getData(需先node server.js)。 onload在readyState===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前设置;onload里xhr.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'后不要用responseText再parse,否则可能得到null。 - 服务端
Content-Type: application/json与responseType配合,浏览器自动解析为数组。 - 联调路径:先
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)、text、arraybuffer(二进制)、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-解析响应报文.html 、05-响应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 / 字段 |
|---|---|
| 报文 | status、getResponseHeader |
| 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.onprogress;event.loaded/event.total计算百分比。 - 对应 07-进度事件.html ;
loadstart/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秒超时
【代码注释】
- 兼容方案:
send后setTimeout,若readyState!==4则xhr.abort()。 - 了解即可;优先使用 XHR2 的
xhr.timeout+ontimeout(06-响应超时.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.timeout与ontimeout配合,避免大文件长时间无响应。- 按钮
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 秒响应,多试几次观察
ontimeout与onload分支。 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 |
| 超时 | timeout、ontimeout + /getInfo |
【实战要点】
- 经典应用场景:文件上传进度条、慢接口 5s 超时提示。
- 常见坑 :上传进度误绑在
xhr而非xhr.upload;timeout写在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.js中req.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解决跨域.html ;getdata02的cb须与页面全局函数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 基础技术的核心内容,包括:
- Ajax 技术概述:理解 Ajax 的基本概念和应用场景
- XMLHttpRequest 对象:掌握 XHR 对象的属性和方法
- Ajax 基本流程:学习标准的 Ajax 请求处理流程
- 发送请求数据:掌握 GET、POST 请求的数据发送方式
- 响应数据处理:学习各种格式响应的解析方法
- 进度事件监控:实现文件上传下载的进度显示
- 同步异步控制:理解并正确使用同步和异步请求
- 实战应用案例:通过实际项目巩固 Ajax 基础知识
- 跨域:同源策略、CORS 响应头、JSONP 回调
Ajax 是现代 Web 开发的重要技术。建议按 §0.4 顺序跑通:01~08 原生 Ajax → 02-跨域 CORS/JSONP ,再进入 ajax 封装、记账本 REST、Fetch/Axios 等进阶主题。
相关资源: