Ajax简介
Ajax(Asynchronous JavaScript And XML),可以通过其在浏览器中向服务器发送异步请求,无需刷整个页面就可以获取数据。
Ajax不是新的编程语言,而是一种将现在的标准组合在一起使用的新方式
使用场景:实现懒加载,当需要展示某一数据时再去请求数据
优点:
- 无需刷新整个页面就可以与服务器端进行通信
- 允许根据用户时间来更新部分页面内容
缺点:
- 没有浏览历史,不能回退
- 存在跨域问题
- SEO不友好:使用ajax请求的数据,是异步请求的方式。SEO主要是获取源文档内容,也就是响应体内的内容,但是响应体内的内容需要在解析的过程中动态去获取的,所以SEO无法获取通过ajax请求的数据,也即通过ajax展示的数据对SEO不友好。
XML
XML(可扩展标记语言):被设计用来传输和存储数据。XML和HTML类似,不同的是HTML中都是预定义标签,而XML都是自定义标签,用来表示一些数据。但是目前大部分使用JSON来替代XML,原因:JSON数据更容易书写,并且转换也更方便。
HTTP
HTTP(hypertext transport protocol)协议,超文本传输协议,协议详细规定了浏览器和万维网服务器之间互相通信的规则。主要规定了请求和响应规则。
格式:
- 请求报文
行 POST /getNames HTTP/1.1 (分别是:请求方式/请求资源路径/HTTP版本号)
头 Host: xxx.xxx.xxx
Connection: keep-alive
Pragma: no-cache Cache-Control: no-cache
Accept-Encoding: gzip, deflate, br(以上都是浏览器告诉服务器想要请求什么样的数据,以及想要跟服务器进行怎样的约定)
空行
体 username=admin&password=admin(如果是post请求则请求体有数据)
- 响应报文
行 HTTP/1.1 200 OK (分别是:HTTP版本号/响应状态码/响应状态码的描述)
头
cache-control: no-cache
content-encoding:br
content-type: application/json; charset=utf-8(以上都是服务器告诉浏览器响应的数据相关的信息)
空行
体 响应数据
Ajax使用
ajax基本使用
// 获取xmlhttprequest实例
const xhr = new XMLHttpRequest();
// 初始化
xhr.open('GET','http://127.0.0.1:8000/server');
// 发送请求 如果部需要发送请求体,则必须传null,因为该参数在一些浏览器中是必须的。
xhr.send(null);
// 事件绑定,处理响应结果,每次readyState数据发生变化都会触发onreadystatechange事件。
xhr.onreadystatechange = function () {
// readyState表示当前处在请求/响应的哪个阶段
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
// 响应行
console.log(xhr.status);// 状态码
console.log(xhr.statusText);//状态码字符串
// 所有的响应头
console.log(xhr.getAllResponseHeaders());
// 响应体
console.log(xhr.response);
// or 响应体的字符串表示,该方式不受reponseType设置影响
console.log(xhr.responseText);
}
}
}
readyState的属性值有:
- 0:未初始化。尚未调用open方法。
- 1:已打开(open)。已调用open方法,但是还没有调用send()方法
- 2:已发送(send)。已调用send方法,尚未收到响应。
- 3:接受中。已收到部分响应。
- 4:完成。已收到全部的响应。
所以可以看出,一般通过判断readyState是否为4从而判断是否获取到了响应数据。
以上基本使用中用到了open方法作为请求的初始化操作。那么open方法如何去使用呢?
xhr.open('GET', 'http://127.0.0.1',true);
第一个参数代表请求的方式,一般有:
- GET
- POST
第二个参数代表请求的URL,如果是GET方式请求,当要传输查询字符串的时候,则需要在URL后面进行拼接
例如:
/test?uname=admin&password=admin
第三个参数表示是否使用异步请求,默认为true。如果为false,则是同步请求,会导致阻塞。
初始化完成之后,可以使用send方法发送数据。
- GET方法,由于该方法传输查询参数是通过拼接字符串的形式,所以当使用send方法的时候,不需要传入任何数据,但是有些浏览器需要该参数,因此最好传一个null。
- POST方法,可以使用该方法传输任何数据,该数据作为请求体进行传输。
send方法可以不写么?不可以,原因:open仅仅初始化了请求,但是并没有发送请求,而发送请求的真正时间点,在send方法调用之后。
设置请求头
可以使用setRequestHeader设置预定义请求头和自定义请求头
xhr.setRequestHeader('uname','admin');// 自定义请求头
注意:自定义请求头可以会触发预检请求。
获取json数据
当服务器端返回的数据是json字符串数据的时候,那客户端如何获取json数据本身呢?
- 手动转换
利用JSON.parse(jsonstr)来进行转换
- 配置,自动转换
XMLHttpRequest实例对象上有一个属性:responseType,可以通过这个属性对返回的数据进行自动转换,这里需要赋值为json,即:xhr.responseType = 'json'
这样,获取到的响应体数据会自动转换为json格式,但是需要注意的是,只有response这个属性数据才会受responseType数据设置的影响。responseText本身就是对响应体的字符串表示,所以不受影响。
超时和网络异常处理
- 对于请求超时,客户端可以这样设置
xhr.timeout = 2000;
表示,如果2s中还未收到响应,则作为超时处理,此时会取消请求。
例如:
一般对于请求超时,客户端都会通知用户,类似的,这里可以使用超时回调,来进行超时的一个处理:
xhr.ontimeout = function() {
alert('网络超时');
}
- 对于网络异常/断网的情况下,也有对应的回调函数供开发人员去操作
xhr.onerror = function() {
alert('哦豁,网络异常');
}
取消请求
可以利用XMLHttpRequest实例对象上的方法abort()取消还没有获得响应的请求。
xhr.abort();
重复请求问题
有一种场景:用户点击按钮,发送请求。但是如果用户不断点击按钮就可能发生不断发请求的情况,服务器就需要处理相同的请求,从而造成服务器的压力比较大。
例如:
以上便是用户点击多次的一个情况,可以看到请求也发了多次。
为了避免这个情况,需要在用户多次点击的时候进行处理,比如:当用户点击多次的时候,如果上一次请求还在发送过程中,则取消上一次的请求,重新开启一个新的请求。这种方式也就是我们经常所说的防抖解决方案。
let xhr = null;
let isSending = false; // 标识是否正在发送请求
btn.addEventListener('click', function () {
// 获取xmlhttprequest实例
if(isSending) xhr.abort();
xhr = new XMLHttpRequest();
isSending = true;
xhr.responseType = 'json';
xhr.onerror = function() {
alert('哦豁,网络出错')
}
// 初始化
xhr.open('POST','http://127.0.0.1:8000/server');
xhr.setRequestHeader('uname', 'admin');
// 发送请求
xhr.send();
// 事件绑定,处理响应结果
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
isSending = false;
if (xhr.status >= 200 && xhr.status < 300) {
// input.innerHTML = JSON.parse(xhr.responseText).uname;
input.innerHTML = xhr.response.uname;
}
}
}
})
其实除了防抖利用标识符解决外,最有效的方式则是禁用当前的按钮,不允许用户进行点击,一方面告诉用户请求已经发送了,另一方面也防止用户多次点击的情况。
Jquery中使用Ajax
- 引入jquery
这里采用cdn引入
- 编写get/post请求
$.get('http://127.0.0.1:8000/server',{a: 12, b: 34}, function (data) {
console.log(data)
},'json')
$.post('http://127.0.0.1:8000/server', {a:12, b:34},function (data) {
console.log(data)
}, 'json')
这两个方法第一个参数表示请求的url,第二个参数表示传递的数据,第三个参数表示对返回的响应体的格式要求,也即responseType的值。
通过的请求方法:
$.ajax({
// url
url: 'http://127.0.0.1:8000/server',
// method
type: 'POST',
// data
data: {a: 1, b: 2},
// 响应体类型
dataType: 'json',
// headers
headers: {
a: 1
},
// 超时时间设置
timeout: 2000,
// 成功回调
success: function (data) {
console.log(data);
},
// 失败回调,包括网络异常/超时/等情况
error: function (err) {
console.log(err);
}
})
fetch发送请求
fetch属于js全局的方法,所以可以直接调用,他是一个基于promise的http请求方法。且是一个异步请求。
// 当调用fetch的时候,基本上请求就开始发送了。
let f = fetch('http://127.0.0.1:8000/server', {
// 初始化配置选项
method: 'POST',
headers: {
uname: 'chen'
},
// 请求体
body: {
uname: 'chen'
}
});
f.then(response=> {
// return response.text();// 返回一个已解决的响应体字符串表示的期约
return response.json();// 返回一个已解决的响应体json表示的期约
}).then(data=> {
console.log(data);
})
ajax工具库-axios
axios基于promise的网络请求库,可以适用于浏览器和node环境
- 引入axios(采用CDN的方式)
- 发送get请求
// 配置基础的url
axios.defaults.baseURL = 'http://127.0.0.1:8000';
axios.get('/server', {
params: {
a: 1,
b: 2
},
headers: {
uname: 'chen'
}
}).then(data=>{
console.log(data);
})
第一个参数表示请求的路径,如果预先设置了基础的url,此处设置的url会跟基础的url进行拼接。第二个参数是对请求的基础配置
- 发送post请求
// 配置基础的url
axios.defaults.baseURL = 'http://127.0.0.1:8000
axios.post('/server', {
// 请求体
name: 'admin',
password: '123'
}, {
// 请求配置
headers: {
uname: 'chen'
},
// post请求也可以在Url后面添加额外的信息
params: {
a: 1,
b: 2
}
}).then(data=>{
console.log(data);
})
第一个参数跟get请求一致,第二个参数表示请求体数据,第三个参数表示请求配置。需要注意的是,由于axios的post请求默认的Content-Type为application/json;charset=UTF-8,因此会触发预检请求,需要对数据进行配置/服务器进行配置
跨域问题
ajax请求是不支持跨域的,也就是ajax请求满足同源策略。所以对于想要获取不同源的数据,就需要解决跨域问题。
跨域问题的解决方案有:
- JSONP
JSONP是一个非官方的跨域解决方案,只支持get请求。在网页中一些标签:img link iframe script。不受同源策略的影响。而JSONP就是利用script标签的跨域能力来发送请求的。
script标签如何发送请求呢?
我们知道如果要引入一个外部脚本,可以使用script标签的src属性进行指定。当浏览器解析到当前的这个script标签的时候,会将其src的js脚本进行引入,类似于将js脚本中的代码放在script标签中相应的位置。
比如:当前有一个外部脚本test.js
console.log(124);
在html文件中进行引入
JSONP
此时浏览器解析完成时(这里先不考虑脚本中代码的执行),HTML呈现为:
JSONP
可以看到对应的位置被外部Js脚本替换。(这里的外部js脚本必须是可执行的js脚本,如果js脚本里面是123,则认为是不合法的)
以上引入的js脚本是同一个域下的,同样可以引入不同域下面的js脚本,我们用的比较多的就是cdn引入的jquery脚本
所以按照以上的思路,我们可以利用script标签的src引入不同域的外部资源。但是如何获取数据呢?
之前有提到过,script标签引入的外部资源必须是可执行的js脚本。而如果简单的想要获取某个非可执行的js脚本数据,这是不合法的。
所以JSONP就说,既然需要执行的js脚本,那么服务器就给你提供一个客户端已有的函数字符串。同时将你需要的数据拼接到函数字符串中。
描述比较生硬,可能不容易理解,可以看以下的例子。
具体实现:
客户端:
发送请求
服务器端(node):
app.get('/server',(req,res) => {
let data = {
uname: 'op'
}
let resData = JSON.stringify(data);
// 将数据拼接到函数字符串中
res.send(`handle(${resData})`);
})
可以看到服务器将含有数据的函数调用字符串返回给了客户端,
具体返回的内容如下:
handle({"uname":"op"})
客户端接收到了这个字符串内容,就会尝试解析,类似于执行js脚本,由于客户端之前有这个handle函数,所以就会直接调用handle函数,利用handle函数就可以获取到服务器端传过来的数据了。
利:
- 实现简单
弊:
- 需要服务器知道客户端已有的函数名称
- 容易遭受xss攻击
- jQuery发送jsonp
jquery有一个方法getJSON可以很方便得发送jsonp,同时不需要服务器端知道函数名称得情况下传递函数字符串。
服务器(node):
app.get('/server',(req,res) => {
let data = {
uname: 'op'
}
let resData = JSON.stringify(data);
// 获取客户端传递得函数名称
let ca = req.query.callback;
res.send(`${ca}(${resData})`);
})
客户端:
发送请求
第一个参数为目标Url,这里需要注意得是后面必须拼接callback参数,否则服务端无法获得函数名称,第二个参数是函数回调,利用该函数可以获取到服务器返回得数据。
- CORS
CORS:跨域资源共享。是官方得跨域解决方案,特点:不需要客户端做任何特殊得操作,完全在服务器中进行处理,支持get/post请求。
工作形式:当浏览器发现当前请求违反了CORS,则会发起一个预检请求,询问服务器是否允许这个请求,服务器通过设置响应头告诉浏览器,允许该请求,则浏览器会将用户发送得请求发送到指定得服务器。
一般设置以下响应头信息:
- Access-Control-Allow-Origin: 表示允许的访问来源。或者可以使用"*" 表示允许所有的访问来源。这个字段一般用于对跨域请求的支持。
- Access-Control-Allow-Headers:表示允许的自定义请求头。
- Access-Control-Allow-Methods:表示允许的请求方式。
例子node():
app.use('/server', (req,res,next) => {
// 设置所有域都可以访问该域的资源
res.setHeader('Access-Control-Allow-Origin','*');
// 设置允许的自定义请求头
res.setHeader('Access-Control-Allow-Headers', '*');
next();
})