视频加载失败

前端Ajax&Fetch与跨域请求

5414 字
27 分钟
前端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的区别

encodeURIComponentURLSearchParams
类型函数类
作用编码一个字符串管理整个查询参数
自动拼 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: 类型fromdata
xhr.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"
100
true
null

对象形式

{
"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:3000
http://localhost:3000
//不同源(跨域)
http://localhost:3000
http://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.cn

JSONP#

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.responseText

responseType:指定响应数据类型

xhr.responseType = 'json';

可选值:

值含义
‘’默认字符串
text文本
jsonJSON对象
blob二进制文件
arraybuffer二进制数据
documentXML/HTML文档

timeout :请求超时时间,单位:毫秒(ms)

//open之后,send之前设置
xhr.timeout = 5000;//5秒后自动超时,连接取消

withCredentials :是否携带 Cookie

//默认false
xhr.withCredentials = true;//跨域请求
//使用Ajax发送请求,默认情况下,同域时,会携带Cookie;跨域时,不会
//xhr.withCredentials = true;
//最终能否成功跨域携带Cookie,还要看服务器同不同意,服务器必须指定域名,不能Access-Control-Allow-Origin: *

其他属性

readyState:表示请求当前的状态。

xhr.readyState
值状态含义
0UNSENTXHR 已创建,还没调用 open()
1OPENED已调用 open()
2HEADERS_RECEIVED已收到响应头
3LOADING正在接收响应体
4DONE请求完成

status:HTTP 状态码

xhr.status
200
201
204
404
500

statusText:状态码对应的描述

xhr.statusText
200 OK
404 Not Found
500 Internal Server Error

responseText:获取服务器返回的文本数据

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请求状态⭐⭐⭐⭐⭐
statusHTTP状态码⭐⭐⭐⭐⭐
responseText文本响应⭐⭐⭐⭐⭐
response响应数据⭐⭐⭐⭐
responseType响应类型⭐⭐⭐⭐
timeout超时时间⭐⭐⭐
withCredentials携带Cookie⭐⭐⭐⭐⭐
statusText状态描述⭐⭐⭐
upload上传进度⭐⭐⭐
responseXMLXML响应⭐⭐

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/json

getAllResponseHeaders():获取所有响应头

const headers =
xhr.getAllResponseHeaders();
console.log(headers);
content-type: application/json
content-length: 123
server: nginx

overrideMimeType() :修改响应的 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 IE10
xhr.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;
//ie8
xhr.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

//数据序列化成json
const serializeJSON = (param) => {
return JSON.stringify(param);
};
export { serialize, addURLData, serializeJSON };
  • 判断传的是fromdata、application/x-www-form-urlencoded还是json还是其他格式
  • setContentType设置请求头发送的数据格式

最终封装和修复bug

源码地址:https://github.com/youzi177/AjaxPackaging

测试API:https://github.com/youzi177/AjaxApi

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 对比

功能XHRFetch
Promise❌✅
async/await❌✅
自动 reject 404❌❌
自动 reject 网络错误❌✅
abortxhr.abort()AbortController
timeoutxhr.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();

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

前端Ajax&Fetch与跨域请求
https://f.youzi177.cn/posts/2026102205-qian-duan-ajaxfetch-yu-kua-yu-qing-qiu/
作者
小封鸭
发布于
2026-10-22
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
小封鸭
小楼一夜听春雨,深巷明朝卖杏花
公告
欢迎来到我的博客。这里主要记录前端全栈和日常内容
分类
标签
最新动态
站点统计
文章
60
分类
3
标签
12
总字数
107,734
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
EdgeOne Pages
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录