原生js实现ajax

原生js实现ajax

ajax创建的步骤


  1. 实例化XMLHttpRequest(或ActiveXObject)对象

  2. 连接服务器

  3. 发送请求

  4. 接收服务器响应数据

  5. 根据响应的状态码进行做不同的处理

其中,在第4个步骤中需要引入一个回调函数,在接收到服务器响应的状态码时进行判断和处理

1.创建ajax对象

1
2
3
4
5
6
var xhr = null;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP'); //兼容IE6
}

2.连接服务器

主要用到了open函数,它的三个参数分别是:

请求方式、请求地址、是否设置异步请求(true表示异步,是默认值,false表示同步,同步请求的情况很少)

1
2
3
xhr.open("GET",url + ? + params,true);  //GET方式

xhr.open("POST",url,true); //POST方式

3.发送请求

请求的方式:

GET请求是通过将数据拼接到URL实现的,POST则是将数据作为发送请求的参数提交到服务器

POST在发送请求(send)之前要先设置表单提交的内容类型

GET提交信息时将请求内容拼接在URL中,所以send传入的参数为空(或者可以是null),POST就必须要把请求参数作为send的参数

1
2
3
4
xhr.send();  //GET方式

xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"); //POST方式
xhr.send(params);

完整点的demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function ajax(url, method, data) {
var xhr = null;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else if (window.ActiveObject) {
try {
xhr = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
} catch(e) {
alert("你的浏览器不支持ajax");
}
}
}
xhr.onerror = function(e) {
console.log(e);
}
xhr.open(method, url);
try {
setTimeout(function() {
xhr.send(data);
});
} catch(e) {
console.log('error:',e);
}
return xhr;
}

关于params

params是提交到服务器的参数,它必须经过encodeURIComponent()进行编码,每次发送请求时都会在参数列表中拼接类似v=xx的字符串,这样是为了拒绝生成缓存,每次都直接将请求发送到服务器

具体实现:

1
2
3
4
5
6
7
8
9
10
function formatParams(data){
var arr = [];
for(var name in data){
arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name]));
}
arr.push(("v=" + Math.random()).replace("."));
return arr.join("&");
}

var params = formaxtParams(data);

4.接收响应

  • responseText:获取字符串形式的响应数据,只读
  • responseXML:获取XML形式的响应数据,只读
  • status和statusText:以数字和文本形式返回HTTP状态码,只读
  • responseType:响应类型,缺省为空字符串,可以取arraybufferblobdocumentjsontext
  • responseURL:返回ajax请求最终的url,如果有请求过程有发生重定向,则返回重定向后的url
  • getAllResponseHeader():获取所有响应报头
  • getResponseHeader():查询响应中某个字段的值

readyState属性:

1
2
3
4
5
0:请求未初始化,open还未调用
1:服务器连接已经建立,open已经调用
2:请求已经接收,接收到头信息
3:请求处理中,接收到响应主体了
4:请求完成,响应就绪

status状态码:

1
2
3
4
5
1xx: 信息响应类,接收到请求并继续处理
2xx:处理成功响应类,动作被成功接收、理解和接受
3xx:重定向响应类,为了完成指定的动作,必须接受下一步处理
4xx:客户端错误,客户请求包含雨大错误或者是不能正确执行
5xx:服务端错误,服务器不能正确执行一个正确的请求

响应事件

onreadystatechange事件


该事件的回调方法在readyState状态改变时触发,在一个收到响应的ajax请求周期中,onreadystatechange方法会被触发4次。因此可以像下面那样处理:

1
2
3
4
5
6
7
xhr.onreadystatechange = function(e) {
if (xhr.readyState == 4) {
if((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
console.log(xhr.responseText);
}
}
}

较为完整的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var xhr = false;
if (XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHTTP");
};
if (xhr) {
xhr.open("GET","./data.json",true);
xhr.send();

xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(JSON.parse(xhr.responseText).name);
// 解析取到的json数据
}
}
}



onloadstart事件:

该事件回调在ajax请求发送前触发,触发的节点在readyState==1之后,readyState==2之前

它还默认传入一个ProgressEvent事件进度对象,包含了3个只读属性:

  • lengthComputable: boolean,默认false,表示长度是否可计算
  • loaded:表示已加载资源大小。若是http下载资源,不包括headers信息
  • total:表示资源总大小。若是http下载资源,不包括headers信息



onprogress事件:

该事件在reaadyState==3时触发,默认传入ProgressEvent进度对象,主要是用来获取资源下载进度。

注意,本事件只适用于IE10+


onload事件:

该事件在ajax请求成功后触发,也就是readyState==4

onloadend事件:

该事件在ajax请求完成后触发,也就是readyState==4后或者readyState==2后。也是默认传入ProgressEvent事件进度对象


ontimeout事件:

请求超时时触发

1
2
3
4
xhr.timeout = 5;
xhr.ontimeout = function(e){
console.error('请求超时')
}




请求二进制文件

处理二进制文件主要使用的是html5的FileReader

相关属性:

  • error:表示读取文件器件发送的错误
  • readyState:表示读物文件的状态。0:文件未加载;1:文件正在读取;2:文件读取完成
  • result:读取的文件内容

相关方法:

  • readAsArrayBuffer:读取文件(或blob对象)为类型化数组(ArrayBuffer), 类型化数组允许开发者以数组下标的方式, 直接操作内存, 由于数据以二进制形式传递, 效率非常高
  • readAsDataURL:读取文件(或blob对象)为base64编码的URL字符串, 与window.URL.createObjectURL方法效果类似
  • readAsText:读取文件(或blob对象)为文本字符串
  • onload:文件读取完成时的事件回调, 默认传入event事件对象. 该回调内, 可通过this.result 或 event.target.result获取读取的文件内容

ajax请求二进制图片并预览:__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var xhr = new XMLHttpRequest(),
url = '';
xhr.open("GET", url);
xhr.responseType = "blob";
xhr.onload = function(){
if(this.status == 200) {
var blob = this.response;
var img = document.createElement("img");
//方案1
img.src = window.URL.createObjectURL(blob); //这里blob依然占据内存
img.onload = function() {
window.URL.revokeObjectURL(img.src); // 释放内存
};
// 方案2
var reader = new FileReader();
reader.readAsDataURL(blob);//FileReader将返回base64编码的data-uri对象
reader.onload = function(){
img.src = this.result;
}
// 方案3
img.src = url;

document.body.appendChild(img);
}
}
xhr.send();

xhr对象

xhr level1

  • 仅支持文本数据传输,无法传输二进制数据
  • 传输数据时,无进度信息提示,只提示是否完成
  • 同源策略,无法请求跨域资源
  • 无超时机制

xhr level2

  • 支持二进制数据,可以上传文件,可以用FormData对象管理表单
  • 有进度提示,可以用xhr.upload.onprogress事件回调方法获取传输进度
  • 还是有同源策略限制,但是用户可以将Access-Control-Allow-Origin等headers设置为*表示允许任何域名请求
  • 可以设置timeout和ontimeout

xhr level2支持IE10及以上,IE8和IE9可以使用XDomainRequest来解决

CORS

全称:跨域资源共享(Cross-origin resource sharing)


它允许浏览器向跨域服务器发出异步http请求, 从而克服了ajax受同源策略的限制

在实际通信过程中,浏览器不会拦截不合法的跨域请求,而是拦截服务器返回的响应,服务器实际上是接收到客户端发出的请求的,只是服务端向客户端发送的响应被浏览器拦截下来了

相关的headers信息

HTTP Response Header(服务器提供)

  • Access-Control-Allow-Origin: 指定允许哪些源的网页发送请求.

  • Access-Control-Allow-Credentials: 指定是否允许cookie发送.

  • Access-Control-Allow-Methods: 指定允许哪些请求方法.

  • Access-Control-Allow-Headers: 指定允许哪些常规的头域字段, 比如说 Content-Type.

  • Access-Control-Expose-Headers: 指定允许哪些额外的头域字段, 比如说 X-Custom-Header.

该字段可省略. CORS请求时, xhr.getResponseHeader() 方法默认只能获取6个基本字段: Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma . 如果需要获取其他字段, 就需要在Access-Control-Expose-Headers 中指定. 如上, 这样xhr.getResponseHeader(‘X-Custom-Header’) 才能返回X-Custom-Header字段的值.(该部分摘自阮一峰老师博客)

  • Access-Control-Max-Age: 指定preflight OPTIONS请求的有效期, 单位为秒.

HTTP Request Header(浏览器OPTIONS请求默认自带)

  • Access-Control-Request-Method: 告知服务器,浏览器将发送哪种请求, 比如说POST.
  • Access-Control-Request-Headers: 告知服务器, 浏览器将包含哪些额外的头域字段.

CORS请求

可分为简单请求和非简单请求,满足下列两个条件的就是简单请求

1)请求是以下3种之一:

  • HEAD
  • GET
  • POST

2)http头不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type字段限三个值 application/x-www-form-urlencodedmultipart/form-datatext/plain

对于简单请求,浏览器将发送一次http请求,同时在Request头域中增加Origin字段,用来表示请求发起源,服务器根据该源采取不同的响应策略,如果服务器认为该请求合法,那么就会在返回的HTTP Response中加入Access-Control-*等字段

对于非简单请求,浏览器会发送两次http请求来验证源是否合法,其中第二次才是真正的http请求



一些demo

原生js上传文件并绑定事件

1
2
3
4
5
6
7
8
9
10
var xhr = ajax(url, method, formDate);
xhr.upload.onprogress = function(e) {
console.log("upload progress:",e.loaded/e.total*100 + "%");
};
xhr.upload.onload = function(){
console.log("upload onload");
};
xhr.onload = function(){
console.log("onload");
}

fetch上传

fetch只要发送一个POST请求,并将body属性设置为formData即可,但是它无法跟踪上传的进度信息

1
2
3
4
5
6
7
8
fetch(url, {
method: method,
body: formData
}).then(function(res){
console.log(res);
}).catch(function(e){
console.log(e);
})

jquery文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$.ajax({
type: method,
url: url,
data: formData,
progressData: false,
contentType: false, //必须是false才会自动加上正确的Content-Type
xhr: function() {
var xhr = $.ajaxSettings.xhr(); //实际就是return new window.XMLHttpRequest()对象
if(xhr.upload) {
xhr.upload.addEventListener("progress", function(e){
console.log("jq upload progress:", e.loaded/e.total*100 + "%");
}, false);
xhr.upload.addEventListener("load", function(){
console.log("jq upload onload.");});
xhr.addEventListener("load", function(){
console.log("jq onload.");
});
return xhr;
}
}
});





参考:

Ajax知识体系大梳理

Ajax关于readyState(状态值)和status(状态码)的研究

HTML5新特性之文件和二进制数据的操作