Skip to content

XMLHttpRequest 基本使用 ​

本页对应 1-7XMLHttpRequest基本使用和案例,共 5 段视频,约 46 分钟。

学习目标 ​

  • 能独立使用 XMLHttpRequest 完成“实例化对象、open 设置请求方法与 URL、监听 loadend 事件、send 发起请求”四步,并从 response 获取服务器返回数据。
  • 能识别 XMLHttpRequest 返回的 JSON 字符串,并使用 JSON.parse 转换为 JavaScript 对象后再使用。
  • 能按 URL 查询参数格式手动拼接 ?key=value&key=value,并将查询参数正确附加到请求地址中。
  • 能使用 URLSearchParams 将 JavaScript 对象转换为查询参数字符串,并说明中文字符被编码不影响服务器解析。
  • 能在地区查询案例中获取输入框值、调用接口、解析返回数据,并使用 map 与 join 生成 HTML 结构后渲染到页面。
  • 能使用 XMLHttpRequest 提交 JSON 数据,设置 Content-Type: application/json,并通过 JSON.stringify 将对象转换为 JSON 后放入 send 方法发送。

知识点 ​

AJAX 与 XHR:为什么要学原生对象 ​

为什么需要:AJAX 是浏览器与服务器通信的技术,平时用的 axios 是对 XHR 相关代码的封装。学会 XHR,既能理解 axios 内部如何与服务器交互,也能在没有 axios 时直接完成通信。

怎么想:AJAX 原理不是某个框架,而是浏览器提供的 window.XMLHttpRequest 对象。先创建 XHR 对象,再按固定步骤发请求、收结果。

怎么做:用构造函数创建对象:

js
const xhr = new XMLHttpRequest()

易错点:构造函数名是 XMLHttpRequest,拼写较长,写错时编辑器可能没有提示;不要把 XHR 和 axios 混为一谈,axios 只是封装。

XHR 四步法:语法与思路 ​

为什么需要:XHR 的用法步骤固定,记住四步就能套用到不同接口。

怎么想:创建对象 → 调用 open 设置请求方法和 URL → 监听 loadend 事件接收结果 → 调用 send 发起请求。请求方法和 URL 要从接口文档获取,不能乱写。

XHR基础语法示意图

怎么做:

js
const xhr = new XMLHttpRequest()
xhr.open('GET', '接口文档中的省份列表URL')
xhr.addEventListener('loadend', () => {
  console.log(xhr.response)
})
xhr.send()

易错点:open 只是设置方法和地址,真正发请求是 send;事件名是 loadend,不是 load end;结果从 xhr.response 取,不是随便定义的变量。

需求实战:获取省份列表并展示 ​

为什么需要:只看语法容易没感觉,用“获取所有省份列表并展示到页面上”验证四步法。

怎么想:结合需求,只需要改三处:请求方法、请求地址、拿到数据后的使用逻辑。其余创建对象、监听事件、发送请求的骨架不变。

怎么做:先输出 xhr.response 确认数据。转写稿中的调试过程是:网络面板看到请求发出,预览里 list 后面是省份数组;控制台也输出了结果。但此时 response 是 JSON 字符串,不是对象,所以要用 JSON.parse 转换,再取 list 渲染:

js
xhr.addEventListener('loadend', () => {
  const data = JSON.parse(xhr.response)
  // 选择器以页面中用于展示的 p 标签为准
  document.querySelector('p').innerText = data.list
})

易错点:原生 XHR 不会自动把 JSON 转成对象,必须自己 JSON.parse;渲染纯文本用 innerText,转写稿中明确这里不考虑解析 HTML 标签;取数据时注意属性是 list。

调试与验证:从网络面板到控制台 ​

为什么需要:确认请求是否发出、服务器是否返回、页面是否渲染,不能只看代码“觉得对”。

怎么想:先看网络面板的请求和预览,再看控制台输出,最后看页面展示。转写稿中的顺序是:保存 → 切到浏览器 → 网络面板确认请求 → 预览看到“获取省份成功”和 list 数组 → 控制台看到输出 → 页面出现省份。

怎么做:若控制台输出的是 JSON 字符串,先 JSON.parse 再输出对象;确认对象里有 list 后,再写入页面。若页面没变化,检查 loadend 回调是否执行、选择器是否选到元素、innerText 是否赋值。

易错点:忘记 send 请求不会发出;JSON.parse 漏写会得到字符串;选择器写错或类名没加会导致渲染失败;不要把 response 当成已经转换好的对象。

小结与易错点清单 ​

XHR 基础使用就四步:创建对象、open 设置方法和 URL、监听 loadend 接收并使用数据、send 发请求。实战中额外注意:xhr.response 是字符串时要 JSON.parse;渲染文本用 innerText;请求方法和地址以接口文档为准。掌握这些,再看 axios 的封装会更容易理解。

先分清:XHR 和查询参数各解决什么问题 ​

为什么需要:查询参数的作用,是浏览器把额外信息带给服务器,服务器据此返回匹配的数据。之前用 axios 时,我们传一个对象,axios 会自动把它拼成查询参数;现在改用原生 XMLHttpRequest(转写稿里口误成“小黄人对象”),没有这层封装,必须自己拼。

怎么想:查询参数放在 URL 上,格式是 url?参数名=值&参数名=值。而 XHR 设置 URL 的地方,就是 open 方法的第二个参数,所以拼接点就在 open 里。

怎么做:先照接口文档确认请求方法、地址和参数名。本节查城市列表,参数名是 pname,表示要查哪个省的城市。示例沿用转写稿演示的湖北省;换成河北省,只需改参数值。

易错点:? 只能出现一次,多个参数之间用 &;参数名不能自己起,必须照文档;不要把参数拼到 open 的第一个参数里。

四步骨架:在 open 的 URL 后拼接查询参数 ​

为什么需要:XHR 的核心用法是固定四步,查询参数只影响第二步的 URL,其他步骤不变。先把骨架写对,再谈拼参数。

怎么想:第一步实例化;第二步 open 设置方法和 URL;第三步监听 loadend;第四步 send 发请求。GET 请求没有请求体,所以 send() 里不传东西。

怎么做:

js
const xhr = new XMLHttpRequest()
xhr.open('GET', '接口地址?pname=湖北省')
xhr.addEventListener('loadend', () => {
  console.log(xhr.response)
})
xhr.send()

易错点:事件名是 loadend,不是 loadend 拼错;response 拿到的先是字符串;URL 末尾不要多写空格;open 的方法名建议写大写 GET,虽然大小写不敏感,但统一更稳。

调试:先确认参数真的到了服务器 ​

为什么需要:查询参数拼错时,代码不一定报错,但服务器返回的数据会不对,所以必须看网络请求。

怎么想:打开开发者工具 Network,点开请求,先看请求 URL 或查询字符串里有没有 pname=湖北省,再看 Preview 里的 message 和 list。

怎么做:保存代码后刷新页面,在 Network 中找到请求;确认查询参数已提交;Preview 中看到“获取城市成功”,list 后面是城市数组,说明参数传递成功。

易错点:不要只看控制台输出,控制台只能证明回调执行了;要确认请求 URL 里的参数名和值;如果参数名写成 pName、pname 不一致,服务器可能查不到。

拿到的是 JSON 字符串:先 parse 再渲染 ​

为什么需要:原生 XHR 不会像 axios 那样自动把响应转成对象。转写稿中 xhr.response 打印出来是 JSON 字符串,直接当对象用会不方便。

怎么想:先用 JSON.parse(xhr.response) 转成 JS 对象,再取 data.list,最后渲染到页面上的 P 标签。

怎么做:

js
xhr.addEventListener('loadend', () => {
  const data = JSON.parse(xhr.response)
  document.querySelector('.city').innerText = data.list
})

易错点:JSON.parse 的括号别漏;确认接口返回的确实是 JSON;innerText 和 innerHTML 不要混;示例直接把数组赋给 innerText,浏览器会转成字符串,若要逐项生成标签,需要后续用 map 和 join。

小结检查清单 ​

  • XHR 携带查询参数:在 open 的 URL 后按 ?参数名=值&参数名=值 拼接。
  • 参数名和值照接口文档写,多个参数用 &。
  • 先用 Network 确认参数到达服务器,再处理响应。
  • 原生 XHR 的 response 是字符串,使用前记得 JSON.parse。
  • 查询参数改变的是“查什么”,四步骨架不变。

本节目标:使用 XHR 完成案例地区查询。页面中输入省份和城市名字,点击查询后,把多对查询参数传给服务器,拿到地区列表并渲染。效果如下:

地区查询页面效果

需求拆解:为什么不用 axios,而用 XHR 再做一遍 ​

为什么需要:前面用 axios 做地区查询时,axios 会自动帮我们把参数对象转成查询字符串,也会自动把响应 JSON 转成对象。现在改用原生 XHR,就要自己处理这些细节,才能理解 axios 内部大致做了什么。

怎么想:先按 XHR 四步走:创建对象、open 设置方法和地址、监听 loadend、send 发请求。案例额外多一步:点击查询时先生成查询参数,再拼到 URL 后面。

怎么做:先取输入框的值,生成查询参数,再调用接口。调试时先打印省份和城市值,确认取到的是 北京、北京市,再去看网络面板里的查询参数。

易错点:XMLHttpRequest 单词不要写错;事件名是 loadend,不是 load end;send() 不要漏;open 的 URL 里要先有 ? 再拼参数。

生成查询参数:URLSearchParams 的作用 ​

为什么需要:本案例有 pname、cname 两对参数,手写 pname=北京&cname=北京市 容易漏 &、漏等号。URLSearchParams 可以把参数对象快速转成“参数名=值&参数名=值”的字符串。

URLSearchParams语法

怎么想:参数名要按接口文档来,本案例是 pname 和 cname;值来自两个输入框。先把它们放进对象,再交给 URLSearchParams,最后调用 toString()。

怎么做:核心代码是:

js
const pname = provinceEl.value;
const cname = cityEl.value;

const params = new URLSearchParams({ pname, cname });
const query = params.toString();
console.log(query);

调试时先打印 query,会看到中文被编码,例如 pname=%E5%8C%97%E4%BA%AC...。这不是错误,服务器可以正常解析,不需要额外处理。

易错点:URLSearchParams 接收的是对象,不是随便一个字符串;参数名不能写错;不要忘记调用 toString() 拿字符串结果。

调用接口与渲染数据:XHR 的完整流程 ​

为什么需要:查询参数生成后,要拼到接口地址后面,再用 XHR 发 GET 请求。服务器返回的是 JSON 字符串,不能直接当对象用。

怎么想:在点击事件里完成三件事:生成 query、用 XHR 请求 接口地址?${query}、在 loadend 回调里解析并渲染。转写稿中的调试顺序是:先看网络面板的“载荷/查询参数”,再看“预览”确认 list 数组,最后在控制台看 xhr.response,发现它是 JSON 字符串,所以用 JSON.parse 转成对象。

怎么做:示例核心代码:

js
const provinceEl = document.querySelector('.province');
const cityEl = document.querySelector('.city');
const btn = document.querySelector('.sel-btn');
const listEl = document.querySelector('.area-list'); // 按实际页面列表父元素替换

btn.addEventListener('click', () => {
  const pname = provinceEl.value;
  const cname = cityEl.value;

  const params = new URLSearchParams({ pname, cname });
  const query = params.toString();

  const xhr = new XMLHttpRequest();
  xhr.open('GET', `接口地址?${query}`);

  xhr.addEventListener('loadend', () => {
    const data = JSON.parse(xhr.response);
    const html = data.list.map(item => `<li>${item}</li>`).join('');
    listEl.innerHTML = html;
  });

  xhr.send();
});

渲染时,data.list 是地区数组,用 map 生成多个 <li>,再用 join('') 拼成一个字符串,最后通过 innerHTML 放到列表父元素中。转写稿里还验证了换城市查询,例如北京查北京市、湖北查武汉市、广东查广州市,列表都能随查询结果变化。

易错点:xhr.response 是字符串,直接 data.list 会拿不到,要先 JSON.parse;map 返回数组,必须 join('') 再赋给 innerHTML;URL 拼接别漏 ?;open 里的方法一般用 GET;渲染容器选择器要按实际页面写,不要选错父元素。

小结:JS 对象如何转成查询参数字符串 ​

笔记小结的答案是:在调用 open 方法时,在 URL 的 ? 后面按照“参数名=值&参数名=值”的格式拼接。更推荐的做法是使用 URLSearchParams:先传参数对象,再调用 toString()。这样既减少手写拼接错误,也方便处理中文编码。整个案例的重点就是:先取输入值生成查询参数,再用 XHR 发请求,最后把 JSON 字符串转成对象并渲染列表。

目标与场景:用原生 XHR 完成注册提交 ​

为什么需要:前面调接口多用 axios,它帮我们处理了请求头、对象转 JSON 等细节。本节要回到浏览器内置的 XMLHttpRequest(转写稿里口述为“小黄人对象”),在没有 axios 帮忙的情况下,自己完成“提交用户名和密码到服务器”的注册功能。

怎么想:注册接口不是“只要发请求”就行。接口文档里写明:请求方法是 POST,参数位置是 body,数据格式是 application/json。因此除了实例化、open、监听 loadend、send 之外,还要额外做两件事:设置请求头、准备符合要求的请求体。

怎么做:先明确本节的核心差异:原生 XHR 需要自己设置 Content-Type: application/json,告诉服务器“我发过去的内容类型是 JSON 字符串”;同时要自己把 JS 对象转成 JSON 字符串。

易错点:不要以为所有接口都要提交数据;只有文档要求 body 参数时才需要本节这两步额外操作。请求方法、地址、字段名都要看接口文档,不能随手写。

为什么数据提交要多两步 ​

为什么需要:使用 axios 时,Content-Type 和对象转 JSON 往往不用我们额外操心;现在用原生 XHR,浏览器不会替我们完成这些“业务格式约定”。

怎么想:接口文档中的 application/json 有两层含义:一是请求头要声明数据格式;二是请求体里真正携带的数据也应是 JSON 字符串。两者要一致。

怎么做:调用 xhr.setRequestHeader('Content-Type', 'application/json') 设置请求头;再用 JSON.stringify(obj) 把 JS 对象转成 JSON 字符串,最后交给 send。

易错点:只设置了请求头,却 send 了不符合要求的数据;或者只转了 JSON,却忘记设置请求头。两者缺一不可。

怎么做:五步代码与请求体位置 ​

为什么需要:原生 XHR 提交数据的流程比普通 GET 多了“设置请求头”和“在 send 中携带请求体”两步。

怎么想:整体仍是五步:实例化对象 → 设置请求方法和地址 → 监听 loadend 接收并使用数据 → 设置请求头 → send 发送请求体。笔记也强调:原生 XHR 需要在 send 方法调用时传入请求体。

在 send 方法中携带请求体数据

怎么做:按接口文档复制注册接口的方法和地址;字段名按文档写。推荐先写 JS 对象,再 JSON.stringify,因为手写 JSON 容易多写或漏写符号。

js
const xhr = new XMLHttpRequest();
xhr.open('POST', '接口文档中的注册地址');

xhr.addEventListener('loadend', () => {
  console.log(xhr.response);
});

// 告诉服务器:请求体是 JSON 字符串
xhr.setRequestHeader('Content-Type', 'application/json');

// 推荐:先写对象,再转 JSON 字符串
const obj = {
  username: 'IT黑马999',
  password: '123456'
};
const data = JSON.stringify(obj);

// 请求体在 send 中携带
xhr.send(data);

易错点:send 里不要随手写一个不符合接口要求的内容;字段名要照文档,例如用户名、密码字段不能自己改名。JSON.stringify 的方法名要写对,转完后再放入 send。

调试过程:看标头、载荷和预览 ​

为什么需要:提交代码写完不代表成功,要到浏览器网络面板确认请求头、请求体和服务器返回。

怎么想:先看请求是否发出;再看请求头里的 Content-Type 是否为 application/json;再看载荷(Payload)里的原始数据是否为 JSON;最后看预览中的返回结果。

怎么做:点击注册后,在网络面板找到请求。查看“标头”中的请求头,确认 Content-Type: application/json;查看“载荷”,必要时点“查看源代码”,确认请求体是 JSON 字符串;查看“预览”,第一次应提示注册成功。再次点击同一账号,转写稿中服务器返回“账号被占用”,说明接口确实收到并处理了提交数据。

易错点:只看控制台输出,不看网络面板;把“载荷”里浏览器格式化后的对象当成原始请求体。要确认原始数据格式与设置一致。

小结与易错点 ​

为什么需要:本节的目标是通过 XHR 提交用户名和密码,完成注册功能。

怎么想:XHR 如何提交请求体数据?在 send 中携带请求体数据,并按照后端要求的内容类型携带。没有 axios 时,请求头要自己设,JS 对象要自己转 JSON 字符串。

怎么做:记住五步:实例化、open、监听 loadend、setRequestHeader、send。接收返回数据时仍可用 xhr.response;若后续要操作数据,按前节方式用 JSON.parse 转成 JS 对象。

易错点:忘记 setRequestHeader;Content-Type 值写错;send 中传错格式;字段名不按文档;重复注册时看到“账号被占用”就以为代码错了,其实要结合接口业务提示判断。

课件图示 ​

课件第6页:XMLHttpRequest查询参数 语法: 用& 符号分隔的键/值对列表 服务

课件第10页:核心步骤: 1. 请求头设置Content-Type 2. 请求体携带符合要求的

课件第3页:XMLHttpRequest基本使用 定义:XMLHttpRequest(XHR

课件第4页:试一试 需求:通过XMLHttpRequest 获取省份数据,渲染到页面上 //

课件第15页:定义: Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。

代码示例 ​

示例 1:XHR 基本使用 —— 获取省份列表并渲染 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>XHR 基本使用</title>
</head>
<body>
  <p class="province"></p>

  <script>
    // 1. 创建 XHR 对象(构造函数有提示,注意单词别写错)
    const xhr = new XMLHttpRequest()

    // 2. 设置请求方法与 URL(方法、地址以接口文档为准)
    xhr.open('GET', '/api/province')

    // 3. 监听 loadend 事件,接收并使用数据
    xhr.addEventListener('loadend', () => {
      // 原生 XHR 不会自动把 JSON 转成对象,必须自己转换
      const data = JSON.parse(xhr.response)
      // data.list 是省份数组,直接渲染成文本
      document.querySelector('.province').innerText = data.list
    })

    // 4. 发起请求
    xhr.send()
  </script>
</body>
</html>

关键行 / 易错点

  • new XMLHttpRequest() 是浏览器内置构造函数,XMLHttpRequest 大小写必须完全一致。
  • 四步顺序固定:创建对象 → open 设置方法与地址 → 监听 loadend → send 发请求,send 必须在最后。
  • 事件名是 loadend(不是 load/loaded),请求结束(成功或失败)都会触发。
  • xhr.response 拿到的是字符串,axios 会自动转换,原生 XHR 不会 —— 一定要 JSON.parse 后再用,否则 data.list 是 undefined。
  • open 的两个参数必须照接口文档写,不能自己编。

示例 2:查询参数 + URLSearchParams(地区查询) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>地区查询</title>
</head>
<body>
  <input type="text" class="province" value="北京市" />
  <input type="text" class="city" value="北京市" />
  <button class="sel-btn">查询</button>
  <ul class="list"></ul>

  <script>
    document.querySelector('.sel-btn').addEventListener('click', () => {
      // 1.1 获取输入框的值
      const pname = document.querySelector('.province').value
      const cname = document.querySelector('.city').value

      // 1.2 用 URLSearchParams 快速生成查询参数
      // 中文会被编码,但不影响服务器解析,无需额外处理
      const params = new URLSearchParams({ pname, cname })
      const query = params.toString() // pname=xxx&cname=xxx

      // 2. 调用接口:查询参数拼在 url 后面,用 ? 分隔
      const xhr = new XMLHttpRequest()
      xhr.open('GET', `/api/area?${query}`)

      xhr.addEventListener('loadend', () => {
        const data = JSON.parse(xhr.response)
        // 3. 渲染数据:map 生成多个 li,再用 join('') 拼成一整段
        const html = data.list.map((item) => `<li>${item}</li>`).join('')
        document.querySelector('.list').innerHTML = html
      })

      xhr.send()
    })
  </script>
</body>
</html>

关键行 / 易错点

  • 查询参数格式:url?参数名=值&参数名=值,问号只写一次,多个参数之间用 & 连接。
  • 原生 XHR 没有 axios 帮你把对象转查询串,这里用 new URLSearchParams(对象) + .toString() 生成;对象的键名必须和接口文档的参数名一致(如 pname、cname)。
  • URLSearchParams 会把中文编码成一串 %E5%8C%97...,这是正常现象,不要手动解码。
  • 拼 URL 时容易漏写 ?,或把模板字符串的反引号写成单引号,导致参数没有拼上。
  • map 之后必须 join(''),直接渲染数组会带逗号。
  • 用 innerHTML 才能解析标签;只想渲染纯文本时用 innerText。

示例 3:XHR 数据提交(注册,设置请求头 + JSON 字符串) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>注册(XHR 提交数据)</title>
</head>
<body>
  <button class="btn">注册</button>

  <script>
    document.querySelector('.btn').addEventListener('click', () => {
      // 1. 创建 XHR 对象
      const xhr = new XMLHttpRequest()

      // 2. 设置请求方法与地址(以接口文档为准)
      xhr.open('POST', '/api/register')

      // 3. 监听事件,接收结果
      xhr.addEventListener('loadend', () => {
        console.log(xhr.response)
      })

      // 4. 设置请求头:告诉服务器请求体是 JSON 格式
      xhr.setRequestHeader('Content-Type', 'application/json')

      // 5. 请求体:推荐先写 JS 对象,再用 JSON.stringify 转成 JSON 字符串
      const data = JSON.stringify({
        username: 'itheima888',
        password: '123456'
      })
      xhr.send(data)
    })
  </script>
</body>
</html>

关键行 / 易错点

  • 相比 GET,提交数据多了两步:setRequestHeader 设置请求头、send(请求体) 携带数据。
  • setRequestHeader('Content-Type', 'application/json') 必须在 open 之后、send 之前调用,否则不生效。
  • 请求体的格式要和 Content-Type 一致:声明了 application/json,send 里就只能是 JSON 字符串。
  • 绝对不要 xhr.send({ username: 'xx' }) 直接传对象 —— 会被转成 [object Object],后端解析失败。
  • 推荐 JSON.stringify(对象),而不是手写 JSON 字符串(容易多逗号、漏引号)。
  • 请求体里的字段名(username、password)必须照接口文档写,不能随手起名。

常见错误 ​

1. 拿到 xhr.response 直接当对象用

  • 现象:xhr.response.list 是 undefined,控制台打印出来是一大串字符串。
  • 原因:responseType 默认为空,原生 XHR 不会像 axios 那样自动 JSON.parse,返回的是原始文本。
  • 修法:const data = JSON.parse(xhr.response) 之后再取属性;或在 open 之后设置 xhr.responseType = 'json',让浏览器帮你转换(此时 response 直接是对象,就不能再 JSON.parse 了,二选一,别叠加)。

2. 监听事件名写成 onloadend / onload

  • 现象:请求在网络面板里明明发出去了,回调函数一次都不执行。
  • 原因:addEventListener 接收的是事件名,不带 on;带 on 的是给 xhr.onloadend = fn 这种赋值写法用的。
  • 修法:xhr.addEventListener('loadend', fn),或统一改用 xhr.onloadend = fn,两种风格不要混着记。

3. setRequestHeader 位置放错

  • 现象:浏览器抛 InvalidStateError: Failed to execute 'setRequestHeader'。
  • 原因:请求头只能在 open() 之后、send() 之前设置,此时状态才是 OPENED。
  • 修法:严格按 new → open → setRequestHeader → send 的顺序写;同理,responseType 也必须在 send 之前设置。

4. POST 提交后端收不到数据 / 报参数为空

  • 现象:接口返回 400 或“用户名不能为空”,但 send 里明明传了东西。
  • 原因:一是没设 Content-Type: application/json,XHR 默认按 text/plain 发,后端按 JSON 解析失败;二是直接把 JS 对象塞进 send,实际发出去的是 [object Object]。
  • 修法:xhr.setRequestHeader('Content-Type', 'application/json') + xhr.send(JSON.stringify(obj)),两步缺一不可。

5. 手拼查询参数漏了 ? 或 &

  • 现象:后端收到的参数名变成 pname 值变成 北京cname=北京市,或直接 404。
  • 原因:url?参数名=值&参数名=值 的分隔符是硬性格式,少一个符号整串就错位;中文和特殊字符还需要编码。
  • 修法:参数超过一个就别手拼,用 new URLSearchParams({ pname, cname }).toString() 一次生成;注意它产出的中文是 %E5%8C%97%E4%BA%AC 形态,这是正常的,服务端会解码。

6. 只写了四步的前三步,忘了 send()

  • 现象:代码不报错,回调不触发,网络面板里没有任何请求。
  • 原因:open 只是配置请求,send 才真正把请求发出去。
  • 修法:把“send 是必写的一步”当成肌肉记忆;调试时先看网络面板有没有请求,没有就是没 send。

练习 ​

练习 1|最小闭环 用原生 XHR 请求省份列表接口,把结果渲染到页面 <ul> 中。要求:用 loadend 监听、显式 JSON.parse、渲染前判断 xhr.status === 200,失败时在页面提示“加载失败”。

练习 2|参数生成对比 给定 { pname: '湖北省', cname: '武汉市' }: ① 手写拼接出完整查询字符串; ② 用 URLSearchParams 生成并 console.log 对比两者差异; ③ 说明为什么 ② 打印出来的中文“看起来不对”却仍然能用。

练习 3|注册提交 用 XHR 完成注册接口调用,写出完整的 5 步代码。要求:请求头正确、请求体由对象经 JSON.stringify 转换、回调中根据返回的 message 字段分别提示“注册成功”和“账号被占用”。

练习 4|找错 下面代码有 3 处问题,指出并改正:

js
const xhr = new XMLHttpRequest()
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.open('post', '/api/register')
xhr.addEventListener('onloadend', () => {
  const data = xhr.response
  console.log(data.message)
})
xhr.send({ username: 'itheima', password: '123456' })

练习 5|封装 写一个 request({ method, url, data }) 函数,内部用 XHR 实现,返回 Promise。要求:GET 时用 URLSearchParams 拼参数、POST 时自动设 Content-Type 并 JSON.stringify、状态码非 2xx 时 reject。写完后回答:这个函数替你省掉了哪几件原本要手写的事?

原视频对照 ​

#视频时长
1XMLHttpRequest-基本使用09:04
2XMLHttpRequest-查询参数07:16
3案例-地区查询-生成查询参数09:29
4案例-地区查询-调用接口及数据渲染08:06
5XMLHttpRequest-数据提交12:36

小结 ​

  1. 原生 XHR 与 axios 的差距集中在三件事:数据格式转换、请求头设置、参数序列化——这三件事 axios 全包了,XHR 全得你自己来。
  2. response 永远是“服务器说了算的原始文本”,除非你主动声明 responseType,否则 JSON.parse 是不可省略的一步。
  3. open 是配置、send 是执行,两者之间是设置请求头和 responseType 的唯一合法窗口,顺序错了就是 InvalidStateError。
  4. 参数一旦超过一个,URLSearchParams 就是默认选择;手拼字符串只在演示原理时有价值,实际写就是给自己埋 bug。
  5. 中文被编码成 %XX 不是错误,是 URL 规范;判断“参数对不对”要看网络面板的“载荷/查询”解析结果,而不是看源码里的字符串长相。
  6. 提交 JSON 是“请求头 + 请求体”一对配套动作,只做一半后端必然解析失败;请求体优先“先写对象再 JSON.stringify”,比手写 JSON 字符串更不容易漏逗号引号。
  7. 四步/五步流程重复写三遍以上就该封装成 request 函数,用 Promise 包一层,后续调用体验才接近 axios。
  8. 排错顺序固定为:网络面板有没有请求 → 请求头/载荷对不对 → 回调有没有进 → 数据要不要转换,按这个链路走,90% 的 XHR 问题能一次定位。