前端Ajax&Fetch与跨域请求
原生Ajax
初识 Ajax
- Ajax是一种概念,XMLHttpRequest是实现的第一种方案,后续现代化使用Fetch,一般开发使用axios
- Ajax 是 Asynchronous JavaScript and XML (异步 JavaScript 和 XML) 的简写
- Ajax中的异步<可以异步地向服务器发送请求>可以异步地向服务器发送请求>,在等待响应的过程中,不会阻塞当前页面,浏览器可以做自己的事情。直到成功获取响应后,浏览器才开始处理响应数据
- 使用 JavaScript 向服务器发送请求,并在不刷新整个页面的情况下更新页面数据。
- XML(可扩展标记语言)是前后端数据通信时传输数据的一种格式
- XML现在已经不怎么用了,现在比较常用的是JSON
- Ajax其实就是浏览器与服务器之间的一种异步通信方式
- 非服务器环境下,很多浏览器是无法使用Ajax,如直接打开文件,就无法使用Ajax
Ajax 的基本用法
- Ajax想要实现浏览器与服务器之间的异步通信,需要依靠XMLHttpRequest,它是一个构造函数
- 不论是XMLHttpRequest,还是Ajax,都没有和具体的某种数据格式绑定
XMLHttpRequest
readystatechange事件监听 readyState 这个状态的变化//它的值从0~4,一共5个状态
- 0<未初始化>未初始化>。尚未调用open
- 1<启动>启动>。已经调用open,但尚未调用send
- 2<发送>发送>。已经调用send,但尚未接收到响应
- 3<接收>接收>。已经接收到部分响应数据
- 4<完成>完成>。已经接收到全部响应数据,而且已经可以在浏览器中使用了
使用步骤:
为了兼容性,不要使用this,使用xhr,为了兼容性,在发送之前,监听事件
// 1. 创建一个 XMLHttpRequest 对象var xhr = new XMLHttpRequest();
// 4.1. 监听请求状态的变化(推荐)xhr.addEventListener('readystatechange', () => { // 5.1. 判断请求是否完成,网络层面,xhr.readyState === 4请求已经结束,可以拿到完整数据 if (xhr.readyState !== 4) return; // 6.1. 判断请求是否成功,服务器层面 if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { }})// 4. 监听请求状态的变化,旧写法,兼容更好xhr.onreadystatechange = () => { // 5. 判断请求是否完成,网络层面,xhr.readyState === 4请求已经结束,可以拿到完整数据 if (xhr.readyState !== 4) return; // 6. 判断请求是否成功,服务器层面 if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { }}
// 2. 配置请求信息,准备发送请求(HTTP方法、URL,是否异步发送请求)xhr.open('GET', 'https://api.apihubs.cn/holiday/get', true);
// 3. 发送请求xhr.send();完成前后端通讯:
const url = 'https://api.apihubs.cn/holiday/get';
const xhr = new XMLHttpRequest();xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) return; if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.responseText); //数据作为字符串形式 console.log(typeof xhr.responseText); //string } }});xhr.open('GET', url, true);xhr.send();GET 请求
携带数据
const url = 'https://www.imooc.com/api/http/search/suggest';const xhr = new XMLHttpRequest();xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) return; if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.responseText); //数据作为字符串形式 console.log(typeof xhr.responseText); //string } }});//get通过请求头携带xhr.open('GET', `${url}?words=js`, true);xhr.send();数据编码
如果在get请求中携带中文,浏览器会自动编码,也就是说,其实直接传中文也没问题。但是不同浏览器的编码不同,后端最终得到的也会不一样的数据,所以需要统一编码,使用encodeURIComponent,还有的原因就是不是所有场景都会自动编码,如果使用浏览器 XHR、fetch通常会自动处理,但是自己拼接特殊字符,会有问题,encodeURIComponent编码不是很推荐,推荐使用URLSearchParams
const name = '张三&李四';const xhr = new XMLHttpRequest();
xhr.open('GET', `/test?name=${name}`);//浏览器发送/test?name=张三&李四//后端解析,参数就被截留了//name = 张三//李四 = ''
//正确写法const name = encodeURIComponent('张三&李四');xhr.open('GET', `/test?name=${name}`);//浏览器发送 /test?name=%E5%BC%A0%E4%B8%89%26%E6%9D%8E%E5%9B%9B//后端解析:张三&李四使用encodeURIComponent来编码
const url = 'http://localhost:3000/get';const xhr = new XMLHttpRequest();xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) return; if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.responseText); //数据作为字符串形式 console.log(typeof xhr.responseText); //string } }});//get通过请求头携带//encodeURIComponent编码xhr.open( 'GET', `${url}?username=${encodeURIComponent('林')}&age=18`, true,);xhr.send();encodeURIComponent详细解析
encodeURIComponent 是一个字符串编码函数,可以在任何需要的地方调用它,不一定非得写在模板字符串里面。
//写法 1:直接写xhr.open( 'GET', `${url}?username=${encodeURIComponent('林')}&age=18`, true);//写法 2:先编码,再拼接const username = encodeURIComponent('林');xhr.open( 'GET', `${url}?username=${username}&age=18`, true//写法 3:多个参数自己拼const params = { username: '林', age: 18};
const query = Object.entries(params) .map(([k, v]) => `${k}=${encodeURIComponent(v)}`) .join('&');
xhr.open('GET', `${url}?${query}`, true);//写法 4:封装一个函数function stringify(params) { return Object.entries(params) .map(([k, v]) => `${k}=${encodeURIComponent(v)}`) .join('&');}
xhr.open( 'GET', `${url}?${stringify({ username: '林', age: 18 })}`, true);推荐使用URLSearchParams:完整示例
const url = 'https://www.imooc.com/api/http/search/suggest';const params = new URLSearchParams({ words: '前端&后端',});const xhr = new XMLHttpRequest();xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) return; if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.responseText); //数据作为字符串形式 console.log(typeof xhr.responseText); //string } }});//get通过请求头携带xhr.open('GET', `${url}?${params}`, true);xhr.send();encodeURIComponent和URLSearchParams的区别
| encodeURIComponent | URLSearchParams | |
|---|---|---|
| 类型 | 函数 | 类 |
| 作用 | 编码一个字符串 | 管理整个查询参数 |
自动拼 key=value | ❌ | ✅ |
自动拼 & | ❌ | ✅ |
| 自动编码 | ✅ | ✅ |
| 获取参数 | ❌ | ✅ |
| 删除参数 | ❌ | ✅ |
| 修改参数 | ❌ | ✅ |
POST 请求
携带数据&数据编码
const url = 'https://www.imooc.com/api/http/search/suggest?words=js';const xhr = new XMLHttpRequest();xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) return; if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { console.log(xhr.responseText); //数据作为字符串形式 console.log(typeof xhr.responseText); //string } }});xhr.open('POST', url, true);// 数据编码const data = new URLSearchParams({ words: '前端&后端',});//常用1: 类型fromdataxhr.send(data);//常用2:不能直接传对象,需要传字符串类型,json解决,这时候就不用编码了// const data1 = {// username: 'alex',// age: 18// };// 对象 → JSON 字符串 → 发送// xhr.send(JSON.stringify(data1));JSON
初识 JSON
- JavaScript Object Notation(JavaScript 对象表示法)
- JSON 是一种“数据格式”,用来在前后端之间传输数据
- JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,用于前后端通信。它本质是字符串,常用于将 JavaScript 对象序列化为字符串(JSON.stringify),或将 JSON 字符串解析为对象(JSON.parse)。
JSON 长什么样:看起来像对象,但它是:字符串格式的数据
{ "name": "Tom", "age": 18}[ { "name": "Tom", "age": 18 }, { "name": "Jerry", "age": 20 }]JSON 的 3 种形式
简单值:JSON 不仅可以是对象和数组,还可以是“单值”
"hello"100truenull对象形式
{ "name": "Tom", "age": 18}数组形式
[ { "name": "Tom", "age": 18 }, { "name": "Jerry", "age": 20 }]注意事项:
- JSON 不允许单引号
- JSON 不允许没有引号
- JSON 不允许undefined
- 不能注释
JSON 的常用方法
JSON 在前端中主要有两个常用方法:JSON.stringify 和 JSON.parse。stringify 用于将 JavaScript 对象转换为 JSON 字符串,常用于数据存储和请求发送;parse 用于将 JSON 字符串转换为 JavaScript 对象,常用于接口数据解析和本地存储读取。
JSON.stringify():JS对象 → JSON字符串
const obj = { name: 'Tom', age: 18};
const json = JSON.stringify(obj);
console.log(json);//{"name":"Tom","age":18}stringify 的进阶用法:
//第二个参数:过滤字段const obj = { name: 'Tom', age: 18, password: '123456'};const json = JSON.stringify(obj, ['name', 'age']);console.log(json);//{"name":"Tom","age":18}
//格式化输出(第三个参数)JSON.stringify(obj, null, 2);console.log(json);//{// "name": "Tom",// "age": 18//}JSON.parse():JSON字符串 → JS对象
const json = '{"name":"Tom","age":18}';const obj = JSON.parse(json);console.log(obj.name);//Tom拓展-封装storage
const storage = window.localStorage;//设置const set = (k, v) => { storage.setItem(k, JSON.stringify(v));};//获取const get = (k) => { return JSON.parse(storage.getItem(k));};//删除const remove = (k) => { storage.removeItem(k);};//清空const clear = () => { storage.clear();};export { set, get, remove, clear };使用:
import { set, get, remove, clear } from './storage.js';const url = 'http://localhost:3000/post';const xhr = new XMLHttpRequest();xhr.addEventListener('readystatechange', () => { if (xhr.readyState !== 4) return; if (xhr.readyState === 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { //console.log(xhr.responseText); //数据作为字符串形式 //console.log(typeof xhr.responseText); //string const data = JSON.parse(xhr.responseText);//转对象 console.log(data.user);//对象取user set('user', data.user);//存对象,k值为user console.log(get('user'));//取值 } }});xhr.open('POST', url, true);//常用1:表单提交就是这种//xhr.send('username=alex&age=18');//常用2:不能直接传对象,需要传字符串类型,json解决xhr.send( JSON.stringify({ username: 'alex', age: 18, }),);跨域
初识跨域
- 跨域就是浏览器不允许“不同源之间互相访问数据”。
- 跨域是指浏览器基于同源策略限制不同源之间的资源访问行为。同源是指协议、域名和端口完全相同。跨域并不是请求无法发送,而是服务器返回的数据被浏览器拦截。常见解决方式有 CORS、代理和 JSONP。
什么是“同源”?
浏览器有一个规则叫:同源策略,判断是否同源看三点:协议 + 域名 + 端口
//同源(可以访问)http://localhost:3000http://localhost:3000//不同源(跨域)http://localhost:3000http://localhost:8080跨域本质是什么?
很多人误解:跨域 = 请求发不出去,其实不是,请求其实已经发出去了,但浏览器拦截了响应
为什么要有跨域限制?
坏网站可以读取你的银行网站数据,比如你登录了银行,同时打开恶意网站,恶意网站可以直接读取你的账户信息,所以浏览器规定不同源之间不能随便读数据
跨域发生在哪里?
跨域只在:浏览器生效,不跨域情况:Node.js 请求接口 → 没有跨域问题
哪些操作会触发跨域?
- Ajax / Fetch
- XMLHttpRequest
- Axios
跨域的错误长什么样?
- Access to XMLHttpRequest has been blocked by CORS policy
- No ‘Access-Control-Allow-Origin’ header
跨域的解决方案
- 后端设置 CORS(最常用);Access-Control-Allow-Origin: *
- 代理(前端常用):前端 → 本地服务器 → 后端
- JSONP(老方案):利用 script 标签不受跨域限制
- iframe / postMessage:用于特殊场景
CORS 跨域资源共享
- CORS 是浏览器“允许跨域访问”的一种官方解决方案。
- CORS 不是前端的东西,是后端设置的规则
- ie10及以上可以兼容
核心请求头
请求头(浏览器自动带)
//我来自哪个源Origin: http://front.a.cn响应头(后端设置)
//允许所有域名Access-Control-Allow-Origin: *//只允许指定域名:Access-Control-Allow-Origin: http://front.a.cnJSONP
JSONP 全称:JSON with Padding,利用 <script> 标签没有跨域限制的特性,实现跨域获取数据的一种老方案。
JSONP 是一种利用 script 标签没有跨域限制而实现跨域请求的技术。其原理是前端定义一个回调函数,通过 script 标签请求服务器,服务器返回一段执行该回调函数的 JavaScript 代码,从而实现数据获取。JSONP 只能发送 GET 请求,安全性较差,现在基本被 CORS 替代。
手写 JSONP
function jsonp(url, callback) { const script = document.createElement('script'); script.src =`${url}?callback=${callback}`; document.body.appendChild(script);}
function handleResponse(data) { console.log(data);}
jsonp( 'https://www.imooc.com/api/http/jsonp', 'handleResponse');JSONP 的缺点:
- 只能 GET
- 安全性差
- 错误处理麻烦
- 需要服务器配合
XHR 对象
XHR 的属性
重点属性:
response:获取响应数据
xhr.response //兼容问题 IE10及以上可以用
//返回值由:xhr.responseType决定xhr.responseType = 'json'; //返回json
//如果responseType不设置就是字符串,所以可以使用xhr.responseTextresponseType:指定响应数据类型
xhr.responseType = 'json';可选值:
| 值 | 含义 |
|---|---|
| ‘’ | 默认字符串 |
| text | 文本 |
| json | JSON对象 |
| blob | 二进制文件 |
| arraybuffer | 二进制数据 |
| document | XML/HTML文档 |
timeout :请求超时时间,单位:毫秒(ms)
//open之后,send之前设置xhr.timeout = 5000;//5秒后自动超时,连接取消withCredentials :是否携带 Cookie
//默认falsexhr.withCredentials = true;//跨域请求
//使用Ajax发送请求,默认情况下,同域时,会携带Cookie;跨域时,不会//xhr.withCredentials = true;//最终能否成功跨域携带Cookie,还要看服务器同不同意,服务器必须指定域名,不能Access-Control-Allow-Origin: *其他属性
readyState:表示请求当前的状态。
xhr.readyState| 值 | 状态 | 含义 |
|---|---|---|
| 0 | UNSENT | XHR 已创建,还没调用 open() |
| 1 | OPENED | 已调用 open() |
| 2 | HEADERS_RECEIVED | 已收到响应头 |
| 3 | LOADING | 正在接收响应体 |
| 4 | DONE | 请求完成 |
status:HTTP 状态码
xhr.status
200201204404500statusText:状态码对应的描述
xhr.statusText
200 OK404 Not Found500 Internal Server ErrorresponseText:获取服务器返回的文本数据
xhr.responseText
{ "name": "Tom", "age": 18}const data = JSON.parse(xhr.responseText);responseURL:最终响应的 URL
xhr.responseURL//https://api.xxx.com/user //一般用于重定向后的最终地址upload :上传对象,用于监听文件上传进度
xhr.upload.onprogress = function (e) { console.log(e.loaded);};responseXML:获取 XML 数据
xhr.responseXML//返回XMLDocument,基本不用,json取代| 属性 | 用途 | 常用程度 |
|---|---|---|
| readyState | 请求状态 | ⭐⭐⭐⭐⭐ |
| status | HTTP状态码 | ⭐⭐⭐⭐⭐ |
| responseText | 文本响应 | ⭐⭐⭐⭐⭐ |
| response | 响应数据 | ⭐⭐⭐⭐ |
| responseType | 响应类型 | ⭐⭐⭐⭐ |
| timeout | 超时时间 | ⭐⭐⭐ |
| withCredentials | 携带Cookie | ⭐⭐⭐⭐⭐ |
| statusText | 状态描述 | ⭐⭐⭐ |
| upload | 上传进度 | ⭐⭐⭐ |
| responseXML | XML响应 | ⭐⭐ |
XHR 的方法
重点方法:
abort():取消请求,一般配合onabort事件使用
xhr.abort();
const xhr = new XMLHttpRequest();xhr.open('GET', '/user');xhr.send();xhr.abort();setRequestHeader():设置请求头。
//必须在open()之后send()之前//不是所有的头信息能设置,出于安全,比如 Origin: http://front.a.cn 伪造跨域就不能设置xhr.setRequestHeader(name, value);//Content-Type告诉服务器,浏览器发送的数据是什么格式的xhr.setRequestHeader( 'Content-Type', 'application/json');xhr.setRequestHeader( 'Authorization', token);其他方法:
open():初始化一个请求
xhr.open(method, url, async);xhr.open('POST', '/login', true);| 参数 | 说明 |
|---|---|
| method | 请求方法(GET、POST…) |
| url | 请求地址 |
| async | 是否异步,默认 true |
send():发送请求。
//GET 请求xhr.send();//POST 请求xhr.send(data);
xhr.send( JSON.stringify({ username: 'admin', password: '123456' }));getResponseHeader() :获取指定响应头
xhr.getResponseHeader(name);const type = xhr.getResponseHeader('Content-Type');console.log(type);//application/jsongetAllResponseHeaders():获取所有响应头
const headers = xhr.getAllResponseHeaders();console.log(headers);
content-type: application/jsoncontent-length: 123server: nginxoverrideMimeType() :修改响应的 MIME 类型。
xhr.overrideMimeType( 'application/json');XHR 最常用的方法总结
| 方法 | 作用 | 常用程度 |
|---|---|---|
| open() | 初始化请求 | ⭐⭐⭐⭐⭐ |
| send() | 发送请求 | ⭐⭐⭐⭐⭐ |
| setRequestHeader() | 设置请求头 | ⭐⭐⭐⭐ |
| getResponseHeader() | 获取一个响应头 | ⭐⭐⭐ |
| getAllResponseHeaders() | 获取全部响应头 | ⭐⭐⭐ |
| abort() | 取消请求 | ⭐⭐⭐⭐ |
| overrideMimeType() | 修改 MIME 类型 | ⭐⭐ |
XHR 的事件
为什么需要事件?
因为ajax是异步的,所以需要在某个时机通知我们,请求发生了什么这就是事件的作用。
重点事件
onload:请求成功完成时触发
和 onreadystatechange 的区别:onreadystatechange状态变化触发多次,onload请求成功结束触发一次
xhr.onload = function () { console.log(xhr.responseText);};//eg IE10xhr.onload = function () { if ( xhr.status >= 200 && xhr.status < 300 ) { console.log(xhr.response); }};onerror :请求失败时触发
xhr.onerror = function () { console.log('网络错误');};//404和500不会触发 onerror IE10//只处理断网、DNS失败、服务器无法连接ontimeout:请求超时时触发
xhr.timeout = 5000;//ie8xhr.ontimeout = function () { console.log('请求超时');};onabort :请求被取消时触发
xhr.onabort = function () { console.log('请求取消');};//xhr.abort();会触发onabort IE10其他事件
onreadystatechange:每次xhr.readyState发生变化时触发。
xhr.onreadystatechange = function () { console.log(xhr.readyState);};//eg:xhr.onreadystatechange = function () { if ( xhr.readyState === 4 && xhr.status === 200 ) { console.log(xhr.responseText); }};onloadstart :请求开始时触发
xhr.onloadstart = function () { console.log('开始请求');};onloadend:请求结束时触发
xhr.onloadend = function () { console.log('请求结束');};//无论成功失败超时取消都会触发onprogress:请求过程中不断触发
//例如下载文件:xhr.onprogress = function (e) { console.log(e.loaded);};console.log( e.loaded, e.total);const percent = e.loaded / e.total * 100;upload.onprogress :上传进度监听
xhr.upload.onprogress = function (e) { const percent = e.loaded / e.total * 100;
console.log(percent);};//文件上传进度条、图片上传、视频上传常用事件总结
| 事件 | 作用 | 常用程度 |
|---|---|---|
| onreadystatechange | 状态变化 | ⭐⭐⭐⭐⭐ |
| onload | 请求成功 | ⭐⭐⭐⭐ |
| onerror | 网络错误 | ⭐⭐⭐⭐ |
| ontimeout | 超时 | ⭐⭐⭐ |
| onabort | 请求取消 | ⭐⭐⭐ |
| onprogress | 下载进度 | ⭐⭐⭐ |
| upload.onprogress | 上传进度 | ⭐⭐⭐⭐ |
| onloadstart | 请求开始 | ⭐⭐ |
| onloadend | 请求结束 | ⭐⭐ |
FormData
使用Ajax提交表单
<form id="loginForm"> <input type="text" name="username" placeholder="用户名"> <input type="password" name="password" placeholder="密码"> <button type="submit">登录</button></form><script>const form = document.getElementById('loginForm');const url = 'https://www.imooc.com/api/http/search/suggest?words=js';const { username, password } = form;form.addEventListener('submit', function (e) { // 阻止浏览器默认提交 e.preventDefault(); //const formData = new FormData(form); const xhr = new XMLHttpRequest(); xhr.onload = function () { if ( (xhr.readyState === 4 && xhr.status === 200) || xhr.status === 304 ) { console.log(xhr.responseText); } }; xhr.open('POST', url, true); //组装数据 const data = `username=${username.value}&password=${password.value}`; //设置请求头 xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded', ); xhr.send(data);});</script>组装数据改版:直接包装成FormData发送
const form = document.getElementById('loginForm');const url = 'https://www.imooc.com/api/http/search/suggest?words=js';form.addEventListener('submit', function (e) { // 阻止浏览器默认提交 e.preventDefault(); const xhr = new XMLHttpRequest(); xhr.onload = function () { if ( (xhr.readyState === 4 && xhr.status === 200) || xhr.status === 304 ) { console.log(xhr.responseText); } }; xhr.open('POST', url, true); //组装数据 const data = new FormData(form); for (const item of data) { console.log(item); }
xhr.send(data);FormData基本用法
const form = document.getElementById("loginForm");const formData = new FormData(form);xhr.send(formData);//添加数据:append()const formData = new FormData();formData.append('username', '张三');formData.append('age', 18);//添加文件const file = document.getElementById('file').files[0];formData.append('avatar', file);//添加多个同名字段formData.append('hobby', '篮球');formData.append('hobby', '足球');formData.append('hobby', '游戏');//修改数据:set()formData.append('name', '张三');formData.set('name', '李四');//name=李四//获取数据formData.get('username');封装Ajax
ajax.js
import DEFAULTS from './defaults';class Ajax { constructor(url, options) { this.url = url; this.options = Object.assign({}, DEFAULTS, options); //初始化 this.init(); } //初始化 init() { //实例化XMLHttpRequest const xhr = new XMLHttpRequest(); this.xhr = xhr; this.bindEvent(); //监听事件 }
//监听事件 bindEvent() { const xhr = this.xhr; const { success, httpCodeError, error, abort, timeout } = this.options; //请求成功完成时触发 xhr.onload = () => { if (this.ok) { success(xhr.response, xhr); } else { httpCodeError(xhr.status, xhr); } }; //请求失败时触发 xhr.onerror = () => { error(xhr); }; //onabort请求被取消时触发 xhr.onabort = () => { abort(xhr); }; //ontimeout请求超时时触发 xhr.ontimeout = () => { timeout(xhr); }; } //检测响应http状态码是否正常 ok() { const xhr = this.xhr; return (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304; }}export default Ajax;defaults.js
//默认参数const DEFAULTS = { method: 'GET', params: null, //请求头 data: null, //请求体 contentType: 'application/x-www-form-urlencoded', response: '', timeoutTime: 0, withCredentials: false, //方法 success() {}, httpCodeError() {}, error() {}, abort() {}, timeout() {},};- 用类封装ajax,能传入url和配置
- 使用DEFAULTS.js设置默认的配置:method、params、data、contentType、response、timeoutTime、withCredentials
- init初始化XMLHttpRequest,绑定
this.xhr = xhr全局可用,this.bindEvent()绑定监听事件 - onload的时候触发自定义的success方法,否则触发自定义的httpCodeError方法
- onerror触发自定义的error方法
- onabort触发自定义的abort方法
- ontimeout触发自定义的timeout方法
继续封装优化:
ajax.js
import { serialize, addURLData } from './utils';//get的时候,传参数需要处理addParam()xhr.open(this.options.method, this.url + this.addParam(), true);//在地址上添加数据 addParam() { const { params } = this.options; //如果为空,直接返回'',比如post请求params就是null //要传对象 if (!params) { return ''; } //处理对象形式 //两种情况:一种本身没参数的,一种本身就带参数的,追加参数的 //工具类解决 return addURLData(thi.url, serialize(params)); }utils.js
//工具函数
//数据序列化成urlencoded格式的字符串const serialize = (param) => { const results = []; for (const [key, value] of Object.entries(param)) { results.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); } return results.join('&');};
//给url添加参数const addURLData = (url, data) => { if (!data) return ''; //判断url是否已经有参数 //如果有?就用&,如果没有就用? const mark = url.includes('?') ? '&' : '?'; return `${mark}${data}`;};export { serialize, addURLData };- open的增加,method,url,和是否异步,method直接配置取就可以,url这里需要考虑get的情况在url添加
- 创建工具函数,addURLData:给url添加参数,serialize数据序列化成urlencoded格式的字符串
继续优化封装
ajax.js
import { HTTP_GET, CONTENT_TYPE_FORM_URLENCODED, CONTENT_TYPE_FORM_JSON,} from './constants';
//发送请求this.sendData(); //发送 sendData() { const xhr = this.xhr; if (!this.isSendData()) { return xhr.send(null); } } //是否需要使用send发送数据 isSendData() { const { data, method } = this.options; if (!data) return false; //判断,转成都是小写 if (method.toLowerCase() === HTTP_GET.toLowerCase()) return false; return true; }constants.js
//常量export const HTTP_GET = 'GET';export const CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded';
export const CONTENT_TYPE_FORM_JSON = 'application/json';defaults.js
import { HTTP_GET, CONTENT_TYPE_FORM_URLENCODED } from './constants';//默认参数const DEFAULTS = { method: HTTP_GET, params: null, //请求头 data: null, //请求体 contentType: CONTENT_TYPE_FORM_URLENCODED, response: '', timeoutTime: 0, withCredentials: false, //方法 success() {}, httpCodeError() {}, error() {}, abort() {}, timeout() {},};- 最后需要把数据发送出去,那就需要判断是否需要发送,get请求不需要发送,直接null
- sendData就是发送,isSendData就是判断要不要发送
- 增加常量,统一管理常量信息
继续完善优化
ajax.js
import { serialize, addURLData, serializeJSON } from './utils';//发送 sendData() { const xhr = this.xhr; if (!this.isSendData()) { return xhr.send(null); } //需要传入就直接传入发送了,但是这里需要判断数据的格式 //xhr.send() let resultData = null; //判断是不是FormData if (!this.isFormData()) { resultData = data; } else if (this.isFormURLEncodedData()) { //发送application/x-www-form-urlencoded格式数据 this.setContentType(CONTENT_TYPE_FORM_URLENCODED); resultData = serialize(data); } else if (this.isJSONData()) { //发送JSON格式数据 this.setContentType(CONTENT_TYPE_FORM_JSON); resultData = serializeJSON(data); } else { this.setContentType(); resultData = data; } xhr.send(resultData); }
//判断是不是FormData isFormData() { return this.options.data instanceof FormData; } //application/x-www-form-urlencoded格式数据 isFormURLEncodedData() { return this.options.responseType .toLowerCase() .includes(CONTENT_TYPE_FORM_URLENCODED); } //json isJSONData() { return this.options.responseType .toLowerCase() .includes(CONTENT_TYPE_FORM_JSON); } //setContentType setContentType(contentType = this.options.contentType) { if (!contentType) return; this.xhr.setRequestHeader('Content-Type', contentType); } //获取 getXHR() { return this.xhr; }utils.js
//数据序列化成jsonconst serializeJSON = (param) => { return JSON.stringify(param);};export { serialize, addURLData, serializeJSON };- 判断传的是fromdata、application/x-www-form-urlencoded还是json还是其他格式
- setContentType设置请求头发送的数据格式
最终封装和修复bug
Ajax应用
搜索提示
二级菜单
多个Ajax请求的并发执行
Axios与Fetch
axios
基本使用 :
const url = 'http://192.168.2.132:3000/post'; axios(url, { method: 'post', Headers: { 'content-type': 'application/json' },
//传对象就是application/json data: { username: 'alex', age: 18, }, timeout: 1, withCredentials: false, }) .then((result) => { console.log(result.data); }) .catch((err) => { console.log(err); });Fetch
Fetch是Ajax于Promise 的(XMLHttpRequest)的一种替代方案,它是基于Promise
fetch 是基于 Promise 的网络请求 API,现在基本已经替代了大部分 XMLHttpRequest 的使用场景。
和 XHR 对比
| 功能 | XHR | Fetch |
|---|---|---|
| Promise | ❌ | ✅ |
| async/await | ❌ | ✅ |
| 自动 reject 404 | ❌ | ❌ |
| 自动 reject 网络错误 | ❌ | ✅ |
| abort | xhr.abort() | AbortController |
| timeout | xhr.timeout | ❌ 自己实现 |
| 上传进度 | ✅ | ❌ |
| 下载进度 | ✅ | ✅(Stream) |
| 设置 responseType | ✅ | 不需要,调用 json()、text() 等即可 |
基本使用
async function login() { try { const response = await fetch( 'http://localhost:3000/login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: 'admin', password: '123456', }), } );
if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); }
const data = await response.json();
console.log('成功:', data); } catch (error) { console.log('失败:', error.message); }}
login();文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














