封装原生Ajax 和 Axios的 二次封装

AJAX

异步的JavaScript与XML技术( Asynchronous JavaScript and XML )

Ajax 核心使用 `XMLHttpRequest` (XHR)对象,首先由微软引入的一个特性;Ajax 不需要任何浏览器插件,能在不更新整个页面的前提下维护数据(可以向服务器请求额外的数据无需重载页面),但需要用户允许JavaScript在浏览器上执行。

XHR 对象用法

1 var xhr = new XMLRequestHttp() // 通过XMLHttpRequest 构造函数来创建

open 方法

xhr.open(method, url, async, user, password);

method:要发送的请求类型 GET、POST、PUT、DELETE 。(必选)

url:请求的URL (必选)

axync :布尔值,是否异步发送请求,默认true(true 时,已完成事务的通知可供事件监听使用,如果 xhr.multipart为true,则此参数必须为true;false 时,send()方法直到收到答复前不会返回)

user:用户名用于认证用途 默认 null

password:用户名用于认证用途 默认 null

调用open方法不会真正发送请求,只是准备发送请求,并且URL有同源策略的限制(须和页面的主域、端口、协议一致,只要一处不符合要求将报错,数据将被拦截,可通过前后端配置,或使用代理来解决)。

setRequestHeader()

如需设置 Accpet 头部信息,可通过setRequestHeader() 方法来设置

Accpet 头部信息:告知客户端可以处理的内容类型,用 MIME类型 表示;使用 Content-Type 服务端使用 `Content-Type` 通知客户端它的选择

媒体类型( MIME类型 ) :一种标准,用来表示文档、文件或字节流的性质和格式。 完整类型列表

Content-Type :实体头部用于指示资源的 MIME 类型,告诉客户端实际返回的内容类型;浏览器会在某些情况下进行MIME查找,并不一定遵循此标题的值; 为了防止这种行为,可以将标题 X-Content-Type-Options 设置为 nosniff。

xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');

send 方法

xhr.send(data);

data:作为请求主体发送的数据,如果不需要通过请求主体发送数据,则必须传 null

调用 send()发送请求,在收到响应后,响应的数据会自动填充XHR对象的属性

responseText :从服务端返回的文本

1 xhr.onload = function () {

2 if (xhr.readyState === xhr.DONE) {

3 if (xhr.status === 200) {5 console.log(xhr.responseText);

6 }

7 }

8 };

responseXML

如果响应的 Content-Type 为 text/html 或 application/xml,将保存包含响应数据的 XML DOM 文档,对于其它类型的数据则为 null, 也可通过overrideMimeType() 强制 XHR 对象解析为 XML

1 // overrideMimeType() 用来强制解析 response 为 XML

2 xhr.overrideMimeType('text/xml');

3

4 xhr.onload = function () {

5 if (xhr.readyState === xhr.DONE) {

6 if (xhr.status === 200) { 8 console.log(xhr.responseXML);

9 }

10 }

11 };

status

返回响应的HTTP状态码,请求完成前值为0,如果XHR 对象出错 值也是0, 200 表示请求成功,304表示请求的资源并没有修改,可直接使用浏览器种缓存的数据。 其它状态信息

statusText

返回响应的HTTP状态说明,status 值为 200 时 statusText为 "OK"

readyState

返回一个当前XHR对象所处的活动状态

值状态描述

0

UNSENT

代理被创建,但尚未调用 open() 方法。

1

OPENED

open() 方法已经被调用。

2

HEADERS_RECEIVED

send() 方法已经被调用,并且头部和状态已经可获得。

3

LOADING

下载中;响应体部分正在被接收 responseText 属性已经包含部分数据。

4

DONE

下载操作已完成。

onreadystatechange

当 readyState变化时会触发次事件函数,如果使用 abort() 取消请求则次事件函数不会被触发

1 xhr.onreadystatechange = function () {

2 if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {

3 console.log(xhr.responseText)

4 }

5 }

参考资料

兼容性

封装 XMLHttpRequest 对象

1 // 创建 构造函数

2 function Ajax(obj) {

3 this.url = obj.url ||'';

4 this.type = obj.type || 'get';

5 this.data = obj.data ||{};

6 this.success = obj.success || null;

7 this.error = obj.error || null;

8 }

9 // 原型上创建方法支持 post 和 get

10 Ajax.prototype.send = function(){

11 var self = this;

12 var toStr = Object.prototype.toString;

13 if (self.data === null && typeof self.data !== 'object' && Array.isArray(obj)) return;

14 return (function(){

15 // 实例化 XML对象

16 var xhr = new XMLHttpRequest();

17 var data = '';

18 // 序列化参数

19 for (var k in self.data){

20 data += k + '=' + self.data[k] + '&';

21 }

22 data = data.substr(0,data.length - 1);

23 // 接收回调函数

24 xhr.onreadystatechange = function(){

25 if (xhr.readyState === 4){

26 if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {

27 isFunction(self.success) && self.success(xhr.responseText)

28 }else{

29 isFunction(self.error) && self.error(xhr)

30 }

31 }

32 }

33 // 初始化请求

34 if(self.type.toLocaleLowerCase() === 'post'){

35 xhr.open ('post',self.url,true)

36 // 设置请求头

37 xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');

38 //发送请求

39 xhr.send(data)

40 } else {

41 xhr.open('get', self.url + "?" + data,true)

42 xhr.send(null)

43 }

44 }());

45 };

46

47 function isFunction(obj){

48 return toStr.call(obj) === "[object Function]"

49 }

50

51 var ajax = new Ajax({

52 type:'post',

53 url:"/login",

54 data:{

55 loginname:"admin",

56 password:"admin"

57 },

58 success:function(e){

59 console.log(e)

60 },

61 error:function(err){

62 console.log(err)

63 },

64 }).send();

XMLHttpRequest Level 2 相比于 老版本的 XMLHttpRequest 新增以下内容:

可以设置 HTTP 请求超时时间

1 var xhr = XMLHttpRequest();

2 xhr.open('GET'.'url');

3 // 超时 2s

4 xhr.timeout = 2000;

5 // 超时处理

6 xhr.ontimeout = function(e) {

7 console.log(e)

8 }

9 xhr.send(null)

可以通过 FormData 发送表单数据

1 // 实例化 FormData

2 var formData = new FormData();

3 // 添加数据

4 formData.append(key,value);

5

6 xhr.open('POST','url');

7 xhr.send(formData);

可以上传文件

FormData 除了可以添加字符串数据,也可以添加 blob、file 类型的数据,因此可以用于上传文件。

在浏览器中,一般是通过文件上传输入框来获取 file 对象,比如:

1

1 document.getElementById('upload-file')

2 .addEventListener('change', function () {

3

4 var formData = new FormData();

5 // 获取数据

6 formData.append('uploadFile', this.files[0])

7 xhr.send(formData)

8 })

支持跨域请求

浏览器默认是不允许跨域请求的,有时候又是必要的,在以前通常使用JSONP来解决(IE10 以下不支持)

为了标准化跨域请求, W3C提出 跨域资源共享(CORS)前端无须修改代码,只需 服务器返回 Access-Control-Allow-Origin 响应头,指定允许对应的域

CORS 默认不发送 cookie 如果需要发送,前端需要设置 withCredentials 属性,同时服务器需要 返回 Access-Control-Allow-Credentials: true,

xhr.withCredentials = true;

检测XHR是否支持CORS最简单的方式,就是检查是否存在 `withCredentials`,再检测`XDomainRequest` 对象是否存在,即可兼顾所有浏览器

1 let createCORSRequest = (method,url)=>{

2 let var xhr = mew XMLHttpRequest();

3 if ('withCredentials' in xhr){

4 xhr.open(method,url,true);

5 }else if(typeof XDomainRequest != 'undefined'){

6 xhr = new XDomainRequest();

7 xhr.open(method,url);

8 }else{

9 xhr = null

10 }

11 return xhr

12 }

13 let request = createCORSRequest('get','baidu.com')

14 if(request){

15 request.onload = function(){

16 // request.responseText

17 }

18 request.send()

19 }

Preflighted Requests:

- 一个透明服务器验证机制,用于检查服务器是否支持[CORS](http://www.ruanyifeng.com/blog/2016/04/cors.html)

这是一个 OPTIONS 请求,使用了三个请求头

- Access-Control-Request-Method:请求自身使用的方法

- Access-Control-Request-Headers:自定义头部信息,多个头部以逗号分隔

- Origin报头:和简单请求相同,将请求的域发送给服务端,服务端再Access-Control-Allow-Origin 响应头中返回同样的域即可解决跨域问题。

img src特性:

- 一个网页可以从任何网页中加载图像,不用担心跨域问题,通过onload 和 onerror 事件处理确定是否接收到响应

- 请求的数据通过查询字符串形式发送,响应可以是任意内容,通常是像素图或204响应。

- 只能发送 get 请求,无法访问服务器的响应文本

1 let img = new Image();

2 img.onload = function (){

3 console.log('done')

4 }

5 img.src = 'http://www.baidu.com?test=test1'

可以获取服务端二进制数据

1. 使用 overrideMimeType 方法覆写服务器指定的 MIME 类型,从而改变浏览器解析数据的方式

1 // 参数 MIME 类型

2 // 告诉浏览器,服务器响应的内容是用户自定义的字符集

3 xhr.overrideMimeType('text/plain; charset=x-user-defined');

4 // 浏览器就会将服务器返回的二进制数据当成文本处理,我们需要做进一步的转换才能拿到真实的数据

5 // 获取二进制数据的第 i 位的值

6 var byte = xhr.responseText.charCodeAt(i) & 0xff

"& 0xff" 运算 参考 阮一峰的文章

2.xhr.responseType 用于设置服务器返回的数据的类型,将返回类型设置为 blob 或者 arraybuffer,然后就可以从 xhr.response 属性获取到对应类型的服务器返回数据。

1 xhr.responseType = 'arraybuffer'

2 xhr.onload = function () {

3 var arrayBuffer = xhr.response

4 // 接下来对 arrayBuffer 做进一步处理...

5 }

可以获取数据传输进度信息 参考资料

使用 onload 监听了一个数据传输完成的事件。

1 // 上传进度监听

2 xhr.upload.addEventListener('progress', onProgressHandler, false);

3

4 // 传输成功完成

5 xhr.upload.addEventListener('load', onLoadHandler, false);

6 // 传输失败信息

7 xhr.upload.addEventListener('error', onErrorHandler, false);

更多资料参考: 阮一峰的文章 MDN

AXIOS