
1. 先搞清楚我们到底在解决什么问题1.1 没有AJAX的年代同步提交的痛苦我刚入行那会儿前端做“登录”这件事非常笨拙用户填完表单点一下提交浏览器把整个页面打包发给服务器服务器处理完再返回一个新的HTML页面浏览器整个刷新。刷新期间页面一片空白你等多久全靠服务器心情。更麻烦的是如果用户填的密码错了服务器返回的往往是全新页面刚才输入的用户名、勾选状态全没了所有操作重来一遍。这种模式叫同步表单提交本质上是“让浏览器离开当前页面再换一页”。它的问题不只是体验差而是它把“数据交互”和“页面跳转”绑死了。页面加载的HTML、CSS、图片、脚本全部要重新下载哪怕这次只是要验证一下用户名是否被占用。那时候我就在想能不能让浏览器悄悄地向服务器要数据拿到结果之后只更新页面上某一个小区域页面本身不动这就是AJAX存在的价值。1.2 AJAX到底是什么以及它解决的核心问题AJAX全称是 Asynchronous JavaScript And XML即“异步的JavaScript与XML”。注意这个名字是2005年那会儿定的核心在于“A”这个异步XML只是当时服务器返回数据的主流格式。今天大家极少再用XML几乎全都返回JSON所以更准确的说法是“用JavaScript在页面不刷新的情况下与服务器交换数据并局部更新页面”。用一句话给AJAX定性AJAX不是一种新语言而是一组技术的组合方案。它把异步网络请求、JavaScript操作DOM、JSON数据解析串在一起让网页具备“后台静默通信”能力。这里要区分两个经常被混在一起的概念同步代码按顺序执行请求发出后必须等响应回来期间程序被卡住。异步请求发出去之后代码不用傻等先继续往下走等数据回来了再通过回调或Promise继续处理。浏览器里默认的AJAX都是异步的。哪怕你不小心把请求写成了同步也强烈不建议在生产环境用它会让页面白屏卡顿。这个我曾经踩过坑后面在第4章讲报文时会细说为什么。1.3 我们用哪种方式写AJAXaxios与原生方案对比实现AJAX浏览器原生有两个选手老牌XMLHttpRequest和较新的fetch。XMLHttpRequest语法古老、嵌套回调容易让人头晕fetch的API虽然简洁但仍有几个不太顺手的地方比如请求默认不带cookie、对超时和取消控制不直接。于是在项目里绝大多数团队会选择装一个基于Promise的HTTP客户端库比如本文主角axios。axios能流行不是因为fetch做不到同样的事而是因为它把日常高频需求做成了顺手的功能自动转换JSON数据响应拿回来直接是对象。请求超时设置、取消请求、拦截器这些都是生产级项目硬需求。同时支持浏览器和Node.js环境后端写脚本调试接口时能复用同一套写法。错误处理更人性化HTTP状态码只要不在2xx范围内会自动进入catch分支。下面所有示例我都用axios演示。如果你刚接触也不要有心理负担它只是对底层XMLHttpRequest的一层封装学的是“发请求、收响应、处理数据”这套通用思路换成fetch或原生XHR思想完全互通。2. URL与查询参数把地址这件事彻底讲明白2.1 URL的五个组成部分写请求第一步是确定“往哪里发”也就是接口URL。很多新手拿到一个接口地址不知道哪些部分是路径、哪些是参数、能不能改结果复制粘贴出来一头雾水。一个标准URL统一资源定位符由五部分组成我拿一个后端模拟接口来拆https://study.example.com:8080/api/user/1001?namejackpage2#top我拆开之后是这样的组成部分示例值含义与作用协议https浏览器和服务器之间用什么规则通信常见有http和httpshttps多了一层加密域名study.example.com服务器在哪台主机上能被人记住的地址端口8080同一台服务器上的不同服务入口默认端口可省略路径/api/user/1001服务器资源的具体位置其中1001是路径参数表示我要操作ID为1001的用户查询字符串?namejackpage2向服务器补充额外的筛选、分页、搜索条件也叫URL参数锚点#top定位页面内部位置注意它根本不会发送给服务器AJAX请求里一般不写前端调用接口时真正要关注的就三个东西协议、路径、查询参数。域名一般由后端配置好写在环境文件里端口也通常被反向代理隐藏掉。2.2 URL查询参数?号后面的学问**查询参数Query Parameters**是URL里从?开始的那一段多个参数用连接形如?page1size10keyword手机。它本质上是一个键值对集合用于向服务器表达你的筛选条件。在axios里传查询参数有两种写法效果完全相同。第一种是直接拼在URL字符串里axios.get(http://localhost:3000/api/user?page1size10)第二种更推荐用params选项让axios帮你去拼代码可读性更好也不用担心某个值里带着特殊字符axios.get(http://localhost:3000/api/user, { params: { page: 1, size: 10, keyword: 手机 } })我团队里的规范是超过两个参数一律用params对象禁止手写拼接字符串。原因很简单手写拼接容易漏、容易忘记编码一到中文参数或特殊符号就翻车。2.3 URL编码为什么参数里的中文乱码了查询参数不是Java调用接口传入的明文面值。URL中只允许出现ASCII字符中文、空格、、这些特殊字符在作为值时都必须经过百分号编码Percent-Encoding。比如“手机”两个字会被编码成%E6%89%8B%E6%9C%BA。我见过很多新手用字符串拼接写keyword坚果手机浏览器可能自动帮你编码一次但如果你在Node.js环境或用工具请求或者参数里出现了本身就会出问题。比如你要搜索的关键词是“iOSAndroid”直接拼接出来的URL会变成?keywordiOSAndroid服务器会把Android当成另一个参数数据就错了。正确做法永远是让axios的params去处理它内部会调用encodeURIComponent对每个键值对进行编码。如果哪天你从地址栏复制了一个带中文的URL给axios发现请求发出了但服务器返回“URL解码失败”十有八九是参数编码不统一服务器端用UTF-8解码而你的环境用了别的编码。遇到这种问题第一步不是改代码而是先确认整个链路都用UTF-8。3. axios使用从快速上手到常用请求方法3.1 axios引入方式与基本GET请求axios的引入方式取决于你项目形态!-- 传统页面直接用CDN -- script srchttps://cdn.jsdelivr.net/npm/axios/dist/axios.min.js/script# 工程化项目使用npm npm install axios// 在模块化项目里导入 import axios from axios引入之后最基础的操作就是发一个GET请求。我习惯在写接口的第一版测试代码时先只打印响应状态和响应体确认通路axios.get(http://localhost:3000/api/users) .then(res { console.log(状态码, res.status) console.log(响应数据, res.data) }) .catch(err { console.error(请求出错, err) })axios一个重要特性是响应对象res里包裹了服务器返回的全部信息而res.data才是业务数据本身。服务器返回的JSON对象会自动被解析成JavaScript对象这一步省了我们大量工作。如果拿到的结果里又包了一层比如res.data.data那是后端定的返回结构看接口文档即可。3.2 常用请求方法与数据提交方式HTTP协议定义了一组请求方法每个方法的语义不同。前端最常见的六个如下方法语义请求体典型场景GET查询资源一般不携带获取列表、详情POST新建资源携带数据注册、登录、新增PUT整体更新资源携带数据修改用户全部信息PATCH局部更新资源携带数据只改用户手机号DELETE删除资源一般不携带删除某条记录HEAD只拿响应头无检查资源是否存在axios对应的写法是axios.get、axios.post、axios.put、axios.patch、axios.delete。以登录为例POST提交数据有两种常见写法写法一直接传对象axios.post(/api/login, { username: jack, password: 123456 })写法二用泛型调用axios({ method: post, url: /api/login, data: { username: jack, password: 123456 } })注意一个高频误区GET用params传参POST、PUT用data传参。params是放在URL查询字符串里的data是放在请求体里的。这两个写反了前端代码不会报错但后端拿不到数据接口会一样返回成功但业务处理错误排查起来极其痛苦。3.3 Content-Type表单提交和JSON提交的区别数据提交时请求体长什么样取决于Content-Type请求头。这也是热词里“content-type 使用表单模式”背后的问题。axios里如果直接传普通对象默认Content-Type是application/json请求体是一串JSON文本{username:jack,password:123456}但如果你使用的是HTML表单提交风格请求体会变成usernamejackpassword123456对应的Content-Type是application/x-www-form-urlencoded。这两种格式后端接收参数的方式完全不同。JSON格式后端一般用RequestBody或request.getInputStream()读取表单格式后端用request.getParameter(username)就能拿到。axios里想发表单格式可以用URLSearchParams把对象转换const formData new URLSearchParams() formData.append(username, jack) formData.append(password, 123456) axios.post(/api/login, formData) // axios会检测到URLSearchParams并自动设置Content-Type为表单格式这里的关键点别把对象直接传进去然后强行指定表单Content-Type很多新手这么干结果axios会把对象序列化成JSON字符串塞进请求体导致后端用表单方式解析时拿到的是乱套数据。要处理多文件上传时就换成FormData对象Content-Type自动变成multipart/form-data这也是热词里“axios multipart”的答案const fd new FormData() fd.append(file, fileInput.files[0]) fd.append(type, avatar) axios.post(/api/upload, fd)3.4 同步与异步为什么axios默认是异步axios底层基于XMLHttpRequest默认异步模式。也就是说你调用axios.get()之后代码不会停下来等服务器返回而是先执行后面的代码等网络响应到达时再触发then或catch里的回调。我刚开始学的时候特别不习惯写了这样的代码let user null axios.get(/api/user/1).then(res { user res.data }) console.log(user) // 结果是null这段代码几乎必然打印出null因为console.log执行时请求还没回来。理解这个行为的关键是想明白事件循环请求发出后被挂起主线程继续跑直到网络响应完成后回调才进入队列执行。遇到这种情况不要试图把异步改成同步而应该把依赖数据的代码全部放进then回调里或者用async/awaitasync function loadUser() { const res await axios.get(/api/user/1) console.log(res.data) }await只是异步函数的语法糖它让代码看起来像同步执行但底层依然是异步的不会阻塞页面渲染。4. HTTP协议与报文浏览器和服务器之间怎么“说话”4.1 请求报文里到底装了什么HTTP协议本质上是一套“约定”浏览器发给服务器的消息叫请求报文服务器返回的叫响应报文。报文由三部分组成起始行 请求头 请求体。拿一个POST登录请求举例浏览器实际发送的内容长这样POST /api/login HTTP/1.1 Host: localhost:3000 Content-Type: application/json Content-Length: 45 User-Agent: Mozilla/5.0 ... Accept: application/json {username:jack,password:123456}第一行是请求行说明方法、路径、协议版本。中间的请求头是一堆键值对告诉服务器额外的上下文信息内容类型、长度、浏览器标识等。空行之后是请求体只有POST/PUT/PATCH等方法才会有请求体。报文的含义用生活化类比请求行是“你要干嘛”去银行办业务请求头是“你的证件和身份信息”请求体是“你要提交的具体单据”。新手不用把每个头都背下来但有几个高频的必须眼熟Host目标服务器域名和端口。Content-Type请求体格式。Content-Length请求体长度。Authorization登录后带的token凭证非常常用。Cookie浏览器自动携带的会话信息。4.2 响应报文与状态码服务器返回的响应报文结构与之对应HTTP/1.1 200 OK Content-Type: application/json Content-Length: 62 Date: ... {code:0,message:success,data:{...}}第一行是状态行200是状态码OK是原因短语。响应头里常见的Content-Type标明返回数据格式。空行之后是响应体也就是我们说的res.data。状态码是排查接口问题的第一线索必须熟练掌握分类状态码区间含义常见例子2xx成功200成功、201创建成功、204无内容3xx重定向301永久重定向、302临时重定向、304缓存有效4xx客户端错误400参数错误、401未认证、403无权限、404找不到、429请求过多5xx服务器错误500服务器内部错误、502网关错误、503服务不可用实际开发中我收到最多的反馈是“接口报错”发过来的截图里往往只写了404或500。这两个完全是不同性质的错误4xx说明前端请求本身有问题地址错、参数格式错、没带token5xx说明服务器或后端代码出问题了。看到500先别急着改前端把问题转给后端更有效。4.3 用开发者工具看真实报文学习协议最直接的方法不是看书而是打开浏览器的开发者工具F12切到Network面板发一次请求点击条目就能看到完整的请求头和响应头。这里有一个反复强调的习惯看接口问题先抓Network面板的真实请求和响应而不是只看控制台报错。控制台的错误信息是浏览器封装过的经常省略关键细节比如实际URL、请求头、服务器返回的具体错误文案。而Network面板里能确认以下几件事请求方法对不对。URL是否和接口文档一致。请求体格式是否正确。响应状态码和响应体里的错误信息。请求耗时和哪个环节最慢。如果响应状态码是200但功能不对那问题几乎一定出在业务逻辑层。这时候把响应体截图发给后端往往比争论“我的代码没错”更高效。5. 接口文档前端工程师的施工图纸5.1 拿到接口文档先看这几个要素接口文档无论写在Swagger、Apifox还是Yapi核心要素都一样。我建议新人拿到文档后按以下顺序检查接口地址确定协议、域名、路径看清楚有没有路径参数需要填。请求方法GET还是POST写反了服务器直接405。请求参数哪些在查询参数里哪些在请求体里参数名必须一字不差注意大小写。请求体格式是JSON还是表单直接决定Content-Type。认证方式有没有要求token或接口密钥漏了会401。响应结构成功和失败时分别返回什么业务码是什么。错误码表后端约定的非HTTP状态下的业务错误码。这里要特别强调“参数名必须一字不差”。接口文档写user_name你传username浏览器不发报错控制台也没有异常但后端拿到的是null然后报“用户名不能为空”。这种问题在代码层面极难排查只能看Network面板对比请求参数。5.2 一个典型接口文档示例对照假设要做用户登录接口文档定义如下字段值地址/api/login方法POST请求头Content-Type: application/json请求体{ username: 字符串, password: 字符串 }成功响应HTTP 200{ code: 0, message: 成功, data: { token: xx, nickname: xx }}失败响应HTTP 200{ code: 1001, message: 用户名或密码错误, data: null }注意这个设计的巧妙之处HTTP状态码永远200业务是否成功用code字段表示。这是目前很常见的一种设计因为HTTP状态码语义有限而业务错误类型很多。axios请求代码axios.post(/api/login, { username: form.username, password: form.password }).then(res { if (res.data.code 0) { // 成功把token存起来 localStorage.setItem(token, res.data.data.token) } else { alert(res.data.message) } }).catch(err { console.error(网络异常, err) })5.3 接口返回结构的常见坑开发经验多了你会发现接口文档写得再好后端返回结构也会有几个常见坑。知道这些坑能省下大量沟通时间。第一个坑code是数字还是字符串。有的后端返回0有的返回0000有的成功用200。判断成功条件时务必和你接口文档保持一致别用抽象比较糊弄。第二个坑返回数据嵌套层级不统一。有的接口成功时data是对象失败时data是null甚至直接省略data字段。前端取值前要做防御性判断const data res.data?.data ?? null第三个坑数组还是对象。列表接口返回data是一个数组详情接口返回data是一个对象千万别用同一条数据处理的逻辑去通用处理所有接口每接一个接口之前先打印一次res.data确认结构。6. 案例实战用form-serialize插件完成用户登录6.1 为什么要用form-serialize插件前面讲了axios发请求但真实项目中还有一个很常见的体力活从表单元素取值。如果页面有7、8个输入框传统写法是const username document.querySelector(#username).value const password document.querySelector(#password).value const nickname document.querySelector(#nickname).value // 以此类推每增加一个字段就要多写两行。字段一多又容易漏尤其复选框、下拉框的取值方式还不一样复选框可能拿不到值下拉框要注意选中的是value还是text。form-serialize是一个专门解决这个问题的表单序列化插件。它读取表单内所有带name属性的字段把它们的值整理成一个对象或查询字符串大大减少手工取值代码。用法很简单script srchttps://cdn.jsdelivr.net/npm/form-serialize0.7.2/dist/form-serialize.min.js/scriptconst form document.querySelector(#login-form) const formData serialize(form, { hash: true }) console.log(formData) // { username: jack, password: 123456 }第二个参数{ hash: true }表示返回一个JavaScript对象如果不传或者传{ hash: false }它返回的是usernamejackpassword123456这种URL编码字符串配合表单格式提交更方便。6.2 用户登录案例完整实现下面把前面所有知识点串起来写一个完整可跑的登录页面。HTML结构如下form idlogin-form input typetext nameusername placeholder用户名 input typepassword namepassword placeholder密码 button typesubmit登录/button /form对应JavaScriptdocument.querySelector(#login-form).addEventListener(submit, async function (event) { // 阻止表单默认的同步提交行为这是AJAX第一步 event.preventDefault() // 用form-serialize收集表单数据 const formData serialize(this, { hash: true }) try { const res await axios.post(/api/login, formData) const result res.data if (result.code 0) { localStorage.setItem(token, result.data.token) location.href /dashboard.html } else { alert(result.message) } } catch (err) { console.error(请求异常, err) alert(网络异常请稍后重试) } })这里面最容易被漏掉的一行是event.preventDefault()。如果不加浏览器会在axios请求发出后紧接着执行表单默认提交页面立刻刷新给你一种“接口好像没反应”的感觉。新手调试时遇到这种问题先查这一行。6.3 form-serialize的字段覆盖与空值处理form-serialize虽然好用也有一些需要知道的细节。首先是字段必须有name属性。插件序列化的依据是name没有name的输入框会被忽略。有人把表单控件写了id忘了name序列化结果里就是缺字段。其次是空值默认不返回。如果某个输入框是空的序列化结果里不会包含这个字段除非你传入{ empty: true }const formData serialize(form, { hash: true, empty: true })加了empty: true之后空的输入框也会带上空字符串。这个要看后端接口的校验规则来取舍如果后端要求字段必须存在就加如果不要求就不加省得传一堆空值占带宽。最后是复选框和单选框。插件只会序列化被选中的复选框没选中的自然忽略这避免了很多手写取值时的麻烦。但要注意如果后端希望未选中的项也传一个false或空数组那就得自己在序列化结果上补充处理不能完全依赖插件。6.4 结合登录案例梳理完整链路把案例从用户按下按钮到页面跳转整个链路复盘一下用户点击提交按钮触发submit事件。事件处理函数调用preventDefault()阻止浏览器原生同步提交。serialize()把表单控件值收集成对象。axios.post()发起异步请求请求体默认转为JSON字符串。服务器校验用户名和密码返回业务响应。前端判断业务码成功则保存token并跳转失败则提示错误信息。整个过程页面没有任何刷新和闪烁这就是AJAX的体验优势。如果你在Network面板里观察能发现请求和响应在毫秒级就完成了HTML、CSS、JS资源没有重复加载。最后再分享一个我实际开发里的习惯写登录这类涉及用户凭证的接口时前端不要只依赖alert提示至少要在catch分支里把完整错误对象打到控制台。很多网络层面的问题超时、DNS解析失败、跨域被拦截服务器根本不会返回业务错误码只在控制台里能看到线索。先把请求链路打通再处理业务逻辑这是AJAX开发里最省时间的排错顺序。