旧版XMLHttpRequest的缺点
只支持文本数据的传输,无法用来读取和上传文件
传送和接收数据时,没有进度信息,只能提示有没有完成
XMLHttpRequest Level2
XMLHttpRequest Level2的新功能
可以设置 HTTP 请求的时限
可以使用 FormData 对象管理表单数据
可以上传文件
可以获得数据传输的进度信息
设置HTTP请求时限
有时,Ajax 操作很耗时,而且无法预知要花多少时间。如果网速很慢,用户可能要等很久。
新版本的 XMLHttpRequest 对象,增加了 timeout 属性,可以设置 HTTP 请求的时限:
xhr.timeout = 3000
上面的语句,将最长等待时间设为 3000 毫秒。过了这个时限,就自动停止HTTP请求。与之配套的还有一个 timeout 事件,用来指定回调函数:
var xhr = new XMLHttpRequest()
// 设置 超时时间
xhr.timeout = 3
// 设置超时以后的处理函数
xhr.ontimeout = function() {
console.log('请求超时了!')
}
xhr.open('GET', 'http://www.liulongbin.top:3006/api/getbooks')
xhr.send()
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
FormData对象管理表单数据
Ajax 操作往往用来提交表单数据。为了方便表单处理,HTML5 新增了一个 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://www.liulongbin.top:3006/api/formdata')
// 5. 直接提交 FormData 对象,这与提交网页表单的效果,完全一样
xhr.send(fd)
FormData对象用以将数据编译成键值对,以便用XMLHttpRequest来发送数据。
其主要用于发送表单数据,但亦可用于发送带键数据(key data),而独立于表单使用。
如果表单enctype属性设为multipart/form-data ,则会使用表单的submit()方法来发送数据,从而,发送数据具有同样形式。key是唯一的,一个key可能对应多个value。
如果是使用表单初始化,每一个表单字段对应一条数据,它们的HTML name属性即为key值,它们value属性对应value值。
作用
1.模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。
2.异步上传二进制文件
使用
<body>
<form id="form1">
<input type="text" name="uname" autocomplete="off" />
<input type="password" name="upwd" />
<button type="submit">提交</button>
</form>
<script>
// 1. 通过 DOM 操作,获取到 form 表单元素
var form = document.querySelector('#form1')
form.addEventListener('submit', function (e) {
// 阻止表单的默认提交行为
e.preventDefault()
// 创建 FormData,快速获取到 form 表单中的数据
var fd = new FormData(form)
var xhr = new XMLHttpRequest()
xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata')
xhr.send(fd)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText))
}
}
})
</script>
</body>
注意:
Formdata 对象不能用于 get 请求,因为对象需要被传递到 send 方法中,而 get 请求方式的请求参数只能放在请求地址的后面。
服务器端 bodyParser 模块不能解析 formData 对象表单数据,我们需要使用 formidable 模块进行解析。
实例方法
1.获取表单对象中属性的值
formData.get('key');
2.设置表单对象中属性的值
formData.set('key', 'value');
3.删除表单对象中属性的值
formData.delete('key');
4.向表单对象中追加属性值
formData.append('key', 'value');
注:set 方法与 append 方法的区别是,在属性名已存在的情况下,set 会覆盖已有键名的值,append会保留两个值。
二进制文件上传
新版 XMLHttpRequest 对象,不仅可以发送文本信息,还可以上传文件。
实现步骤:
定义 UI 结构
<!-- 1. 文件选择框 -->
<input type="file" id="file1" />
<!-- 2. 上传按钮 -->
<button id="btnUpload">上传文件</button>
<br />
<!-- 3. 显示上传到服务器上的图片 -->
<img src="" alt="" id="img" width="800" />
验证是否选择了文件
// 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('请选择要上传的文件!')
}
})
向 FormData 中追加文件
// 1. 创建 FormData 对象
var fd = new FormData()
// 2. 向 FormData 中追加文件
//avatar为自定义参数
fd.append('avatar', files[0])
使用 xhr 发起上传文件的请求
// 1. 创建 xhr 对象
var xhr = new XMLHttpRequest()
// 2. 调用 open 函数,指定请求类型与URL地址。其中,请求类型必须为 POST
xhr.open('POST', 'http://www.liulongbin.top:3006/api/upload/avatar')
// 3. 发起请求
xhr.send(fd)
监听 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://www.liulongbin.top:3006' + data.url
} else { // 上传文件失败
console.log(data.message)
}
}
}
显示文件上传进度
新版本的 XMLHttpRequest 对象中,可以通过监听 xhr.upload.onprogress 事件,来获取到文件的上传进度。语法格式如下:
// 创建 XHR 对象
var xhr = new XMLHttpRequest()
// 监听 xhr.upload 的 onprogress 事件
xhr.upload.onprogress = function(e) {
// e.lengthComputable 是一个布尔值,表示当前上传的资源是否具有可计算的长度
if (e.lengthComputable) {
// e.loaded 已传输的字节
// e.total 需传输的总字节
var percentComplete = Math.ceil((e.loaded / e.total) * 100)
}
}
基于Bootstrap渲染进度条
<!-- 进度条 -->
<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.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 + '%')
}
}
监听上传完成的事件
xhr.upload.onload = function() {
$('#percent')
// 移除上传中的类样式
.removeClass()
// 添加上传完成的类样式
.addClass('progress-bar progress-bar-success')
}
axios
Axios 是专注于网络数据请求的库。
相比于原生的 XMLHttpRequest 对象,axios 简单易用。
相比于 jQuery,axios 更加轻量化,只专注于网络数据请求。
axios发起GET请求
axios.get('url', { params: { /*参数*/ } }).then(callback)
axios发起POST请求
axios.post('url', { /*参数*/ }).then(callback)
直接使用axios发起请求
axios 也提供了类似于 jQuery 中 $.ajax() 的函数,语法如下:
axios({
method: '请求类型',
url: '请求的URL地址',
data: { /* POST数据 */ },
params: { /* GET参数 */ }
}) .then(callback)
示例:
<body>
<button id="btn1">发起GET请求</button>
<button id="btn2">发起POST请求</button>
<button id="btn3">直接使用axios发起GET请求</button>
<button id="btn4">直接使用axios发起POST请求</button>
<script>
document.querySelector('#btn1').addEventListener('click', function() {
var url = 'http://www.liulongbin.top:3006/api/get'
var paramsObj = {
name: 'z2s',
age: 20
}
axios.get(url, {
params: paramsObj
}).then(function(res) {
console.log(res.data)
})
})
document.querySelector('#btn2').addEventListener('click', function() {
var url = 'http://www.liulongbin.top:3006/api/post'
var dataObj = {
address: '北京',
location: '顺义区'
}
axios.post(url, dataObj).then(function(res) {
console.log(res.data)
})
})
document.querySelector('#btn3').addEventListener('click', function() {
var url = 'http://www.liulongbin.top:3006/api/get'
var paramsData = {
name: '钢铁侠',
age: 35
}
axios({
method: 'GET',
url: url,
params: paramsData
}).then(function(res) {
console.log(res.data)
})
})
document.querySelector('#btn4').addEventListener('click', function() {
axios({
method: 'POST',
url: 'http://www.liulongbin.top:3006/api/post',
data: {
name: '娃哈哈',
age: 18,
gender: '女'
}
}).then(function(res) {
console.log(res.data)
})
})
</script>
</body>
Ajax请求限制
Ajax 只能向自己的服务器发送请求。
比如现在有一个A网站、有一个B网站,A网站中的 HTML 文件只能向A网站服务器中发送 Ajax 请求,B网站中的 HTML 文件只能向 B 网站中发送 Ajax 请求
A 网站是不能向 B 网站发送 Ajax请求的,同理,B 网站也不能向 A 网站发送 Ajax请求。
同源
如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属于同一个源,其中只要有一个不相同,就是不同源。
同源策略
同源策略(英文全称 Same origin policy)是浏览器提供的一个安全功能。
MDN 官方给定的概念:同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
通俗的理解:浏览器规定,A 网站的 JavaScript,不允许和非同源的网站 C 之间,进行资源的交互,例如:
无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
无法接触非同源网页的 DOM
无法向非同源地址发送 Ajax 请求
同源政策的目的
1.同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。最初的同源政策是指 A 网站在客户端设置的 Cookie,B网站是不能访问的。
2.随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送Ajax 请求,如果请求,浏览器就会报错。
跨域
同源指的是两个 URL 的协议、域名、端口一致,反之,则是跨域。
出现跨域的根本原因:浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。
注意:浏览器允许发起跨域请求,但是,跨域请求回来的数据,会被浏览器拦截,无法被页面获取到!
如何实现跨域数据请求
现如今,实现跨域数据请求,最主要的两种解决方案,分别是 JSONP 和 CORS。
JSONP:出现的早,兼容性好(兼容低版本IE)。
是前端程序员为了解决跨域问题,被迫想出来的一种临时解决方案。
缺点是只支持 GET 请求,不支持 POST 请求。
CORS:出现的较晚,它是 W3C 标准
属于跨域 Ajax 请求的根本解决方案。支持 GET 和 POST 请求
缺点是不兼容某些低版本的浏览器。
JSONP
JSONP (JSON with Padding) 是 JSON 的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。
它不属于 Ajax 请求,但它可以模拟 Ajax 请求
具体JSONP讲解:JSONP
JSONP的实现原理
由于浏览器同源策略的限制,网页中无法通过 Ajax 请求非同源的接口数据。
但是 <script>
标签不受浏览器同源策略的影响,可以通过 src 属性,请求非同源的 js 脚本。
因此,JSONP 的实现原理:
就是通过 <script>
标签的 src 属性,请求跨域的数据接口,并通过函数调用的形式,接收跨域接口响应回来的数据。
<body>
<script>
function abc(data) {
console.log('JSONP响应回来的数据是:')
console.log(data)
}
</script>
<!-- <script src="http://ajax.frontend.itheima.net:3006/api/jsonp?callback=abc&name=ls&age=30"></script> -->
<script src="http://www.liulongbin.top:3006 /api/jsonp?callback=abc&name=ls&age=30"></script>
</body>
1.在scrip标签中求情跨域接口
2.通过函数调用方式 callback = abc
3.接收数据
JSONP的缺点
由于 JSONP 是通过 <script>
标签的 src 属性,来实现跨域数据获取的,所以,JSONP 只支持 GET 数据请求,不支持 POST 请求。
注意:JSONP 和 Ajax 之间没有任何关系,不能把 JSONP 请求数据的方式叫做 Ajax,因为 JSONP 没有用到 XMLHttpRequest 这个对象。
jQuery中的JSONP
jQuery 提供的 $.ajax() 函数,除了可以发起真正的 Ajax 数据请求之外,还能够发起 JSONP 数据请求,例如:
$.ajax({
url: 'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20',
// 如果要使用 $.ajax() 发起 JSONP 请求,必须指定 datatype 为 jsonp
dataType: 'jsonp',
success: function(res) {
console.log(res)
}
})
默认情况下,使用 jQuery 发起 JSONP 请求,会自动携带一个 callback=jQueryxxx 的参数,jQueryxxx 是随机生成的一个回调函数名称。
自定义参数及回调函数名称
在使用 jQuery 发起 JSONP 请求时,如果想要自定义 JSONP 的参数以及回调函数名称,可以通过如下两个参数来指定:
$.ajax({
url: 'http://ajax.frontend.itheima.net:3006/api/jsonp?name=zs&age=20',
dataType: 'jsonp',
// 发送到服务端的参数名称,默认值为 callback
jsonp: 'callback',
// 自定义的回调函数名称,默认值为 jQueryxxx 格式
jsonpCallback: 'abc',
success: function(res) {
console.log(res)
}
})
jQuery中JSONP的实现过程
jQuery 中的 JSONP,也是通过 <script>
标签的 src 属性实现跨域数据访问的,只不过,jQuery 采用的是动态创建和移除 <script>
标签的方式,来发起 JSONP 数据请求。
在发起 JSONP 请求的时候,动态向 <header> 中 append 一个 <script> 标签;
在 JSONP 请求成功以后,动态从 <header> 中移除刚才 append 进去的 <script> 标签;
JSONP代码优化
1.客户端需要将函数名称传递到服务器端。
<script src="http://localhost:3001/better?callback=fn2"></script>
app.get('/better', (req, res) => {
const fnName = req.query.callback;
}
2.将 script 请求的发送变成动态请求。
<script type="text/javascript">
// 获取按钮
var btn = document.getElementById('btn');
// 为按钮添加点击事件
btn.onclick = function () {
// 创建script标签
var script = document.createElement('script');
// 设置src属性
script.src = 'http://localhost:3001/better?callback=fn2';
// 将script标签追加到页面中
document.body.appendChild(script);
// 为script标签添加onload事件
script.onload = function () {
// 将body中的script标签删除掉
document.body.removeChild(script);
}
}
</script>
3.封装 jsonp 函数,方便请求发送。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<button id="btn1">点我发送请求</button>
<button id="btn2">点我发送请求</button>
<script type="text/javascript">
// 获取按钮
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
// 为按钮添加点击事件
btn1.onclick = function () {
jsonp({
// 请求地址
url: 'http://localhost:3001/better',
data: {
name: 'lisi',
age: 30
},
success: function (data) {
console.log(123)
console.log(data)
}
})
}
btn2.onclick = function () {
jsonp({
// 请求地址
url: 'http://localhost:3001/better',
success: function (data) {
console.log(456789)
console.log(data)
}
})
}
function jsonp (options) {
// 动态创建script标签
var script = document.createElement('script');
// 拼接字符串的变量
var params = '';
for (var attr in options.data) {
params += '&' + attr + '=' + options.data[attr];
}
// myJsonp0124741
var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
// 它已经不是一个全局函数了
// 我们要想办法将它变成全局函数
window[fnName] = options.success;
// 为script标签添加src属性
script.src = options.url + '?callback=' + fnName + params;
// 将script标签追加到页面中
document.body.appendChild(script);
// 为script标签添加onload事件
script.onload = function () {
document.body.removeChild(script);
}
}
</script>
</body>
</html>
4.服务器端代码优化之 res.jsonp 方法。
优化前:
app.get('/better', (req, res) => {
//接收客户端传递过来的函数的名称
const fnName = req.query.callback;
// 将函数名称对应的函数调用代码返回给客户端
const data = JSON.stringify({name: "张三"});
const result = fnName + '('+ data +')';
setTimeout(() => {
res.send(result);
}, 1000)
优化后:
res.jsonp({name: 'lisi', age: 20});
});
服务器端解决同源限制
同源政策是浏览器给予Ajax技术的限制,服务器端是不存在同源政策限制。
使用第三方模块request
1号服务器:
const request = require('request');
app.get('/server', (req, res) => {
request('http://localhost:3001/cross', (err, response, body) => {
res.send(body);
})
});
2号服务端:
app.get('/cross', (req, res) => {
res.send('ok')
});
cookie
withCredentials
1.在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。
2.withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false
3.Access-Control-Allow-Credentials:true 允许客户端发送请求时携带cookie
示例:
客户端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>实现跨域功能</title>
<link rel="stylesheet" href="/assets/bootstrap/dist/css/bootstrap.min.css">
<style type="text/css">
.container {
padding-top: 60px;
}
</style>
</head>
<body>
<div class="container">
<form id="loginForm">
<div class="form-group">
<label>用户名</label>
<input type="text" name="username" class="form-control" placeholder="请输入用户名">
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" class="form-control" placeholder="请输入用密码">
</div>
<input type="button" class="btn btn-default" value="登录" id="loginBtn">
<input type="button" class="btn btn-default" value="检测用户登录状态" id="checkLogin">
</form>
</div>
<script type="text/javascript">
// 获取登录按钮
var loginBtn = document.getElementById('loginBtn');
// 获取检测登录状态按钮
var checkLogin = document.getElementById('checkLogin');
// 获取登录表单
var loginForm = document.getElementById('loginForm');
// 为登录按钮添加点击事件
loginBtn.onclick = function () {
// 将html表单转换为formData表单对象
var formData = new FormData(loginForm);
// 创建ajax对象
var xhr = new XMLHttpRequest();
// 对ajax对象进行配置
xhr.open('post', 'http://localhost:3001/login');
// 当发送跨域请求时,携带cookie信息
xhr.withCredentials = true;
// 发送请求并传递请求参数
xhr.send(formData);
// 监听服务器端给予的响应内容
xhr.onload = function () {
console.log(xhr.responseText);
}
}
// 当检测用户状态按钮被点击时
checkLogin.onclick = function () {
// 创建ajax对象
var xhr = new XMLHttpRequest();
// 对ajax对象进行配置
xhr.open('get', 'http://localhost:3001/checkLogin');
// 当发送跨域请求时,携带cookie信息
xhr.withCredentials = true;
// 发送请求并传递请求参数
xhr.send();
// 监听服务器端给予的响应内容
xhr.onload = function () {
console.log(xhr.responseText);
}
}
</script>
</body>
</html>
服务端:
// 拦截所有请求
app.use((req, res, next) => {
// 1.允许哪些客户端访问我
// * 代表允许所有的客户端访问我
// 注意:如果跨域请求中涉及到cookie信息传递,值不可以为*号 比如是具体的域名信息
res.header('Access-Control-Allow-Origin', 'http://localhost:3000')
// 2.允许客户端使用哪些请求方法访问我
res.header('Access-Control-Allow-Methods', 'get,post')
// 允许客户端发送跨域请求时携带cookie信息
res.header('Access-Control-Allow-Credentials', true);
next();
});
app.post('/login', (req, res) => {
// 创建表单解析对象
var form = formidable.IncomingForm();
// 解析表单
form.parse(req, (err, fields, file) => {
// 接收客户端传递过来的用户名和密码
const { username, password } = fields;
// 用户名密码比对
if (username == 'itheima' && password == '123456') {
// 设置session
req.session.isLogin = true;
res.send({message: '登录成功'});
} else {
res.send({message: '登录失败, 用户名或密码错误'});
}
})
});
app.get('/checkLogin', (req, res) => {
// 判断用户是否处于登录状态
if (req.session.isLogin) {
res.send({message: '处于登录状态'})
} else {
res.send({message: '处于未登录状态'})
}
});
jQuery中Ajax全局事件
全局事件
只要页面中有Ajax请求被发送,对应的全局事件就会被触发.
.ajaxStart() // 当请求开始发送时触发
.ajaxComplete() // 当请求完成时触发
要绑定在document身上。
NProgress插件
1.纳米级进度条,使用逼真的涓流动画来告诉用户正在发生的事情!
引入:
<link rel='stylesheet' href='nprogress.css'/>
<script src='nprogress.js'></script>
使用:
NProgress.start(); // 进度条开始运动
NProgress.done(); // 进度条结束运动
RESTful 风格的 API
一套关于设计请求的规范。
GET: 获取数据
POST: 添加数据
PUT: 更新数据
DELETE: 删除数据
users => /users
articles => /articles
传统请求地址回顾:
GET http://www.example.com/getUsers // 获取用户列表
GET http://www.example.com/getUser?id=1 // 比如获取某一个用户的信息
POST http://www.example.com/modifyUser // 修改用户信息
GET http://www.example.com/deleteUser?id=1 // 删除用户信息
RESTful API 的实现:
GET: http://www.example.com/users 获取用户列表数据
POST: http://www.example.com/users 创建(添加)用户数据
GET: http://www.example.com/users/1 获取用户ID为1的用户信息
PUT: http://www.example.com/users/1 修改用户ID为1的用户信息
DELETE: http://www.example.com/users/1 删除用户ID为1的用户信息
// 获取用户列表信息
app.get('/users', (req, res) => {
res.send('当前是获取用户列表信息的路由');
});
// 获取某一个用户具体信息的路由
app.get('/users/:id', (req, res) => {
// 获取客户端传递过来的用户id
const id = req.params.id;
res.send(`当前我们是在获取id为${id}用户信息`);
});
// 删除某一个用户
app.delete('/users/:id', (req, res) => {
// 获取客户端传递过来的用户id
const id = req.params.id;
res.send(`当前我们是在删除id为${id}用户信息`);
});
// 修改某一个用户的信息
app.put('/users/:id', (req, res) => {
// 获取客户端传递过来的用户id
const id = req.params.id;
res.send(`当前我们是在修改id为${id}用户信息`);
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src="/js/jquery.min.js"></script>
<script type="text/javascript">
// 获取用户列表信息
$.ajax({
type: 'get',
url: '/users',
success: function (response) {
console.log(response)
}
})
// 获取id为1的用户信息
$.ajax({
type: 'get',
url: '/users/1',
success: function (response) {
console.log(response)
}
})
// 删除id为10的用户信息
$.ajax({
type: 'delete',
url: '/users/10',
success: function (response) {
console.log(response)
}
})
// 更新id为1的用户信息
$.ajax({
type: 'put',
url: '/users/10',
success: function (response) {
console.log(response)
}
})
</script>
</body>
</html>