目录
🌐 原生ajax
1. 什么是AJAX?
2. AJAX的使用
2.1 创建XHR对象
2.2 向服务器发送请求
2.3 服务器响应
2.4 onreadystatechange 事件
3. 手写一个简易的AJAX
🌐 现代AJAX的使用方式
1. jQuery封装的ajax
2. fetch API
3. axios
🌐 原生ajax
现代浏览器,最开始与服务器交换数据,都是通过XMLHttpRequest对象。它可以使用JSON、XML、HTML和text文本等格式发送和接收数据。
多年来,
XMLHttpRequest
一直是web开发者的亲密助手。无论是直接的,还是间接的, 当我们谈及Ajax技术的时候,通常意思就是基于XMLHttpRequest
的Ajax,它是一种能够有效改进页面通信的技术。 Ajax的兴起是由于Google的Gmail所带动的,随后被广泛的应用到众多的Web产品(应用)中,可以认为, 开发者已经默认将XMLHttpRequest
作为了当前Web应用与远程资源进行通信的基础。
1. 什么是AJAX?
AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML),意思是异步网络请求。区别于传统web开发中采用的同步方式。
AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
AJAX 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。
Ajax带来的最大影响就是页面可以无刷新的请求数据。以往,页面表单提交数据,在用户点击完”submit“按钮后,页面会强制刷新一下,体验十分不友好。 有很多使用 AJAX 的应用程序案例:新浪微博、Google 地图、开心网等等。 可以这么说AJAX的出现极大了推动了前端的发展。
2. AJAX的使用
2.1 创建XHR对象
所有现代浏览器均支持 XMLHttpRequest 对象(IE5 和 IE6 使用 ActiveXObject)。XMLHttpRequest 用于在后台与服务器交换数据。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。 所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均内建 XMLHttpRequest 对象。
let xmlhttp = new XMLHttpRequest()
老版本的 Internet Explorer (IE5 和 IE6)使用 ActiveX 对象:
let xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
处理兼容性问题:
let xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp = new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
2.2 向服务器发送请求
向服务器发送请求就要用到XMLHtttpRequest对象的open和send方法
xmlhttp.open("GET","test1.txt",true);
xmlhttp.send();
GET or POST ?
与 POST 相比,GET 更简单也更快,并且在大部分情况下都能用。
然而,在以下情况中,请使用 POST 请求:
- 无法使用缓存文件(更新服务器上的文件或数据库)
- 向服务器发送大量数据(POST 没有数据量限制)
- 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠
2.3 服务器响应
如需获得来自服务器的响应,请使用 XMLHttpRequest 对象的 responseText属性。 responseText 属性返回字符串形式的响应,因此您可以这样使用:
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
2.4 onreadystatechange 事件
当请求被发送到服务器时,我们需要执行一些基于响应的任务。
每当 readyState 改变时,就会触发 onreadystatechange 事件。
readyState 属性存有 XMLHttpRequest 的状态信息。
下面是 XMLHttpRequest 对象的三个重要的属性:
属性 | 描述 |
---|---|
onreadystatechange | 存储函数(或函数名),每当 readyState 属性改变时,就会调用该函数。 |
readyState | 存有 XMLHttpRequest 的状态。从 0 到 4 发生变化。- 0: 请求未初始化 1: 服务器连接已建立2: 请求已接收3: 请求处理中4: 请求已完成,且响应已就绪 |
status | 200: "OK";404: 未找到页面 |
3. 手写一个简易的AJAX
这也算是面试中的一个高频考点了,虽然工作中不会中不会使用原生的AJAX而是使用封装好的AJAX,但是掌握基本原理还是必要的,这也应该是每位程序员的素养~
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}else{
console.log('404 NOT FOUND')
}
}
xhr.send()
上面这种是最简易的,我们还可以结合Promise来实现,看下面一个版本👇
function ajax(url) {
const p = new Promise((resolve, reject) => {
let xhr = XMLHttpRequest()
xhr.open("GET", url, true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
resolve(xhr.responseText)
} else {
reject(new Error("404 NOT FOUND"))
}
}
xhr.send()
})
return p
}
番外:封装原生的请求
function obj2str(data) { data = data || {}; // 如果没有传参, 为了添加随机因⼦,必须⾃⼰创建⼀个对象 data.t = new Date().getTime(); var res = []; for (var key in data) { //在URL中是不可以出现中⽂的,如果出现了中⽂需要转码,可以调⽤encodeURIComponent⽅法,URL中 res.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key])); } return res.join("&"); } function ajax(option) { var str = obj2str(option.data); //key=value&key=value; var xmlhttp, timer; if (option.type.toLowerCase() === "get") { //toLowerCase将⼤写转化为⼩写 xmlhttp.open(option.type, option.url + "?" + str, true); xmlhttp.send(); } else { xmlhttp.open(option.type, option.url, true); //注意点:以下代码必须放在open和send之间 xmlhttp.setRequestHeader( "Content-type", "application/x-www-form-urlencoded" ); xmlhttp.send(str); } xmlhttp.onreadystatechange = function (ev2) { if (xmlhttp.readyState === 4) { clearInterval(timer); //判断是否请求成功(Http状态码⼤于等于200,且⼩于300,和状态码等于304为请求成功) if ( (xmlhttp.status >= 200 && xmlhttp.status < 300) || xmlhttp.status === 304 ) { option.success(xmlhttp); } else { option.error(xmlhttp); } } }; if (option.timeout) { timer = setInterval(function () { console.log("中断请求"); xmlhttp.abort(); clearInterval(timer); }, option.timeout); } }
使用:👇
ajax({ url:"http://server-name/login", type:'post', data:{ username:'username', password:'password' }, dataType:'json', timeout:10000, contentType:"application/json", success:function(data){ console.log(data); //服务器返回响应,根据响应结果,分析是否登录成功 }, //异常处理 error:function(e){ console.log(e); } })
原生ajax的优点:
- 不重新加载页面的情况下更新网页
- 在页面已加载后从服务器请求/接收数据
- 在后台向服务器发送数据。
原生ajax的缺点:
- 使用起来也比较繁琐,需要设置很多值。
- 早期的IE浏览器有自己的实现,这样需要写兼容代码。
🌐 现代AJAX的使用方式
使用原生的js还是比较繁琐,实际工程中建议使用jQuery之类的库,封装的ajax请求方法非常好用,且解决了浏览器兼容性的问题。
1. jQuery封装的ajax
为了更快捷的操作DOM,并且规避一些浏览器兼容问题,产生了jQuery。它里面的AJAX请求也兼容了各浏览器,可以有简单易用的方法$.get,$.post。简单点说,就是对XMLHttpRequest对象的封装。
$.ajax({
type: 'POST',
url: url,
data: data,
dataType: dataType,
success: function () {},
error: function () {}
})
jQuery-ajax的优点:
- 对原生XHR的封装,做了兼容处理,简化了使用。
- 增加了对JSONP的支持,可以简单处理部分跨域。
jQuery-ajax的缺点:
- 如果有多个请求,并且有依赖关系的话,容易形成回调地狱。
- 本身是针对MVC的编程,不符合现在前端MVVM的浪潮。
- ajax是jQuery中的一个方法。如果只是要使用ajax却要引入整个jQuery非常的不合理。
关于jQuery中的AJAX:
由于jQuery的使用频率已经很低了,关于jQuery中的AJAX如何使用,可以参看:jQuery中如何发送ajax请求以及解决跨域问题,当然,心有余力的小伙伴们可以自行查阅jQuery关于ajax的相关资料。
2. fetch API
fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多了,参数有点像jQuery ajax。但是,一定记住:fetch不是ajax的进一步封装,而是原生的js,没有使用XMLHttpRequest对象。
fetch是前端发展的一种新技术产物,是XMLHttpRequest
的最新替代技术,它是W3C的正式标准
以下内容摘自mozilla:
Fetch API 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。
这种功能以前是使用 XMLHttpRequest实现的。Fetch提供了一个更好的替代方法,可以很容易地被其他技术使用,例如 Service Workers。Fetch还提供了单个逻辑位置来定义其他HTTP相关概念,例如CORS和HTTP的扩展。
在使用fetch的时候需要注意:
- 当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500错误码(fetch只对网络请求报错,对400,500都当做成功的请求,需要封装去处理)。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
- 默认情况下,fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。
fetch是低层次的API,代替XHR,可以轻松处理各种格式,非文本化格式。可以很容易的被其他技术使用,例如Service Workers。但是想要很好的使用fetch,需要做一些封装处理。
一个使用fetch获取数据的例子:👇
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(myJson);
});
fetch代表着更先进的技术方向,但是目前兼容性不是很好,在项目中使用的时候得慎重。
fetch 是比较新的技术,低版本浏览器和IE浏览器支持性不好
fetch优点
①、语法简介,更加语义化
②、基于标准的promise实现,支持async/await
③、同构方便,使用isomorphic-fetch
④、更加底层,提供的API丰富
⑤、脱离了XHR,是ES规范里新的实现方式
⑥、其考虑到了传输的文件比较大的时候的处理
fetch缺点
fetch 是一个低层次的API,你可以把它考虑成原生的XHR,所以使用起来并不是那么舒服,需要进行封装。
①、当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的 Promise 不会被标记为 reject,即使响应的 HTTP 状态码是 404 或 500错误码。相反,它会将 Promise 状态标记为 resolve (如果响应的 HTTP 状态码不在 200 - 299 的范围内,则设置 resolve 返回值的 ok 属性为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
②、fetch 不会发送跨域 cookies(fetch请求默认不会带cookie),需要添加配置项: fetch(url, {credentials: ‘include’})
③、fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
④、fetch没有办法原生监测请求的进度,而XHR可以
请注意,fetch规范与jQuery.ajax()主要有两种方式的不同,牢记:
- 当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
- 默认情况下,fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。
🙋♂️ 番外:为什么要使用fetch
XMLHttpRequest 是一个设计粗糙的 API,不符合关注分离(Separation of Concerns)的原则,配置和调用方式非常混乱,而且基于事件的异步模型写起来也没有现代的 Promise,generator/yield,async/await 友好。
Fetch 的出现就是为了解决 XHR 的问题,拿例子说明:
使用 XHR 发送一个 json 请求一般是这样:
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function() {
console.log(xhr.response);
};
xhr.onerror = function() {
console.log("Oops, error");
};
xhr.send();
使用 Fetch 后,顿时看起来好一点
fetch(url).then(function(response) {
return response.json();
}).then(function(data) {
console.log(data);
}).catch(function(e) {
console.log("Oops, error");
});
使用 ES6 的 箭头函数 后:
fetch(url).then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))
现在看起来好很多了,但这种 Promise 的写法还是有 Callback 的影子,而且 promise 使用 catch 方法来进行错误处理的方式有点奇怪。不用急,下面使用 async/await 来做最终优化:
注:async/await 是非常新的 API,属于 ES7,这是它的完整规范。使用 Babel 开启 runtime 模式后可以把 async/await 无痛编译成 ES5 代码。也可以直接使用 regenerator 来编译到 ES5。
// 注:这段代码如果想运行,外面需要包一个 async function
try {
let response = await fetch(url);
let data = response.json();
console.log(data);
} catch(e) {
console.log("Oops, error", e);
}
duang~~ 的一声,使用 await
后,写异步代码就像写同步代码一样爽。await
后面可以跟 Promise 对象,表示等待 Promise resolve()
才会继续向下执行,如果 Promise 被 reject()
或抛出异常则会被外面的 try...catch
捕获。
Promise,generator/yield,await/async 都是现在和未来 JS 解决异步的标准做法,可以完美搭配使用。这也是使用标准 Promise 一大好处。最近也把项目中使用第三方 Promise 库的代码全部转成标准 Promise,为以后全面使用 async/await 做准备。
另外,Fetch 也很适合做现在流行的同构应用,有人基于 Fetch 的语法,在 Node 端基于 http 库实现了 node-fetch,又有人封装了用于同构应用的 isomorphic-fetch。
注:同构(isomorphic/universal)就是使前后端运行同一套代码的意思,后端一般是指 NodeJS 环境。
3. axios
首先,你需要知晓:axios并不是一种新的技术,而只是一种技术方案。
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,有以下特点:
- 从浏览器中创建 XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF
- 提供了一些并发请求的接口(重要,方便了很多的操作)
axios的使用方法
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 上面的请求也可以这样做
axios.get('/user', {
params: {
ID: 12345
}
}).then(function (response) {
console.log(response);
}).catch(function (error) {
console.log(error);
});
关于更多axios的使用,大家请参考: axios官方文档
浏览器支持
axios面向现代浏览器设计(只支持现代浏览器),所以,古老的浏览器并不支持。
因为axios设计简洁,API简单,支持浏览器和node,所以大受欢迎。它能很好的与各种前端框架整合。因此,推荐大家在项目中使用axios库。
目前来看axios算是比较完美的一种方案了,几乎没有什么大的缺陷。
小结:
原生XHR几乎很少开发会用,JqueryAjax属于老当益壮的那种,虽然很老,但是很好用,Fetch是属于初生牛犊,还需要慢慢成长,axios就目前来说,算是非常好的了,无脑使用即可。
参考资料:Ajax、fetch、axios的区别与优缺点