不定期更新,建议关注收藏点赞。
同源策略和跨域
- 同源:如果两个页面的协议,域名和端口都相同,则两个页面具有相同的源。反之,跨域。
- 同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。
MDN 官方给定的概念:同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。通俗的理解:浏览器规定,A 网站的 JavaScript,不允许和非同源的网站 C 之间,进行资源的交互,例如:
无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
无法接触非同源网页的 DOM
无法向非同源地址发送 Ajax 请求 - 浏览器对跨域请求拦截
JSONP允许跨域请求
⚠️ 注意:JSONP 本质上是注入一段 < script> 脚本,不是 AJAX,因此只能用于 GET 请求,而且存在一定的安全风险(容易被 XSS 攻击)。
JSONP (JSON with Padding) 是 JSON 的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。
JSONP,它有个限制,只能用GET请求,并且要求返回JavaScript。这种方式跨域实际上是利用了浏览器允许跨域引用JavaScript资源。由于JSONP通过 < script> 标签的 src 属性,来实现跨域数据获取的,所以,JSONP 只支持 GET 数据请求,不支持 POST 请求。
由于浏览器同源策略的限制,网页中无法通过 Ajax 请求非同源的接口数据。但是 < script> 标签不受浏览器同源策略的影响,可以通过 src 属性,请求非同源的 js 脚本。因此,JSONP 的实现原理,就是通过 < script> 标签的 src 属性,请求跨域的数据接口,并通过函数调用的形式,接收跨域接口响应回来的数据。
注意:JSONP 和 Ajax 之间没有任何关系,不能把 JSONP 请求数据的方式叫做 Ajax,因为 JSONP 没有用到 XMLHttpRequest 这个对象。
- url最后加上的?callback=abc 的意思是:“后端你别直接返回数据,你要调用我叫 abc 的函数,把数据当参数传给我。”这是 JSONP 的核心机制。
如果你用的是现代前端(比如 fetch 或 axios),那就不用 JSONP 了,因为现在可以用 CORS 跨域请求。但在一些老旧系统里 JSONP 还挺常见。
<script src="http://ajax.frontend.net:3006/api/jsonp?callback=success&name=zs&age=20"></script>
<script>
function success(data) {//回调函数
console.log('获取到了data数据:')
console.log(data)
}
</script>
<script>
function abc(data) {
console.log('拿到了Data数据:')
console.log(data)
}
</script>
<script src="./js/getdata.js?callback=abc"></script>
//getdata.js里的内容如下
abc({ name: 'ls', age: 30 })
/*
JSONP通常以函数调用的形式返回 foo('data');
如果在页面中先准备好foo()函数,
然后给页面动态加一个<script>节点,
相当于动态读取外域的JavaScript资源,最后就等着接收回调了
以163的股票查询URL为例 将得到如下返回:
refreshPrice({"0000001":{"code": "0000001", ... });
在页面中准备好回调函数:
*/
function refreshPrice(data) {
var p = document.getElementById('test-jsonp');
p.innerHTML = '当前价格:' +
data['0000001'].name +': ' +
data['0000001'].price + ';' +
data['1399001'].name + ': ' +
data['1399001'].price;
}
//最后用getPrice()函数触发
function getPrice() {
var
js = document.createElement('script'),
head = document.getElementsByTagName('head')[0];
js.src = 'http://api.money.126.net/data/feed/0000001,1399001?callback=refreshPrice';
//回调函数在url这里设定好了
head.appendChild(js);
}
//服务端返回格式(需支持 JSONP)
//服务端接收到 ?callback=handleResponse 后,应该返回如下内容
handleResponse({
name: 'Alice',
age: 25
});
- jQuery中的JSONP
jQuery 提供的 $.ajax() 函数,除了可以发起真正的 Ajax 数据请求之外,还能够发起 JSONP 数据请求。默认情况下,使用 jQuery 发起 JSONP 请求,会自动携带一个 callback=jQueryxxx 的参数,jQueryxxx 是随机生成的一个回调函数名称。
jQuery 中的 JSONP,也是通过 < script> 标签的 src 属性实现跨域数据访问的,只不过,jQuery 采用的是动态创建和移除 < script> 标签的方式,来发起 JSONP 数据请求。
在发起 JSONP 请求的时候,动态向 < header> 中 append 一个 < script> 标签;
在 JSONP 请求成功以后,动态从 < header> 中移除刚才 append 进去的 < script> 标签;
$.ajax({
url: 'http://ajax.frontend.net:3006/api/jsonp?name=zs&age=20',
// 如果要使用 $.ajax() 发起 JSONP 请求,必须指定 datatype 为 jsonp
dataType: 'jsonp',
success: function(res) {
console.log(res)
}
})
//自定义参数及回调函数名称
//使用 jQuery 发起 JSONP 请求时,
//如果想要自定义 JSONP 的参数以及回调函数名称,可以通过如下两个参数来指定:
$.ajax({
url: 'http://ajax.frontend.net:3006/api/jsonp?name=zs&age=20',
dataType: 'jsonp',
// 发送到服务端的参数名称,默认值为 callback
jsonp: 'callback',
// 自定义的回调函数名称,默认值为 jQueryxxx 格式
jsonpCallback: 'abc',
success: function(res) {
console.log(res)
}
})
CORS 允许跨域请求
CORS(Cross-Origin Resource Sharing,跨源资源共享)是一种 浏览器的安全机制,它允许服务器明确声明哪些外部域名可以访问它的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP 请求。
CORS需要浏览器和服务器同时支持,整个CORS过程都是浏览器完成的,无需用户参与。因此实现CORS的关键就是服务器,只要服务器实现了CORS请求,就可以跨源通信了。
- 简单请求/非简单请求
简单请求不会触发CORS预检请求。若该请求满足以下两个条件,就可以看作是简单请求:
1)请求方法是以下三种方法之一:
● HEAD
● GET
● POST
2)HTTP的头信息不超出以下几种字段:
● Accept
● Accept-Language
● Content-Language
● Last-Event-ID
● Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
若不满足以上条件,就属于非简单请求了。
(1)简单请求过程:
对于简单请求,浏览器会直接发出CORS请求,它会在请求的头信息中增加一个Origin字段,该字段用来说明本次请求来自哪个源(协议+端口+域名),服务器会根据这个值来决定是否同意这次请求。如果Orign指定的域名在许可范围之内,服务器返回的响应就会多出以下信息头:
Access-Control-Allow-Origin: http://api.bob.com //至少设置这个
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Expose-Headers: FooBar // 指定返回其他字段的值
Content-Type: text/html; charset=utf-8 // 表示文档类型
如果Orign指定的域名不在许可范围之内,服务器会返回一个正常的HTTP回应,浏览器发现没有上面的Access-Control-Allow-Origin头部信息,就知道出错了。这个错误无法通过状态码识别,因为返回的状态码可能是200。
(2)非简单请求过程
非简单请求是对服务器有特殊要求的请求,比如请求方法为DELETE或者PUT等。非简单请求的CORS请求会在正式通信之前进行一次HTTP查询请求,称为预检请求。
浏览器会询问服务器,当前所在的网页是否在服务器允许访问的范围内,以及可以使用哪些HTTP请求方式和头信息字段,只有得到肯定的回复,才会进行正式的HTTP请求,否则就会报错。
预检请求使用的请求方法是OPTIONS,表示这个请求是来询问的。他的头信息中的关键字段是Origin,表示请求来自哪个源。除此之外,头信息中还包括两个字段:
● Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法。
● Access-Control-Request-Headers: 该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。
服务器在收到浏览器的预检请求之后,会根据头信息的三个字段来进行判断,如果返回的头信息在中有Access-Control-Allow-Origin这个字段就是允许跨域请求,如果没有,就是不同意这个预检请求,就会报错。
Access-Control-Allow-Origin: http://api.bob.com // 允许跨域的源地址
Access-Control-Allow-Methods: GET, POST, PUT // 服务器支持的所有跨域请求的方法
Access-Control-Allow-Headers: X-Custom-Header // 服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true // 表示是否允许发送Cookie
Access-Control-Max-Age: 1728000 // 用来指定本次预检请求的有效期,单位为秒
/*
至少设置'Access-Control-Allow-Origin'
'Access-Control-Allow-Methods'
'Access-Control-Allow-Headers'
*/
只要服务器通过了预检请求,在以后每次的CORS请求都会自带一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
减少OPTIONS请求次数:
OPTIONS请求次数过多就会损耗页面加载的性能,降低用户体验度。所以尽量要减少OPTIONS请求次数,可以后端在请求的返回头部添加:Access-Control-Max-Age:number。它表示预检请求的返回结果可以被缓存多久,单位是秒。该字段只对完全一样的URL的缓存设置生效,所以设置了缓存时间,在这个时间范围内,再次发送请求就不需要进行预检请求了。
- 客户端发送的请求中添加
当一个前端页面试图向 不同源的服务器 发送请求时,浏览器会自动添加一些额外的 CORS 请求头(如 Origin),服务器需要响应特定的头,才允许这个跨域请求。
默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie.(服务端已设置Access-Control-Allow-Credentials 设置为 true)
// 原生 xml 的设置方式
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// axios 设置方式
axios.defaults.withCredentials = true;
/*
如果是 复杂请求(例如使用了 PUT、DELETE 方法,
或自定义头部),浏览器会先发送一个 OPTIONS 请求,
询问服务器是否允许。
*/
OPTIONS /api/data HTTP/1.1
Origin: https://frontend.com
Access-Control-Request-Method: PUT
//or
GET /data HTTP/1.1
Origin: https://frontend.com
- 服务器返回的常见的 CORS 响应头
响应头 | 说明 |
---|---|
Access-Control-Allow-Origin | 指定允许访问的域名,或者 * 表示所有域名 |
Access-Control-Allow-Methods | 允许的 HTTP 方法(GET, POST 等) |
Access-Control-Allow-Headers | 允许的自定义头部字段 |
Access-Control-Allow-Credentials | 是否允许发送 Cookie(需设置为 true) |
Access-Control-Expose-Headers | 浏览器可以访问的响应头 |
Origin表示本域,也就是浏览器当前页面的域。当JavaScript向外域(如sina.com)发起请求后,浏览器收到响应后,首先检查Access-Control-Allow-Origin是否包含本域,如果是,则此次跨域请求成功,如果不是,则请求失败,JavaScript将无法获取到响应的任何数据。
假设本域是my.com,外域是sina.com,只要响应头Access-Control-Allow-Origin为http://my.com,或者是*,本次请求就可以成功。跨域能否成功,取决于对方服务器是否愿意给你设置一个正确的Access-Control-Allow-Origin,决定权始终在对方手中。这种跨域请求,称之为“简单请求”。简单请求包括GET、HEAD和POST
Content-Type:每个文件扩展名对应一个Content-Type
在引用外域资源时,除了JavaScript和CSS外,都要验证CORS
例如,当你引用了某个第三方CDN上的字体文件时:
@font-face
这个规则允许在web页面上加载自定义字体,
对自定义字体有不同程度的支持,
但是这个规则接受语句创建和提供这些字体。
@font-face {
font-family: 'FontAwesome';
src: url('http://cdn.com/fonts/fontawesome.ttf') format('truetype');
}
浏览器确认服务器响应的Access-Control-Allow-Methods头确实包含将要发送的AJAX请求的Method,才会继续发送AJAX,否则,抛出一个错误。
http跨域时的options请求
出于安全考虑,并不是所有域名访问后端服务都可以。其实在正式跨域之前,浏览器会根据需要发起一次预检(也就是option请求),用来让服务端返回允许的方法(如get、post),被跨域访问的Origin(来源或者域),还有是否需要Credentials(认证信息)等
浏览器的同源策略导致的。默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。
完全一致的意思是,域名要相同(www.example.com和example.com不同),协议要相同(http和https不同),端口号要相同(默认是:80端口,它和:8080就不同)
解决跨域问题
用JavaScript怎么请求外域(就是其他网站)的URL了呢?
- CORS
支持 GET 和 POST 请求。缺点是不兼容某些低版本的浏览器。
1) 使用 fetch() API(推荐)
现代浏览器支持 fetch(),可以发起跨域请求,但前提是目标网站支持 CORS。如果目标网站没有明确设置允许跨域访问,那么请求会被浏览器阻止。如果目标网站支持 CORS 并正确设置了头部(例如 Access-Control-Allow-Origin),请求就会成功。
fetch('https://example.com/data', {
method: 'GET', // 或者 'POST',取决于你的请求类型
headers: {
'Content-Type': 'application/json'
},
mode: 'cors' // 必须设置为 'cors',表示进行跨域请求
})
.then(response => response.json()) // 解析 JSON 数据
.then(data => console.log(data)) // 处理返回的响应
.catch(error => console.error('Error:', error)); // 错误处理
fetch('https://example.com')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text(); // 将响应转换为文本(HTML)
})
.then(html => {
console.log(html); // 打印出返回的 HTML 文本
})
.catch(error => {
console.error('There has been a problem with your fetch operation:', error);
});
2)使用 XMLHttpRequest(旧版)
XMLHttpRequest 是发起 HTTP 请求的传统方式,它也可以用于发起跨域请求,但依赖于目标网站的 CORS 设置。
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/data', true);
//第三个参数 是否异步(asynchronous)
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.send();
3)服务器设置 CORS 响应头(推荐)
例如在 Node.js 使用 cors 中间件:
const cors = require('cors');
app.use(cors({ origin: 'https://frontend.com' }));
- JSONP
兼容性好(兼容低版本IE)。是前端程序员为了解决跨域问题,被迫想出来的一种临时解决方案。缺点是只支持 GET 请求,不支持 POST 请求。 - nginx代理跨域
nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。 - nodejs 中间件代理跨域
node中间件实现跨域代理,原理大致与nginx相同,都是通过开启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
使用node + express + http-proxy-middleware搭建一个proxy服务器。 - 是通过在同源域名下架设一个代理服务器来转发,JavaScript负责把请求发送到代理服务器:
'/proxy?url=http://www.sina.com.cn'
代理服务器再把结果返回,这样就遵守了浏览器的同源策略。这种方式麻烦之处在于需要服务器端额外做开发。
是上面第4点的手动开发版。第4点,用 http-proxy-middleware, 相当于用工具帮你搭桥,搭桥过程它都帮你处理好了。第5点,自己写 /proxy 接口 ,相当于自己一砖一瓦搭桥,虽然累但更自由。
实例:淘宝搜索
<div class="box">
<!-- tab 栏区域 -->
<div class="tabs"></div>
<!-- 搜索区域 -->
<div class="search-box"></div>
<!-- 搜索建议列表 -->
<div id="suggest-list"></div>
</div>
<!-- 模板结构 -->
<script type="text/html" id="tpl-suggestList">
{{each result}}
<div class="suggest-item">{{$value[0]}}</div>
{{/each}}
</script>
// 渲染建议列表
function renderSuggestList(res) {
// 如果没有需要渲染的数据,则直接 return
if (res.result.length <= 0) {
return $('#suggest-list').empty().hide()
}
// 渲染模板结构
var htmlStr = template('tpl-suggestList', res)
$('#suggest-list').html(htmlStr).show()
}
// 监听文本框的 keyup 事件
$('#ipt').on('keyup', function() {
// 获取用户输入的内容
var keywords = $(this).val().trim()
// 判断用户输入的内容是否为空
if (keywords.length <= 0) {
return $('#suggest-list').empty().hide()
}
// TODO:获取搜索建议列表
getSuggestList(keywords);
})
function getSuggestList(kw) {
$.ajax({
// 指定请求的 URL 地址,其中,q 是用户输入的关键字
url: 'https://suggest.taobao.com/sug?q=' + kw,
// 指定要发起的是 JSONP 请求
dataType: 'jsonp',
// 成功的回调函数
success: function(res) {
console.log(res)
renderSuggestList(res)
}
})
}
防抖策略
防抖策略(debounce)是当事件被触发后,延迟 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。
- 输入防抖debounce
用户在输入框中连续输入一串字符时,可以通过防抖策略,只在输入完成后,才执行查询的请求,这样可以有效减少请求次数,节约请求资源;
var timer = null // 1. 防抖动的 timer
function debounceSearch(keywords) { // 2. 定义防抖的函数
timer = setTimeout(function() {
// 发起 JSONP 请求
getSuggestList(keywords)
}, 500)
}
$('#ipt').on('keyup', function() {
// 3. 在触发 keyup 事件时,立即清空 timer
clearTimeout(timer)
// ...省略其他代码
debounceSearch(keywords)
})
- 缓存搜索建议列表
//1. 定义全局缓存对象
var cacheObj={}
// 2. 渲染建议列表并缓存
function renderSuggestList(res) {
// ...省略其他代码
// 将搜索的结果,添加到缓存对象中
var k = $('#ipt').val().trim()
cacheObj[k] = res
}
//3. 优先从缓存中获取搜索建议
// 监听文本框的 keyup 事件
$('#ipt').on('keyup', function() {
// ...省略其他代码
// 优先从缓存中获取搜索建议
if (cacheObj[keywords]) {
return renderSuggestList(cacheObj[keywords])
}
// 获取搜索建议列表
debounceSearch(keywords)
})
节流策略
节流策略(throttle):减少一段时间内事件的触发频率。
节流的应用场景:
鼠标连续不断地触发某事件(如点击),只在单位时间内只触发一次;
懒加载时要监听计算滚动条的位置,但不必每次滑动都触发,可以降低计算的频率,而不必去浪费 CPU 资源;
- 节流阀
节流阀为空,表示可以执行下次操作;不为空,表示不能执行下次操作。
当前操作执行完,必须将节流阀重置为空,表示可以执行下次操作了。
每次执行操作前,必须先判断节流阀是否为空。 - 实例:鼠标跟随效果
<!-- UI 结构 -->
<img src="./assets/angel.gif" alt="" id="angel" />
/* CSS 样式 */
html, body {
margin: 0;
padding: 0;
overflow: hidden;
}
#angel {
position: absolute;
}
//不节流版
$(function() {
// 获取图片元素
var angel = $('#angel')
// 监听文档的 mousemove 事件
$(document).on('mousemove', function(e) { // 设置图片的位置
$(angel).css('left', e.pageX + 'px').css('top', e.pageY + 'px')
})
})
//节流优化后
$(function() {
var angel = $('#angel')
var timer = null // 1.预定义一个 timer 节流阀
$(document).on('mousemove', function(e) {
if (timer) { return } // 3.判断节流阀是否为空,如果不为空,则证明距离上次执行间隔不足16毫秒
timer = setTimeout(function() {
$(angel).css('left', e.pageX + 'px').css('top', e.pageY + 'px')
timer = null // 2.当设置了鼠标跟随效果后,清空 timer 节流阀,方便下次开启延时器
}, 16)
})
})
- 节流和防抖的区别
防抖:如果事件被频繁触发,完全相同的事件,防抖保证最多一次触发,前面 N 多次的中途过程操作的触发都会被忽略。
节流:如果事件被频繁触发,节流能够减少事件触发的频率,节流是有间隔的执行事件。
URL编码
URL 地址中,只允许出现英文相关的字母、标点符号、数字,因此,在 URL 地址中不允许出现中文字符。
如果 URL 中需要包含中文这样的字符,则必须对中文字符进行编码(转义)。
URL编码的原则:使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。
- URL编码和解码的API
由于浏览器会自动对 URL 地址进行编码操作,因此,大多数情况下,程序员不需要关心 URL 地址的编码与解码操作。
encodeURI('黑马程序员')
// 输出字符串 %E9%BB%91%E9%A9%AC%E7%A8%8B%E5%BA%8F%E5%91%98
decodeURI('%E9%BB%91%E9%A9%AC')
// 输出字符串 黑马
Axios
axios
是一个基于Promise
的 JavaScript HTTP 客户端,用于浏览器和 Node.js 中发送 HTTP 请求,并处理请求的结果。axios
主要用于与服务器进行数据交互,比如发送 GET、POST、PUT、DELETE 请求等。相比于浏览器原生的fetch
API,axios
更多功能、更简单易用、相比jQuery更轻量化- 安装axios
如果你使用的是 Node.js 或者 React 等项目,首先需要通过npm
或yarn
安装axios
。
# 使用 npm 安装
npm install axios
# 使用 yarn 安装
yarn add axios
- 使用
axios
提供了多种方法来发送 HTTP 请求,包括axios.get()
、axios.post()
、axios.put()
、axios.delete()
等。
axios.get('url', { params: { /*参数*/ } }).then(callback)
import axios from 'axios';
axios.get('https://api.example.com/data')
.then(response => {
console.log(response.data); // 响应数据
})
.catch(error => {
console.error('Error fetching data:', error);
});
// 请求的 URL 地址
var url = 'http://test.top:3006/api/get'
// 请求的参数对象
var paramsObj = { name: 'zs', age: 20 }
// 调用 axios.get() 发起 GET 请求
axios.get(url, { params: paramsObj }).then(function(res) {
// res.data 是服务器返回的数据
var result = res.data
console.log(res)
})
axios.get('/api/todolist')
.then((res)=>{
console.log(res.data);
this.setState(()=>{
return {
list: [...res.data]
}
})
.catch(()=>{alert('error')})
}
axios.post('url', { /*参数*/ }).then(callback)
const data = {
name: 'John Doe',
email: 'john@example.com'
};
axios.post('https://api.example.com/users', data)
.then(response => {
console.log('User created:', response.data);
})
.catch(error => {
console.error('Error posting data:', error);
});
//axios 也提供了类似于 jQuery 中 $.ajax() 的函数
axios({
method: '请求类型',
url: '请求的URL地址',
data: { /* POST数据 */ },
params: { /* GET参数 */ }
}) .then(callback)
axios({
method: 'GET',
url: 'http:/test.top:3006/api/get',
params: { // GET 参数要通过 params 属性提供
name: 'zs',
age: 20
}
}).then(function(res) {
console.log(res.data)
})
axios({
method: 'POST',
url: 'http://test.top:3006/api/post',
data: { // POST 数据要通过 data 属性提供
bookname: '程序员的自我修养',
price: 666
}
}).then(function(res) {
console.log(res.data)
})
发送 PUT 请求
import axios from 'axios';
const updatedData = {
name: 'Jane Doe',
email: 'jane@example.com'
};
axios.put('https://api.example.com/users/1', updatedData)
.then(response => {
console.log('User updated:', response.data);
})
.catch(error => {
console.error('Error updating data:', error);
});
发送 DELETE 请求
import axios from 'axios';
axios.delete('https://api.example.com/users/1')
.then(response => {
console.log('User deleted:', response.data);
})
.catch(error => {
console.error('Error deleting data:', error);
});
- 请求配置
axios
支持配置请求的各个方面,如 headers、params、timeout 等。你可以通过第二个参数传递一个配置对象来设置这些选项。
示例:设置请求头
import axios from 'axios';
axios.get('https://api.example.com/data', {
headers: {
'Authorization': 'Bearer your_token_here'
}
})
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Error:', error);
});
//示例:设置请求超时
axios.get('https://api.example.com/data', {
timeout: 5000 // 设置超时时间为 5 秒
})
.then(response => {
console.log(response.data);
})
.catch(error => {
if (error.code === 'ECONNABORTED') {
console.log('Request timeout');
} else {
console.error('Error:', error);
}
});
- 拦截器(Interceptors)
axios
提供了拦截器机制,使得你可以在请求发送之前或响应返回之后执行一些逻辑。
请求拦截器可以在请求发送之前修改请求的配置。
响应拦截器可以在响应返回之后处理响应数据或者捕获错误。
import axios from 'axios';
axios.interceptors.request.use(config => {
console.log('Request Interceptor:', config);
// 在发送请求之前做些什么,比如添加 token 等
config.headers['Authorization'] = 'Bearer your_token_here';
return config;
}, error => {
return Promise.reject(error);
});
//响应拦截器
axios.interceptors.response.use(response => {
console.log('Response Interceptor:', response);
return response;
}, error => {
console.error('Response Error:', error);
return Promise.reject(error);
});
- 取消请求
有时你可能需要取消一个正在进行的请求,axios
提供了CancelToken
来实现这一功能。
import axios from 'axios';
const cancelToken = axios.CancelToken;
const source = cancelToken.source();
axios.get('https://api.example.com/data', {
cancelToken: source.token
})
.then(response => {
console.log(response.data);
})
.catch(error => {
if (axios.isCancel(error)) {
console.log('Request canceled:', error.message);
} else {
console.error('Error:', error);
}
});
// 取消请求
source.cancel('Operation canceled by the user.');
- 并发请求
axios
还可以让你同时发送多个请求,并在它们全部完成后进行处理,使用axios.all
和axios.spread
来实现。
import axios from 'axios';
const request1 = axios.get('https://api.example.com/data1');
const request2 = axios.get('https://api.example.com/data2');
axios.all([request1, request2])
.then(axios.spread((response1, response2) => {
console.log('Response 1:', response1.data);
console.log('Response 2:', response2.data);
}))
.catch(error => {
console.error('Error:', error);
});
- 处理错误
axios
会将所有错误统一通过catch
捕获。你可以通过error.response
来访问服务器返回的错误信息。
import axios from 'axios';
axios.get('https://api.example.com/data')
.then(response => {
console.log(response.data);
})
.catch(error => {
if (error.response) {
// 服务器返回了响应
console.error('Response error:', error.response);
} else if (error.request) {
// 请求已发出但没有收到响应
console.error('Request error:', error.request);
} else {
// 发生了其他错误
console.error('Error:', error.message);
}
});
- axios v.s. AJAX v.s. fetch
axios
在前端开发中具有较高的使用率,fetch
作为原生 API 也被广泛采用,而 XMLHttpRequest
的使用率相对较低。
特性 | AJAX (XMLHttpRequest ) | Fetch API | Axios |
---|---|---|---|
基于 | 回调函数 | Promise | Promise |
请求响应处理 | 需要手动管理请求状态和解析响应 | 自动解析 JSON,但仍需要手动处理错误 | 自动解析 JSON,无需手动处理响应数据 |
请求和响应拦截器 | 无 | 无 | 支持请求和响应拦截器 |
跨域请求 | 需要额外配置 CORS 头 | 需要额外配置 CORS 头 | 支持自动处理跨域请求(需要后端支持) |
请求取消 | 需要使用 AbortController | 需要使用 AbortController | 支持请求取消,直接使用 CancelToken |
浏览器兼容性 | 在所有浏览器中都能工作,但老旧浏览器不支持 | 大多数现代浏览器支持,但 IE 需要 polyfill | 支持现代浏览器和 IE(需要 polyfill) |
支持的功能 | 基本的 HTTP 请求功能 | 基本的 HTTP 请求功能 | 更丰富的功能:请求/响应拦截器、取消请求等 |
使用难度 | 相对较复杂,需要手动管理请求状态和响应解析 | 简单,支持 Promise ,且 API 直观 | 简单,且有更多的功能和配置选项 |
- axios与python requests库
功能 | Axios (JavaScript) | Requests (Python) |
---|---|---|
异步操作 | 基于 Promise ,支持 async/await | 同步请求,需要配合 aiohttp 异步库 |
自动解析响应 | 自动解析 JSON | 自动解析 JSON |
请求拦截器/响应拦截器 | 支持请求和响应拦截器 | 不支持直接的拦截器功能 |
取消请求 | 支持 CancelToken 来取消请求 | 无原生支持取消请求 |
跨域请求 | 处理跨域请求(CORS) | 不涉及浏览器,跨域由后端控制 |
浏览器支持 | 支持现代浏览器 | 仅支持 Python 环境 |
低级 HTTP 请求通常是指在较低的抽象层次上进行的 HTTP 请求,通常需要开发者手动处理许多细节,例如请求头、参数、响应处理、编码等。相比高级 HTTP 请求,低级 HTTP 请求提供了更多的灵活性和控制,但也要求开发者更了解底层的 HTTP 协议和细节。
python中类似的库:requests、httpx、aiohttp、urllib、pycurl、tornado,后端开发使用。
AJAX
AJAX(Asynchronous JavaScript and XML)简称xhr,是基于 JavaScript 的技术,用于实现网页的异步请求和更新。
它允许网页在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容。这种方式能提高用户体验,因为它使得网页加载更加流畅,用户可以与页面进行交互,而无需等待页面的完全刷新。
尽管它的名字中有 “XML”,但现在 AJAX 主要用于通过 JSON(JavaScript Object Notation)与服务器交换数据。不过,它也支持通过 XML、HTML 或纯文本等格式进行数据交换。
jQuery已将其使用大大简化,详见jQuery一本通
jQuery基于xhr对象封装了Ajax函数
- AJAX 的工作原理
AJAX 使用 JavaScript 与服务器进行通信,通过 XMLHttpRequest 对象或现代的 fetch API 来发送请求。如果要让用户留在当前页面中,同时发出新的HTTP请求,就必须用JavaScript发送这个新请求,接收到数据后,再用JavaScript更新页面,这样一来,用户就感觉自己仍然停留在当前页面,但是数据却可以不断地更新。例如Gmail的页面在首次加载后,剩下的所有数据都依赖于AJAX来更新。
需要注意:AJAX请求是异步执行的,要通过回调函数获得响应。
使用
- 用户发起请求。创建xhr对象,调用xhr.open(),【post:设置 Content-Type 属性(固定写法)】xhr.send(),【post:同时指定要发送的数据】,监听xhr.onreadystatechange事件
// 1. 创建 XHR 对象
var xhr = new XMLHttpRequest()
// 2. 调用 open 函数,指定 请求方式 与 URL地址
xhr.open('GET', 'http://test.top:3006/api/getbooks')
// 3. 调用 send 函数,发起 Ajax 请求
xhr.send()
// 4. 监听 onreadystatechange 事件
xhr.onreadystatechange = function() {
// 4.1 监听 xhr 对象的请求状态 readyState ;与服务器响应的状态 status
if (xhr.readyState === 4 && xhr.status === 200) {
// 4.2 打印服务器响应回来的数据
console.log(xhr.responseText)
}
}
- JavaScript 使用 XMLHttpRequest 或 fetch() 向服务器发送请求。
//使用 xhr 对象发起带参数的 GET 请求时,
//只需在调用 xhr.open 期间,为 URL 地址指定参数即可
// ...省略不必要的代码
xhr.open('GET', 'http://test.top:3006/api/getbooks?id=1&bookname=a')
// ...省略不必要的代码
//无论使用 $.ajax(),还是使用 $.get(),
//又或者直接使用 xhr 对象发起 GET 请求,
//本质上,都是直接将参数以查询字符串的形式,追加到 URL 地址的后面,发送到服务器的。
$.get('url', {name: 'zs', age: 20}, function() {})
// 等价于
$.get('url?name=zs&age=20', function() {})
$.ajax({ method: 'GET', url: 'url', data: {name: 'zs', age: 20}, success: function() {} })
// 等价于
$.ajax({ method: 'GET', url: 'url?name=zs&age=20', success: function() {} })
- 使用xhr发起POST请求
// 1. 创建 xhr 对象
var xhr = new XMLHttpRequest()
// 2. 调用 open()
xhr.open('POST', 'http://test.top:3006/api/addbook')
// 3. 设置 Content-Type 属性(固定写法)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
// 4. 调用 send(),同时将数据以查询字符串的形式,提交给服务器
xhr.send('bookname=水浒传&author=施耐庵&publisher=天津图书出版社')
// 5. 监听 onreadystatechange 事件
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
- 服务器接收请求,处理数据,并返回响应。
- JavaScript 处理服务器返回的数据,并更新页面内容。
- 自己封装ajax
function resolveData(data) {
var arr = []
for (var k in data) {
var str = k + '=' + data[k]
arr.push(str)
}
return arr.join('&')
}
// var res = resolveData({ name: 'zs', age: 20 })
// console.log(res)
function my_request(options) {
var xhr = new XMLHttpRequest()
// 把外界传递过来的参数对象,转换为 查询字符串
var qs = resolveData(options.data)
if (options.method.toUpperCase() === 'GET') {
// 发起GET请求
xhr.open(options.method, options.url + '?' + qs)
xhr.send()
} else if (options.method.toUpperCase() === 'POST') {
// 发起POST请求
xhr.open(options.method, options.url)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(qs)
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var result = JSON.parse(xhr.responseText)
options.success(result)
}
}
}
my_request({
method: '请求类型',
url: '请求地址',
data: { /* 请求参数对象 */ },
success: function(res) { // 成功的回调函数
console.log(res) // 打印数据
}
})
- XMLHttpRequest Level2的新功能
- 可以设置 HTTP 请求的时限
- 可以使用 FormData 对象管理表单数据
- 可以上传文件
实现步骤:
定义 UI 结构
验证是否选择了文件
向 FormData 中追加文件
使用 xhr 发起上传文件的请求
监听 onreadystatechange 事件 - 可以获得数据传输的进度信息
xhr.timeout = 3000//过了这个时限,就自动停止HTTP请求。
xhr.ontimeout = function(event){//超时回调函数
alert('请求超时!')
}
// FormData对象管理表单数据
// 1. 新建 FormData 对象
var fd = new FormData()
// 2. 为 FormData 添加表单项
fd.append('uname', 'zs')
fd.append('upwd', '123456')
// 3. 创建 XHR 对象
var xhr = new XMLHttpRequest()
// 4. 指定请求类型与URL地址
xhr.open('POST', 'http://test.top:3006/api/formdata')
// 5. 直接提交 FormData 对象,这与提交网页表单的效果,完全一样
xhr.send(fd)
//FormData对象也可以用来获取网页表单的值
// 获取表单元素
var form = document.querySelector('#form1')
// 监听表单元素的 submit 事件
form.addEventListener('submit', function(e) {
e.preventDefault()
// 根据 form 表单创建 FormData 对象,会自动将表单数据填充到 FormData 对象中
var fd = new FormData(form)
var xhr = new XMLHttpRequest()
xhr.open('POST', 'http://test.top:3006/api/formdata')
xhr.send(fd)
xhr.onreadystatechange = function() {}
})
//上传文件
//1.html定义UI结构
<!-- 1. 文件选择框 -->
<input type="file" id="file1" />
<!-- 2. 上传按钮 -->
<button id="btnUpload">上传文件</button>
<br />
<!-- 3. 显示上传到服务器上的图片 -->
<img src="" alt="" id="img" width="800" />
//2.验证是否选择文件
// 1. 获取上传文件的按钮
var btnUpload = document.querySelector('#btnUpload')
// 2. 为按钮添加 click 事件监听
btnUpload.addEventListener('click', function() {
// 3. 获取到选择的文件列表
var files = document.querySelector('#file1').files
if (files.length <= 0) {
return alert('请选择要上传的文件!')
}
// ...后续业务逻辑
})
//3.追加文件
// 1. 创建 FormData 对象
var fd = new FormData()
// 2. 向 FormData 中追加文件
fd.append('avatar', files[0])
//4.发出请求
// 1. 创建 xhr 对象
var xhr = new XMLHttpRequest()
// 2. 调用 open 函数,指定请求类型与URL地址。其中,请求类型必须为 POST
xhr.open('POST', 'http://test.top:3006/api/upload/avatar')
// 3. 发起请求
xhr.send(fd)
//5. 监听 onreadystatechange 事件
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
var data = JSON.parse(xhr.responseText)
if (data.status === 200) { // 上传文件成功
// 将服务器返回的图片地址,设置为 <img> 标签的 src 属性
document.querySelector('#img').src = 'http://test.top:3006' + data.url
} else { // 上传文件失败
console.log(data.message)
}
}
}
//显示文件上传进度
// 创建 XHR 对象
var xhr = new XMLHttpRequest()
// 监听 xhr.upload 的 onprogress 事件
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
// 1. 计算出当前上传进度的百分比
var percentComplete = Math.ceil((e.loaded / e.total) * 100)
$('#percent')
// 2. 设置进度条的宽度
.attr('style', 'width:' + percentComplete + '%')
// 3. 显示当前的上传进度百分比
.html(percentComplete + '%')
}
}
//基于Bootstrap渲染进度条
<link rel="stylesheet" href="./lib/bootstrap.css" />
<script src="./lib/jquery.js"></script>
<!-- 进度条 -->
<div class="progress" style="width: 500px; margin: 10px 0;">
<div class="progress-bar progress-bar-info progress-bar-striped active" id="percent" style="width: 0%">
0%
</div>
</div>
//监听上传完成的事件
xhr.upload.onload = function() {
$('#percent')
// 移除上传中的类样式
.removeClass()
// 添加上传完成的类样式
.addClass('progress-bar progress-bar-success')
}
什么是 XMLHttpRequest 对象?
简称xhr,是浏览器提供的js成员
XMLHttpRequest 对象用于在后台与服务器交换数据。
XMLHttpRequest 对象能够:
在不重新加载页面的情况下更新网页
在页面已加载后从服务器请求数据
在页面已加载后从服务器接收数据
在后台向服务器发送数据
通过检测window对象是否有XMLHttpRequest属性来确定浏览器是否支持标准的XMLHttpRequest。注意,不要根据浏览器的navigator.userAgent来检测浏览器是否支持某个JavaScript特性,一是因为这个字符串本身可以伪造,二是通过IE版本判断JavaScript特性将非常复杂。
当创建了XMLHttpRequest对象后,要先设置onreadystatechange的回调函数。在回调函数中,通常我们只需通过readyState === 4
判断请求是否完成,如果已完成,再根据status === 200
判断是否是一个成功的响应。
responseText 只适用于文本响应。如果服务器返回的是 JSON 或 XML 数据,可以使用 JSON.parse(req.responseText) 来将其转换为 JavaScript 对象,或使用 req.responseXML 获取 XML 格式的响应。
XMLHttpRequest对象的open()方法有3个参数,第一个参数指定是GET还是POST,第二个参数指定URL地址,第三个参数指定是否使用异步,默认是true,所以不用写。注意,千万不要把第三个参数指定为false,否则浏览器将停止响应,直到AJAX请求完成。如果这个请求耗时10秒,那么10秒内你会发现浏览器处于“假死”状态。
最后调用send()方法才真正发送请求。GET请求不需要参数,POST请求需要把body部分以字符串或者FormData对象传进去。
AJAX - onreadystatechange 事件
当请求被发送到服务器时,我们需要执行一些基于响应的任务。
每当 readyState 改变时,就会触发 onreadystatechange 事件。
readyState 属性存有 XMLHttpRequest 的状态信息。
在 onreadystatechange 事件中,我们规定当服务器响应已做好被处理的准备时所执行的任务。
readyState 表示当前请求的状态。XMLHttpRequest 的请求生命周期有 5 个阶段(0 到 4),表示请求的不同状态:
0 - UNSENT:对象已创建,但尚未调用 open() 方法。
1 - OPENED:已调用 open(),但尚未调用 send()。
2 - HEADERS_RECEIVED:已发送请求,并且已接收到响应头。
3 - LOADING:正在下载响应体。
4 - DONE:响应已完全下载,处理完成。成功或失败
status 表示 HTTP 响应的状态码。
var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象
request.onreadystatechange = function () { // 状态发生变化时,函数被回调
if (request.readyState === 4) { // 成功完成
// 判断响应结果:
if (request.status === 200) {
// 成功,通过responseText拿到响应的文本:
return success(request.responseText);
} else {
// 失败,根据响应码判断失败原因:
return fail(request.status);
}
} else {
// HTTP请求还在继续...
}
}
// 发送请求:
request.open('GET', '/api/categories');
request.send();
对于低版本的IE,需要换成ActiveXObject对象
标准写法和IE写法混在一起,可以这么写:
var request;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
} else {
request = new ActiveXObject('Microsoft.XMLHTTP');
}
- 例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AJAX Example</title>
</head>
<body>
<h1>AJAX Example</h1>
<button id="loadData">Load Data</button>
<div id="result"></div> <!-- 结果将显示在这里 -->
<script src="app.js"></script>
</body>
</html>
// 获取按钮和结果展示区域
const button = document.getElementById('loadData');
const result = document.getElementById('result');
// 添加点击事件监听器
button.addEventListener('click', function() {
// 创建一个新的 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();
// 配置请求:GET 请求,指向一个 JSON 文件
xhr.open('GET', 'data.json', true); // 异步请求
// 设置 onload 事件处理函数
xhr.onload = function() {
if (xhr.status === 200) { // 如果请求成功
const data = JSON.parse(xhr.responseText); // 解析返回的 JSON 数据
result.innerHTML = `Message: ${data.message}`; // 显示结果
} else {
result.innerHTML = 'Error loading data';
}
};
// 发送请求
xhr.send();
});
XMLHttpRequest对象的open()方法有3个参数,第一个参数指定是GET还是POST,第二个参数指定URL地址,第三个参数指定是否使用异步,默认是true,所以不用写。
注意,尽量不要把第三个参数指定为false,否则浏览器将停止响应,直到AJAX请求完成。如果这个请求耗时10秒,那么10秒内你会发现浏览器处于“假死”状态。
最后调用send()方法才真正发送请求。GET请求不需要参数,POST请求需要把body部分以字符串或者FormData对象传进去。
- 上面代码的URL使用的是相对路径。如果你把它改为’http://www.sina.com.cn/',再运行,肯定报错。在Chrome的控制台里,还可以看到错误信息。
这是因为浏览器的同源策略导致的。默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。
完全一致的意思是,域名要相同(www.example.com和example.com不同),协议要相同(http和https不同),端口号要相同(默认是:80端口,它和:8080就不同)使用 JavaScript 请求外域(即跨域请求)时,通常会遇到 跨域资源共享 (CORS, Cross-Origin Resource Sharing) 的限制。默认情况下,浏览器会阻止网页发起跨域请求,以保护用户的安全。为了绕过这个限制,目标网站必须明确允许你的域名进行请求。
Fetch
- 例子:使用 fetch() API 替代 XMLHttpRequest
fetch() 是现代浏览器提供的一个 API,使用 Promise,比 XMLHttpRequest 更简洁,更强大的功能:fetch() 支持更多的功能,如设置请求头、控制超时等。并且它默认支持异步操作。这里是相同请求的 fetch() 版本:
const button = document.getElementById('loadData');
const result = document.getElementById('result');
button.addEventListener('click', function() {
// 使用 fetch 发起请求
fetch('data.json')
.then(response => response.json()) // 解析返回的 JSON
.then(data => {
result.innerHTML = `Message: ${data.message}`; // 显示数据
})
.catch(error => {
result.innerHTML = 'Error loading data';
console.error(error);
});
});