一、了解Ajax
1、背景:
在 2005 年,Google 通过其 Google Suggest 使 AJAX 变得流行起来。
Google Suggest 使用 AJAX 创造出动态性极强的 web 界面:当您在谷歌的搜索框输入关键字时,JavaScript 会把这些字符发送到服务器,然后服务器会返回一个搜索建议的列表。
2、介绍:
AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。
AJAX 不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。
3、什么是ajax
AJAX是异步的JavaScript和XML(Asynchronous JavaScript And XML)。简单点说,就是使用 XMLHttpRequest
对象与服务器通信。 它可以使用JSON,XML,HTML和text文本等格式发送和接收数据。AJAX最吸引人的就是它的“异步”特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。
你可以使用AJAX最主要的两个特性做下列事:
- 在不重新加载页面的情况下发送请求给服务器。
- 接受并使用从服务器发来的数据。
4、工作原理:
二、XMLHttpRequest 对象
怎样发送http请求?为了使用JavaScript向服务器发送一个http请求,你需要一个包含必要函数功能的对象实例XMLHttpRequest。
XMLHttpRequest
对象正是 AJAX 编程的实现方式。XMLHttpRequest
对象用于与服务器交互。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。
在此之前,都是通过在浏览器窗口输入url来获取整个页面数据,而ajax可以通过JavaScript代码创建XMLHTTPRequest对象来发送请求,与服务器交互,实现页面局部刷新页面。
所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均内建 XMLHttpRequest 对象。而老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveXObject对象。效果等同,只是浏览器内部实现的对象名字不一样。
怎么处理服务器的响应数据?
发送一个请求后,你会收到响应。在这一阶段,你要告诉XMLHttpRequest
请求对象是由哪一个JavaScript函数处理响应,onreadystatechange事件监听响应
,当请求状态改变时调用函数。
那么XMLHTTPRequest对象是如何来发送数据和处理响应的呢?
ajax是和网络有关的,所以XMLHTTPRequest对象包含了一切与处理请求头、响应头、状态码等等相关操作的属性、方法以及事件。
创建XMLHTTPRequest对象,发送请求,直到服务器响应,返回响应数据,都在XMLHTTPRequest对象上。比如服务器响应的数据对应XMLHTTPRequest对象的responseText属性;响应的状态码200,对应XMLHTTPRequest对象的 status 属性。
XMLHTTPRequest对象上方法、属性、事件:
方法:open()、send()、setRequestHeader()、abort()、getAllResponseHeaders()
属性:readyState状态、status状态码 200
代表一个成功的请求、statusText状态文本,200对应OK、timeout超时时间被终止请求、
response、responseText、responseType、responseURL、responseXML、
upload
事件:onreadystatechange、onload、ontimeout
上述提到,IE5和IE6浏览器是通过ActiveXObject对象来实现Ajax的,所以要进行兼容性的处理(IE8都没有多人用了,还有人用IE5 IE6?根本不用兼容)
1、创建XHR实例对象
var XHR = new XMLHttpRequest();
var XHR;
if (window.XMLHttpRequest) {
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
XHR = new XMLHttpRequest();
}
else {
// IE6, IE5 浏览器执行代码
XHR = new ActiveXObject("Microsoft.XMLHTTP");
}
2、XMLHttpRequest.open() 初始化http请求
设置以get或post的方式去打开一个url地址(open方法只是设置,send方法才是发送http请求)
xhrReq.open(method, url, async);
参数:
method:GET或POST
url:请求地址
async:一个可选的布尔参数,表示是否异步执行操作,默认为
true
。——异步如果值为
false
,send()
方法直到收到答复前不会返回。——同步。注意:主线程上的同步请求,程序要等会ajax的响应结束,而不会往下执行。很容易破坏用户体验,应该避免;实际上,许多浏览器已完全弃用主线程上的同步XHR支持。在Worker
中允许同步请求
3、MLHttpRequest.setRequestHeader() 设置请求头
我们通过http协议传输数据,客户端在发送数据时,要设置该条数据流是怎么的文件类型,以及要经过怎么样格式编码。
XMLHttpRequest.setRequestHeader() 是设置HTTP请求头部的方法。此方法必须在 open()
方法和 send()
之间调用。
比如:设置 Content-Type头部来指定数据流的MIME类型。
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
自定义一些header属性进行跨域请求时,可能会遇到"not allowed by Access-Control-Allow-Headers in preflight response",你可能需要在你的服务端设置"Access-Control-Allow-Headers"。
如果没有设置 Accept
属性,则此发送出send()
的值为此属性的默认值*/*
。
安全起见,有些请求头的值只能由浏览器客户端(user agent)设置,开发者设置的请求头会被视为无效。
Mime类型表:https://tool.oschina.net/commons
以下列举几个:
.txt text/plain
.html text/html
.jpg image/jpeg
.png image/png
.mp4 video/mpeg4
.css text/css
.js application/x-javascript
比如:发送json数据
XHR.setRequestHeader("Content-type","application/json; charset=utf-8")
发送纯文本数据
XHR.setRequestHeader("Content-type","text/plain; charset=utf-8")
参考阅读文章:https://blog.csdn.net/iamduoluo/article/details/7215639
4、XMLHttpRequest.send() 发送http请求
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/send
XMLHttpRequest.send()
方法用于发送 HTTP 请求。接收一个参数,作为发送给服务器的数据;如果是get请求,不接收参数,即不发送任何数据。
发送二进制内容的最佳方法(如上传文件)是使用一个与send()方法结合的 ArrayBufferView 或者Blobs。
send()
方法的参数可以是任何你想发送给服务器的内容,如果是 POST
请求的话。发送表单数据时应该用服务器可以解析的格式,像查询语句:
"name=value&anothername="+encodeURIComponent(myVar)+"&so=on"
或者其他格式, 类似 multipart/form-data
,JSON,XML等。
如果你使用 POST 数据,那就需要设置请求的MIME类型。比如,在调用 send()
方法获取表单数据前要有下面这个:
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
ajax的post请求,发送json数据:
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:8080/websocket');
xhr.onreadystatechange = function (event) {
console.log(xhr.readyState);
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr)
}
}
xhr.setRequestHeader("Content-type","application/json; charset=utf-8")
var data = {
name: 'zhu',
age: 24
}
xhr.send(JSON.stringify(data))
5、XMLHttpRequest.onreadystatechange
XMLHttpRequest对象上的readyState属性,随着此次ajax客户端和服务器交互数据,动态的改变readyState属性,不同的值,代表了ajax响应
的不同状态。
readyState属性值的状态表示:创建
XMLHttpRequest实例对象——>open方法创建http请求——>send方法发送http请求——>服务器正在响应——>服务器响应完成
从创建XMLHttpRequest对象开始,到发送数据、接收数据、XMLHttpRequest对象一共会经历以下5中状态。具体状态值对应的情况如下:
readyState 值 | 状态 | 描述 |
0 | UNSENT | 代理被创建,但尚未调用 open() 方法。0 (未初始化) or (请求还未初始化) |
1 | OPENED | open() 方法已经被调用。1 (正在加载) or (已建立服务器链接) |
2 | HEADERS_RECEIVED | send() 方法已经被调用,并且头部和状态已经可获得。2 (加载成功) or (请求已接受) |
3 | LOADING | 下载中; responseText 属性已经包含部分数据。3 (交互) or (正在处理请求) |
4 | DONE | 下载操作已完成。4 (完成) or (请求已完成并且响应已准备好) |
再来看readystatechange事件:
通过XMLHttpRequest.onreadystatechange监听readyState属性值的改变,只要readyState属性发生变化(0——>1,1——>2,2——>3,3——>4),就会触发
readystatechange事件。而我们要监听的正是readyState为4的情况。
只要 readyState
属性发生变化,就会调用readystatechange事件相应的处理函数。XMLHttpRequest.onreadystatechange
会在 XMLHttpRequest
的readyState
属性发生改变时触发 readystatechange
事件的时候被调用。
语法:当 readyState
的值改变的时候,callback
函数会被调用
XMLHttpRequest.onreadystatechange = callback;
var xhr= new XMLHttpRequest(),
method = "GET",
url = "https://www.easy-mock.com/mock/5b4c4032189fc57b63eb8410/example/mock";
xhr.open(method, url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText)
}
}
xhr.send();
XHR.readyState == 4 表示发送ajax请求成功且服务器响应成功。
XHR.status == 200 表示服务器成功响应了数据。
XHR.responseText 服务器响应的数据挂载在XMLHttpRequest对象的responseText属性上
在检查完请求状态和HTTP响应码后, 你就可以用服务器返回的数据做任何你想做的了。你有两个方法去访问这些数据:
httpRequest.responseText
– 服务器以文本字符的形式返回httpRequest.responseXML
– 以 XMLDocument 对象方式返回,之后就可以使用JavaScript来处理
6、XMLHttpRequest.timeout 和 timeout事件,ajax请求超时
(1)XMLHttpRequest.timeout属性
兼容IE8
设置超时时间,当ajax请求超时达到这个时间,就自动终止该条请求。浏览器在请求某个资源的时候,会一直在某个时间段内一直请求该资源,直到浏览器默认的超时时间,才会终止请求。
是一个无符号长整型数,代表着一个请求在被自动终止前所消耗的毫秒数。默认值为 0,意味着没有超时。为什么默认值是0?发送ajax请求,经常网络传输,经过服务器的处理,在正常的带宽时间内得到数据,就是没有超时。但是如果网络较慢,某些处理的环境会花费超过阈值时间,即为超时。
超时并不应该用在一个 document environment 中的同步 XMLHttpRequests 请求中,否则将会抛出一个 InvalidAccessError
类型的错误。
当超时发生, timeout 事件将会被触发。在IE中,超时属性可能只能在调用 open() 方法之后且在调用 send() 方法之前设置。
(2)timeout
事件 兼容IE10
当进度由于预定时间到期而终止时,会触发timeout
事件。
timeout
事件实现了 ProgressEvent
接口,它继承自 Event
— 它拥有在这个接口上定义的属性和方法。该事件上包括了ProgressEvent事件信息。
ProgressEvent
接口是测量如 HTTP 请求(一个XMLHttpRequest
,或者一个 <img>
,<audio>
,<video>
,<style>
或 <link>
等底层资源的加载)等底层流程进度的事件。
var XHR;
if (window.XMLHttpRequest) {
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
XHR = new XMLHttpRequest();
} else {
// IE6, IE5 浏览器执行代码
XHR = new ActiveXObject("Microsoft.XMLHTTP");
}
XHR.onreadystatechange = function (event) {
console.log(XHR.readyState);
if (XHR.readyState == 4 && XHR.status == 200) {
console.log(XHR)
}
}
XHR.ontimeout = function(){
console.log("超时100ms而终止ajax请求")
}
XHR.open("GET", "http://localhost:8080/about.html");
XHR.setRequestHeader("Content-type", "text/html, charset=utf-8");
XHR.timeout = 100; //设置超时时间100ms
XHR.send();
7、MLHttpRequest.onload 请求完成时触发load事件
不管成功还是失败,整个请求完成,即触发load事件。如果请求没有完成而终止,不触发load事件。比如由于超时timeout终止了请求,或者abort终止请求,导致ajax请求没有完成,不会触发load事件。
8、XMLHttpRequest.abort() 和 XMLHttpRequest的abort事件
(1)XMLHttpRequest.abort()
如果该请求已被发出,XMLHttpRequest.abort() 方法将终止该请求。当一个请求被终止,它的 readyState
将被置为 XMLHttpRequest.UNSENT
(0),并且请求的 status
置为 0。
(2)XMLHttpRequest.onabort 兼容性IE10
当一个请求终止时 abort
事件被触发,比如程序执行 XMLHttpRequest.abort()
。好像只监听abort方法触发的终止事件。
什么时候能执行abort方法,而成功的触发abort事件呢?在onreadystatechange事件中无法执行abort方法而终止请求;在timeout事件中无法执行abort方法而终止请求。好像不太容易终止掉。
测试时,在这种情况下,成功终止了:
var XHR;
if (window.XMLHttpRequest) {
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
XHR = new XMLHttpRequest();
} else {
// IE6, IE5 浏览器执行代码
XHR = new ActiveXObject("Microsoft.XMLHTTP");
}
XHR.onreadystatechange = function (event) {
if (XHR.readyState == 4 && XHR.status == 200) {
console.log(XHR)
}
}
// XHR.ontimeout = function(event){
// console.log(XHR);
// }
XHR.onload = function(event){
console.log(event)
}
XHR.onabort = function(){
console.log("ajax请求终止")
}
XHR.open("GET", "http://localhost:8080/about.html");
XHR.setRequestHeader("Content-type", "text/html, charset=utf-8");
// XHR.timeout = 100; //设置超时时间100ms
XHR.send();
console.log(XHR.readyState); //1
XHR.abort();
三、简单的实例:
发送ajax请求,获取一个html数据
var XHR;
if (window.XMLHttpRequest) {
// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
XHR = new XMLHttpRequest();
} else {
// IE6, IE5 浏览器执行代码
XHR = new ActiveXObject("Microsoft.XMLHTTP");
}
XHR.onreadystatechange = function (event) {
console.log(XHR.readyState);
if (XHR.readyState == 4 && XHR.status == 200) {
console.log(XHR)
}
}
XHR.open("GET", "http://localhost:8080/about.html");
XHR.setRequestHeader("Content-type", "text/html, charset=utf-8")
XHR.send();
结果:response属性和responseText属性为响应数据
四、原生ajax与jquery封装的ajax对比
jquery的ajax官网:https://api.jquery.com/jQuery.ajax/#jQuery-ajax-settings
好消息是原生ajax代码兼容所有最新的主流浏览器。而坏消息是使用起来十分复杂。的确令人恶心!
jQuery 已经成为了解决 Ajax 的默认标准。
1、关于请求缓存问题
jquery的ajax默认会清除浏览器缓存(注意是当前ajax请求的缓存,不是浏览器所有的缓存)。查看 jQuery 文档,可知道 jQuery 在每个请求(GET)后面追加一个时间戳作为查询字符串。这在某个程度上让请求变得独一无二,并避免浏览器缓存。当然,你能在纯 Ajax 达到该目的,而且相当简单:都是在url请求后面添加时间戳
var bustCache = '?' + new Date().getTime();
oReq.open('GET', e.target.dataset.url + bustCache, true);
2、关于请求头
jQuery 私下帮你设置请求头部了,所以后端能检测这是一个 Ajax 请求。一般来说,后端并不关心 GET 请求是从哪而来,只要能返回正确的响应即可。当你相用同样的 web API 返回 Ajax 或 HTML 时,这就派上用场了。让我们看看如何通过原生 JavaScript 设置请求头部:
var oReq = new XMLHttpRequest();
oReq.open('GET', e.target.dataset.url, true);
oReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
oReq.send();
与此同时,我们在 服务端做一个检测,拿Node.js举例:
if (req.headers['x-requested-with'] === 'XMLHttpRequest') {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({message: 'Hello World!'}));
}
正如你所看到的,原生 Ajax 是一个灵活且现代化的前端 API。你可以利用请求头部做很多事情,其中一种是版本控制。例如,我想让某个 web API 支持多个版本。但我又不想利用 URL,取而代之的是:通过设置请求头部,使客户端能选择它们想要的版本。所以,我们能这样设置请求头部:
oReq.setRequestHeader('x-vanillaAjaxWithoutjQuery-version', '1.0');
然后,在后端写上相应代码:
if (req.headers['x-requested-with'] === 'XMLHttpRequest' &&
req.headers['x-vanillaajaxwithoutjquery-version'] === '1.0') {
// Send Ajax response
}
3、关于响应类型
responseText
默认返回的是字符串 ,但是原生ajax可以设置前端 API 所期望服务器返回的数据类型。比如:
var oReq = new XMLHttpRequest();
oReq.onload = function (e) {
results.innerHTML = e.target.response.message;
};
oReq.open('GET', e.target.dataset.url, true);
oReq.responseType = 'json';
oReq.send();
这样我们就不必再对返回的纯文本解析为 JSON 了,我们能告诉 API 我们期待接收的数据类型。该特性几乎得到了所有最新主流浏览器的支持。当然,jQuery 会自动帮我们转为适当的类型。但遗憾的是,到 IE11 为止,开发团队仍未对 xhr.responseType='json' 进行支持。虽然该特性目前在 Microsoft Edge 得到支持。但这个 Bug 提出几乎两年了。 当然,可以这么做解决这个 IE兼容性的问题:
oReq.onload = function (e) {
var xhr = e.target;
if (xhr.responseType === 'json') {
results.innerHTML = xhr.response.message;
} else {
results.innerHTML = JSON.parse(xhr.responseText).message;
}
};
需要注意的是,ajax需要前后端的相互配合,前端发送ajax请求,设置相关的请求,经过客户端的处理(有些设置不被客户端允许),发送到服务端,服务端对ajax请求进行验证处理后,响应数据。
原生ajax使用的比较少,只需要知道一些相关的方法api,接下来还需要研究:
(1)请求头、响应头等等报文。
(2)jquery对ajax的实现和使用;
(3)cors跨域问题;
(4)Fetch
(5)cookie
(6)axios:基于promise的http库
(7)websocket双全工通讯协议
等等。。。
参考文章:
https://www.runoob.com/ajax/ajax-intro.html
https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
https://segmentfault.com/a/1190000004100271
https://developer.mozilla.org/zh-CN/docs/Web/Guide/AJAX/Getting_Started