一、Ajax
它是浏览器提供的一套方法,可以实现页面无刷新更新数据,提高用户浏览网站应用的体验。
1. 应用场景
- 页面上拉加载更多数据
- 列表数据无刷新分页
- 表单离开焦点数据验证
- 搜索框提示文字下拉列表
2. 运行原理
Ajax 相当于浏览器发送请求与接收响应的代理人,以实现在不影响用户浏览页面的情况下,局部更新页面数据,从而提高用户体验。
3. 实现步骤
3.1 创建 Ajax 对象
var xhr = new XMLHttpRequest();
3.2 告诉 Ajax 请求地址以及请求方式
xhr.open('get', 'http://www.example.com');
3.3 发送请求
xhr.send();
3.4 获取服务器端给与客户端的响应数据
xhr.onload = function () {
console.log(xhr.responseText);
}
4. 请求参数
4.1 GET 请求方式
xhr.open('get', 'http://www.example.com?name=zhangsan&age=20');
4.2 POST 请求方式
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('name=zhangsan&age=20');
在 http 请求与响应的过程中,无论是请求参数还是响应内容,如果是对象类型,最终都会被转换为对象字符串进行传输。
JSON.parse() // 将 json 字符串转换为json对象
JSON.stringify() // 将json对象转换为json字符串
5. 请求报文
在 HTTP 请求和响应的过程中传递的数据块就叫报文,包括要传送的数据和一些附加信息,这些数据和信息要遵守规定好的格式。
6. 请求参数的格式
6.1 application/x-www-form-urlencoded
name=zhangsan&age=20&sex=男
6.2 application/json
{name: 'zhangsan', age: '20', sex: '男'}
在请求头中指定 Content-Type 属性的值是 application/json,告诉服务器端当前请求参数的格式是 json。
注意: get 请求是不能提交 json 对象数据格式的,传统网站的表单提交也是不支持 json 对象数据格式的。
7. 获取服务器端响应
7.1 Ajax 状态码
在创建ajax对象,配置ajax对象,发送请求,以及接收完服务器端响应数据,这个过程中的每一个步骤都会对应一个数值,这个数值就是ajax状态码。
0:请求未初始化(还没有调用open()) 1:请求已经建立,但是还没有发送(还没有调用send()) 2:请求已经发送 3:请求正在处理中,通常响应中已经有部分数据可以用了 4:响应已经完成,可以获取并使用服务器的响应了
xhr.readyState // 获取Ajax状态码
7.2 onreadystatechange 事件
当 Ajax 状态码发生变化时将自动触发该事件。
在事件处理函数中可以获取 Ajax 状态码并对其进行判断,当状态码为 4 时就可以通过 xhr.responseText 获取服务器端的响应数据了。
// 当Ajax状态码发生变化时
xhr.onreadystatechange = function () {
// 判断当Ajax状态码为4时
if (xhr.readyState == 4) {
// 获取服务器端的响应数据
console.log(xhr.responseText);
}
}
7.3 两种获取服务器端响应方式的区别
| 区别描述 | onload事件 | onreadystatechange事件 |
|---|---|---|
| 是否兼容IE低版本 | 不兼容 | 兼容 |
| 是否需要判断Ajax状态码 | 不需要 | 需要 |
| 被调用次数 | 一次 | 多次 |
8. Ajax 错误处理
8.1 xhr.status 获取http状态码
可以判断服务器端返回的状态码,分别进行处理。
8.2 网络中断,请求无法发送到服务器端。
会触发xhr对象下面的onerror事件,在onerror事件处理函数中对错误进行处理。
8.3 常见 http 状态码
- 1xx 服务器收到请求
- 2xx 请求成功,如 200
- 3xx 重定向,如 302
- 301 永久重定向(配合 location, 浏览器自动处理)
- 302 临时重定向(配合 location, 浏览器自动处理)
- 304 资源未被修改(已经请求过资源,资源未过期)
- 4xx 客户端错误,如 404
- 403 没有权限,服务器不想让你访问
- 404 资源没找到
- 5xx 服务端错误,如 500
-
500 服务器错误
-
504 网关超时, 是指服务器作为网关或代理,但是没有及时从上游服务器收到请求
-
9. 低版本 IE 浏览器的缓存问题
在低版本的 IE 浏览器中,Ajax 请求有严重的缓存问题,即在请求地址不发生变化的情况下,只有第一次请求会真正发送到服务器端,后续的请求都会从浏览器的缓存中获取结果。即使服务器端的数据更新了,客户端依然拿到的是缓存中的旧数据。
在请求地址的后面加请求参数,保证每一次请求中的请求参数的值不相同。
xhr.open('get', 'http://www.example.com?t=' + Math.random());
10. Ajax 封装
ajax 发送一次请求代码过多,发送多次请求代码冗余且重复。我们将请求代码封装到函数中,发请求时调用函数即可。
请求参数要考虑的问题
- 请求参数位置问题
- get 请求放在请求地址后
- post 放在 send 方法中
- 请求参数格式问题
- application/x-www-form-urlencoded
- application/json
// 对象的属性名称可以对属性进行描述
ajax({
// 请求类型
type: 'post',
// 请求地址
url: 'http://localhost:3000/json',
// 请求参数
data: {
username: 'zhangsan',
id: '12345'
},
// 请求格式
header: {
'Content-Type': 'application/json'
},
// 请求成功执行回调函数回调函数
success: function(data, xhr) {
console.log('这里是success函数');
console.log(data);
},
});
// 封装 ajax 函数
function ajax(options) {
// 存储的是默认值
var defaults = {
type: 'get',
url: '',
data: {},
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
success: function() {},
error: function() {}
};
Object.assign(defaults, options);
// 创建 ajax 对象
var xhr = new XMLHttpRequest();
// 拼接请求参数的变量
var params = '';
// 循环用户传递进来的对象格式参数
for (var attr in defaults.data) {
// 将参数转换为字符串格式
params += attr + '=' + defaults.data[attr] + '&';
}
// 截取字符串最后的 &
params = params.substr(0, params.length - 1);
// 判断请求方式
if (defaults.type == 'get') {
defaults.url = defaults.url + '?' + params;
}
// 配置ajax对象
xhr.open(defaults.type, defaults.url);
// 如果请求方式为post
if (defaults.type == 'post') {
var contentType = defaults.header['Content-Type'];
// 设置请求参数格式的类型
xhr.setRequestHeader('Content-Type', contentType);
if (contentType == 'application/json') {
// 向服务器端传递 json 数据格式的参数
// 参数必须是传递字符串
xhr.send(JSON.stringify(defaults.data));
} else {
// 向服务器端传递普通类型的请求参数
xhr.send(params);
}
} else {
// 发送请求
xhr.send();
}
// 监听xhr对象 下面onload事件
// 当xhr对象接收完响应数据后触发
xhr.onload = function() {
// xhr.getResponseHeader();
// 获取响应头中的数据
var contentType = xhr.getResponseHeader('Content-Type');
// 服务器端返回数据
var responseText = xhr.responseText;
// 如果响应类型中包含application/json
if (contentType.includes('application/json')) {
// 将json 字符串转换为json对象
responseText = JSON.parse(responseText);
}
// 当http状态码等于200的时候
if (xhr.status == 200) {
// 请求成功,调用处理成功情况的函数
defaults.success(responseText, xhr);
} else {
// 请求失败,调用处理失败情况的函数
defaults.error(responseText, xhr);
}
}
}
11. 同源策略
11.1 Ajax请求限制
Ajax 只能向自己的服务器发送请求。比如现在有一个A网站、有一个B网站,A网站中的 HTML 文件只能向A网站服务器中发送 Ajax 请求,B网站中的 HTML 文件只能向 B 网站中发送 Ajax 请求,但是 A 网站是不能向 B 网站发送 Ajax请求的,同理,B 网站也不能向 A 网站发送 Ajax请求。
11.2 什么是同源
如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属于同一个源,其中只要有一个不相同,就是不同源
协议://域名:端口
11.3 同源政策的目的
同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。最初的同源政策是指 A 网站在客户端设置的 Cookie,B网站是不能访问的。
随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送 Ajax 请求,如果请求,浏览器就会报错。
11.4 解决同源策略方案
- JSONP
- CORS 跨域资源共享
- 服务器代理
1. 使用 JSONP 解决同源限制问题
Ajax 不能跨域,但 img的src(获取图片),link 的href(获取css),script的src(获取javascript)这三个都不符合同源策略,它们可以跨域获取数据。这里要介绍的JSONP就是利用 script 的src来实现跨域获取数据的。
JSONP 是 json with padding 的缩写,它不属于 Ajax 请求,但它可以模拟 Ajax 请求。
- 将不同源的服务器端请求地址写在 script 标签的 src 属性中
<script src="www.example.com"></script>/
- 服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数。
const data = 'fn({name: "张三", age: "20"})';
res.send(data);
- 在客户端全局作用域下定义函数 fn
function fn (data) { }
- 在 fn 函数内部对服务器端返回的数据进行处理
function fn (data) { console.log(data); }
JSONP 代码优化
- 客户端需要将函数名称传递到服务器端。
- 将 script 请求的发送变成动态请求。
- 封装 jsonp 函数,方便请求发送。
- 服务器端代码优化之 res.jsonp 方法。
<script src="http://localhost:3000/province?jsonp=jsonp"></script>
2. CORS 跨域资源共享
CORS:全称为 Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送 Ajax 请求,克服了 Ajax 只能同源使用的限制。
origin: http://localhost:3000
Access-Control-Allow-Origin: 'http://localhost:3000'
Access-Control-Allow-Origin: '*'
Node 服务器端设置响应头示例代码:
// 拦截所有请求
app.use((req, res, next) => {
// 1. 允许那些客户端访问我
// * 代表允许所有的客户端访问我
// res.header('Access-Control-Allow-Origin', 'http://localhost:3000');
res.header('Access-Control-Allow-Origin', '*');
// 2. 允许客户端使用哪些请求方法访问我
res.header('Access-Control-Allow-Methods', 'get,post');
next();
});
3. 访问非同源数据 服务器端解决方案
同源政策是浏览器给予Ajax技术的限制,服务器端是不存在同源政策限制
const request = require('request');
app.get('/server', (req, res) => {
// 想非同源服务器访问数据
request('http://localhost:3001/cross', (err, response, body) => {
res.send(body);
});
});
11.5 withCredentials属性
在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。 withCredentials:指定在涉及到跨域请求时,是否携带cookie信息,默认值为false Access-Control-Allow-Credentials:true 允许客户端发送请求时携带cookie
xhr.withCredentials = true;
// 服务器端设置
app.use((req, res, next) => {
// 1. 允许那些客户端访问我
// * 代表允许所有的客户端访问我
// 注意: 如果跨域请求时值不能为 * 号,要传递具体的域名信息
res.header("Access-Control-Allow-Origin", "http://localhost:3000");
// 2. 允许客户端使用哪些请求方法访问我
res.header("Access-Control-Allow-Methods", "get,post");
// 3. 允许客户端发送跨域请求时携带 cookie信息
res.header("Access-Control-Allow-Credentials", true);
next();
});
二、FormData
1. FormData 对象的作用
-
模拟HTML表单,相当于将HTML表单映射成表单对象,自动将表单对象中的数据拼接成请求参数的格式。
-
异步上传二进制文件
2. 使用
- 准备 HTML 表单
<form id="form">
<input type="text" name="username" />
<input type="password" name="password" />
<input type="button"/>
</form>
- 将 HTML 表单转化为 formData 对象
var form = document.getElementById('form');
var formData = new FormData(form);
- 提交表单对象
xhr.send(formData);
注意:
-
Formdata 对象不能用于 get 请求,因为对象需要被传递到 send 方法中,而 get 请求方式的请求参数只能放在请求地址的后面。
-
服务器端 bodyParser 模块不能解析 formData 对象表单数据,我们需要使用 formidable 模块进行解析。
3. FormData 对象的实例方法
- 获取表单对象中属性的值
// 获取 key 为 key 的第一个值
formData.get('key');
// 获取 key 为 key 的所有值,返回值为数组类型
formData.getAll('key');
- 设置表单对象中属性的值
formData.set('key', 'value');
- 删除表单对象中属性的值
formData.delete('key');
- 向表单对象中追加属性值
formData.append('key', 'value');
注意: set 方法与 append 方法的区别是,在属性名已存在的情况下,set 会覆盖已有键名的值,append会保留两个值。
4. FormData 二进制文件上传
<input type="file" id="file"/>
var file = document.getElementById('file')
// 当用户选择文件的时候
file.onchange = function () {
// 创建空表单对象
var formData = new FormData();
// 将用户选择的二进制文件追加到表单对象中
formData.append('attrName', this.files[0]);
// 配置ajax对象,请求方式必须为post
xhr.open('post', 'www.example.com');
xhr.send(formData);
}
5. FormData 文件上传进度展示
// 当用户选择文件的时候
file.onchange = function () {
// 文件上传过程中持续触发onprogress事件
xhr.upload.onprogress = function (ev) {
// 当前上传文件大小/文件总大小 再将结果转换为百分数
// 将结果赋值给进度条的宽度属性
bar.style.width = (ev.loaded / ev.total) * 100 + '%';
}
}
6. FormData 文件上传图片即时预览
在我们将图片上传到服务器端以后,服务器端通常都会将图片地址做为响应数据传递到客户端,客户端可以从响应数据中获取图片地址,然后将图片再显示在页面中。
xhr.onload = function () {
var result = JSON.parse(xhr.responseText);
var img = document.createElement('img');
img.src = result.src;
img.onload = function () {
document.body.appendChild(this);
}
}
三、jQuery 中的 Ajax
1. $.ajax()方法
1.1 发送Ajax请求
$.ajax({
type: 'get',
url: 'http://www.example.com',
data: { name: 'zhangsan', age: '20' },
contentType: 'application/x-www-form-urlencoded',
beforeSend: function () {
return false
},
success: function (response) {},
error: function (xhr) {}
});
1.2 发送jsonp请求。
$.ajax({
url: 'http://www.example.com',
// 指定当前发送jsonp请求
dataType: 'jsonp',
// 修改callback参数名称
jsonp: 'cb',
// 指定函数名称
jsonCallback: 'fnName',
success: function (response) {}
})
2. serialize方法
serialize()序列化表单元素为字符串。
serializeArray()序列化表单元素为JSON数据。
3. $.get()、$.post()方法
.post方法用于发送post请求。
$.get('/base', 'name=zhangsan&age=30', function(response) {});
$.post('http://www.example.com', {name: 'lisi', age: 22}, function (response) {})
4. 全局事件
只要页面中有Ajax请求被发送,对应的全局事件就会被触发
.ajaxStart() // 当请求开始发送时触发
.ajaxComplete() // 当请求完成时触发
NProgress: 纳米级进度条,使用逼真的涓流动画来告诉用户正在发生的事情!
<link rel='stylesheet' href='nprogress.css'/>
<script src='nprogress.js'></script>
NProgress.start(); // 进度条开始运动
NProgress.done(); // 进度条结束运动
// 当页面中有 ajax请求发送时触发
$(document).on('ajaxStart', function() {
NProgress.start();
})
// 当页面请求完成时触发
$(document).on('ajaxComplete', function() {
NProgress.done();
});
四、RESTful 风格的 API
一种新的 API 设计方法(早已推广使用)
传统 API 设计:把每个 url 当作一个功能
Restful API 设计:把每个 url 当作一个唯一的资源
1. 传统的 methods 和 现在的 methods 对比
| methods | 传统的 methods | 现在的 methods |
|---|---|---|
| get | 获取服务器的数据 | 获取数据 |
| post | 像服务器提交数据 | 增加数据 |
| patch / put | 更新数据 | |
| delete | 删除数据 |
2. 如何设计成一个资源?
2.1 尽量不用 url 参数
- 传统 API 设计: /api/list?pageIndex = 2
- Restfux API: /api/list/2
2.2 用 method 表示操作类型传统 API 设计
- post 请求 /api/create-blog
- post 请求 /api/update-blog?id=100
- get 请求 /api/get-blog?id=100
Restfux API 设计
- post 请求 /api/blog
- patch 请求 /api/blog/100
- get 请求 /api/blog/100
五、Fetch
Fetch API是新的ajax解决方案 Fetch会返回Promise, fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象
用法: fetch(url, options).then()
缺点:
- 发送网络请求需要自己来配置Header的Content-Type,不会默认携带cookie等
- 错误处理相对麻烦(只有网络错误才会reject,HTTP状态码404或者500不会被标记为reject)
- 不支持取消一个请求,不能查看一个请求的进度等等
1. FetchAPI基本用法
1.1 get
fetch('http://localhost:3000/books?id=123', {
//get 请求可以省略不写 默认的是GET
method: 'get'
}).then(function(data) {
// text()方法数据 fetchAPI 的一部分, 它返回一个Promise实例对象,用于获取后台返回的数据
return data.text();
}).then(function(data) {
// 在这个then里面我们能拿到最终的数据
console.log(data)
});
1.2 delete
fetch('http://localhost:3000/books/789', {
method: 'delete'
})
1.3 post
fetch('http://localhost:3000/books', {
method: 'post',
body: 'uname=list&pwd=123',
// 配置请求头
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
1.4 put
fetch('http://localhost:3000/books/456', {
method: 'put',
body: JSON.stringify({
uname: '张三',
pwd: '123'
}),
// 配置请求头
headers: {
'Content-Type': 'application/json'
}
})
2. 响应格式
.text() 将获取到的数据 转换成字符串, 它返回一个Promise实例对象 .json() 将获取到的数据使用 json 转换对象, 它返回一个Promise实例对象