ajax

ajax

1、简单地说,ajax就是一种能够在页面不刷新的情况下从外部获取数据的技术。

var xhr = new XMLHttpRequest();
xhr.open(‘get’,‘a.php’,true);
xhr.send(null);

以上代码中,我们向a.php发送了一个get请求,首先,我们创建了一个XMLHttpRequest对象的实例,
然后调用实例的open方法,open方法接受三个参数,
第一个参数表示请求方式,第二个参数表示请求的文件路径,
第三个参数如果是false,表示为执行同步请求,后续的代码将会在请求的数据返回来之后才被执行,如果是true,则表示执行异步请求,即后续的代码不会等待数据返回,而是立即执行。
最后,需要调用send方法发送请求,send方法接受一个参数,该参数值将会被添加到请求主体当中,如果不需要携带数据,则必须传入null。

2、XMLHttpRequest对象的属性

responseText 表示服务端返回的数据
responseXML 表示服务端返回的XML格式的数据,如果返回的资源不是此格式类型的,该属性值为null
status 代表通讯的状态码,状态码有数字表示,如200表示请求成功,此时可以访问服务端返回的数据了
statusText 代表通讯的状态的文本形式,如’ok’表示请求成功,等同于status的200状态码

status属性可能返回的状态码有很多,但常用的其实就那么几个,除了表示请求成功的200状态码之外,还有304状态码,表示请求的资源未被修改,浏览器会根据这个状态码从缓存中读取相应的资源。

请求的方式分为两种,一种是同步请求,这种方式会等待服务端返回响应后才会继续执行后续的代码,这样的方式显示会造成糟糕的用户体验,因此在现代浏览器中已经被废弃。
我们使用的通常是异步请求,就是后续代码的执行不需要等待服务端的响应。
在这种方式下,我们如何在服务端返回数据后再做点什么呢?事实上,整个通讯流程被分为多个阶段,每个阶段发生变化的时候都会调用xhr对象上的readystatechange事件,并更新xhr对象上readystate属性,这个属性上保存着一个数字,用数字来表示通讯流程中的各个阶段,如:

0 表示未调用open方法
1 表示已经调用open方法
2 表示已经调用send方法
3 表示接受到部分响应数据
4 表示接受完成,可以访问响应的数据了。

因此,我们可以通过readystatechange事件属性指定一个事件处理函数,在函数中通过readystate属性来判断数据是否接受完成了。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {

	if (xhr.readyState === 4) {
		if (xhr.status === 200 || xhr.status === 304) {
			console.log(xhr.responseText)
		}else {
			console.log(xhr.status)
		}
	}
}

xhr.open('get','a.php',true);
xhr.send();

上述代码中,通过onreadystatechange事件,我们让readystate属性值等于4,也就是接受完全部响应数据之后,再通过查看status属性来查看服务端是否返回了我们想要的数据,再根据此条件来打印出数据,或是抛出错误。
需要注意的是,务必在open方法之前监听onreadystatechange事件,否则在不同的浏览器中可能会出现兼容性问题导致错误发生。

3、readyState和status的区别

有的人可能会不太清楚这两者的区别,首先,readyState可能返回0-4的数值,这5个数值分别对应的是一个请求从初始化,创建请求,发送请求,接受响应,接受完毕这个五个步骤。而status属性包含的是有关响应内容的具体信息。
当readyState属性值等于4的时候,表示可以访问响应内容了,但响应的内容却不一定是你想要的。
打个比方,比如你写了一封信,邮寄给远方的亲戚,你在信中表达了自身的困难处境,希望能得到一笔资金援助,几天之后,一个包裹邮寄回来了。这一系列的阶段,可以用一些数字来表示,1表示你写了封信,2表示你发送了信件,3表示你接受到了邮件,事实上,这个3代表的是你接受到包裹的那个阶段(对应readyState等于4),之后,当你兴奋地打开包裹的时候,却发现里面并没有人民币,只有一张白纸,写着:不借!所以,当包裹里装的是人民币的时候,我们可以让status值等于200,表示包裹里是你想要的东西,而不是其他的。

因此,如果你只使用readyState来判断的话,就会产生问题:

xhr.onreadystatechange = function () {

	if (xhr.readyState === 4) {
		var res = xhr.responseText;
		console.log(res.name)
	}
}

以上代码可能发生错误,因为虽然你收到了包裹,但其中装的并不一定是你想要的东西,也就是responseText可能是空的。

或者,只使用status来判断,同样也会产生问题

xhr.onreadystatechange = function () {

	if (xhr.status === 200 || xhr.status === 304) {
		var res = xhr.responseText;
		console.log(res.name)
	}
}

以上代码有两个误区,第一,当通讯阶段发生变化的时候,都会调用onreadystatechange事件,因此,函数会被执行多次,第二,在通讯流程处于0-3的阶段的时候,由于响应数据还没有接受或接受不完整,这是对其进行操作就会发生错误。

需要注意的是,在onreadystatechange事件中应避免使用this,因为在有的浏览器中会由于作用域问题导致错误发生。

4、abort方法

abort方法用于取消请求。请求取消后,http连接会断开,响应也不会返回,xhr会停止触发事件,并且不再允许访问有关xhr对象的属性。

5、对报文头部信息的操作

5.1 setRequestHeader方法 :
该方法用于设置请求报文的头部信息,方法接受两个参数,第一个参数为字段名称,第二个参数为字段值。想要设置头部字段信息,需要在调用open方法与send方法之间进行设置才有效,如下所示:

var xhr = new XMLHttpRequest();
xhr.open('get','a.php',true);

xhr.setRequestHeader("key","value");

xhr.send();

5.2 getResponseHeader方法
该方法用于获取响应头部信息中的信息,方法接受一个头部字段名称,并返回该字段名称的字段值。当readystate状态码小于4的时候,该值为空字符串。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
	
	if (xhr.readstate === 4) {
		if (xhr.status === 200 || xhr.status === 304) {
			xhr.getResponseHeader('date'); //Fri, 11 Jan 2019 15:32:33 GMT
		}
	}
}

xhr.open('get','a.php',true);	
xhr.send();

5.3 getAllResponseHeaders方法
用于获取响应报文中的全部头部信息,当readyState值小于3时,该值返回null

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
	
	if (xhr.readstate === 4) {
		if (xhr.status === 200 || xhr.status === 304) {
			xhr.getAllResponseHeaders(); 
		}
	}
}

xhr.open('get','a.php',true);	
xhr.send();

结果:

date: Fri, 11 Jan 2019 15:32:33 GMT
server: Apache/2.4.23 (Win32) PHP/5.6.25
connection: Keep-Alive
x-powered-by: PHP/5.6.25
content-length: 2
keep-alive: timeout=5, max=98
content-type: text/html; charset=UTF-8

6、get请求

get请求通常用于向服务器获取数据,在请求地址的尾部,可以通过添加一个问号,后面跟上键值对的形式来表示参数,每个键值对之间需要使用&符号进行分割,如:
a.php?key1=value1&key2=value2
需要注意的是,查询参数中的所有key和value值都需要进行正确的编码,才能被正确处理,比如,value值中包含了问号或者&符号这些会产生歧义的字符,就会在处理的时候产生错误,因此,我们需要调用enCodeComponent方法来对每个键名与键值进行编码,来消除可能出现的带有歧义的符号。

var url = `a.php?${encodeURIComponent('key?1')}=${encodeURIComponent('value&1')}&${encodeURIComponent('key=2')}=${encodeURIComponent('value2')}`;

结果:a.php?key%3F1=value%261&key%3D2=value2

7、post请求
post请求通常用于需要向服务器发送大量数据与一些敏感的信息。因为post请求的主体可以包含很多的数据,并且这些数据并不会像get请求那样显示在url尾部。在send方法中,可以传入需要发送的数据,这些数据将会被添加到请求主体当中,发送给服务器。

xhr.open('post','a.php',true);
xhr.send('name=json&age=18');

post提交的常见数据格式

applaction/x-www-form-urlencoded
这是表单提交时使用的默认数据格式,服务端对这种格式的数据支持很好,其数据格式是类似查询参数那样的键值对形式,如:name=json&age=18

xhr.open('post','a.php',true);
xhr.send('name=json');

通过send方法,我们可以传入需要传递的数据,这些数据将被添加在请求主体当中,携带往服务器。
在此,你可能因为在php中使用超级全部变量获取name值如:$_POST[‘name’],因获取不到相应的值而感到疑惑,这其实是因为数据格式造成的,默认情况下,post请求的Content-Type字段值为text/plain,也就是纯文本格式,php在接受这类数据的时候并不会将它们添加到POST变量当中,将数据添加到POST变量当中的依据应该是格式为applaction/x-www-from-urlencoded格式的数据,表单提交时的默认格式就是这种类型,因此,我们需要修改Content-Type字段值:

xhr.open('post','a.php',true);
xhr.setRequestHeader('Content-Type','applaction/x-www-form-urlencoded');
xhr.send('name=json');

这样,在php中通过$_POST[‘name’]来获取name值就正常了。

multipart/form-data
这种数据格式不仅支持纯文本数据格式,而且还支持向服务器发送二进制数据,因此,通常我们用这种格式来完成上传文件的功能。

使用form元素进行提交文件时,需要指定enctype属性值为multipart/form-data
如果需要使用ajax进行异步文件上传的话,就需要使用FormData对象。
XMLHttpRequest2级添加了FormData对象,FormData的作用在于将数据编译成键值对,然后,我们可以通过xhr的send的方法将这个对象的实例发送出去。
FormData拥有append方法,该方法接受两个参数,第一个为键名,第二个为键值,用于往对象中添加键值对形式的数据。

 


var btn = document.getElementById('btn');

btn.addEventListener('click',() => {
	
var xhr = new XMLHttpRequest();
xhr.open('post','a.php',true);
var formData = new FormData();
formData.append('name','json'); 
formData.append('file',file.files[0]);
xhr.send(formData);
})

php后端代码:

echo $_FILES["file"]["name"].$_POST["name"];

选择文件,然后点击上传按钮,我们就可以得到文件名和name属性的值了。

可见FormData对象为开发者提供了诸多遍历,我们不再需要将数据组成以&符号分割的键值对形式,也不需要设置请求头信息,xhr能够识别FormData对象的实例,然后自动添加对应的头信息。
需要注意的是,FormData对象需要较新浏览器版本才支持,ie10及以上版本才支持,但支持还不完善。

appliaction/json

json格式是目前最为流行并且支持度也比较好的通信数据格式。

var data = {name:'json',age:18};
var jsontext = JSON.stringify(data);
var xhr = new XMLHttpRequest();
xhr.open('post','a.php',true);
xhr.setRequestHeader('Content-Type','application/json;charset=utf-8');
xhr.send(jsontext);

在前端,我们需要对js对象序列化为json字符串,然后传入send方法中发送给服务端,并且同样需要设置请求头部字段的Content-Type值为appliaction/json。
在后台的php代码中,需要使用$GLOBALS[‘HTTP_RAW_POST_DATA’]来接受json数据,在解码后即可正常使用。

$data = $GLOBALS['HTTP_RAW_POST_DATA'];
$data = json_decode($data);
echo $data->name; //'json'

超时设定

超时设定的作用是可以让请求在规定的一段时间后,如果仍然没有响应,可以中断请求。

var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
	if (xhr.readyState === 4) {
		try {
			if (xhr.status === 200 || xhr.status === 304) {
				alert(xhr.responseText);
			}
		}catch (e) {
			...
		}
	}
}

xhr.open('get','a.php',true);
xhr.timeout = 1000;
xhr.ontimeout = function () {
	alert('没有响应');
}
xhr.send(null);

上面代码中,通过xhr的timeout属性,我们可以设置超时时间,这里的时间为1000毫秒,当1000毫秒后仍然没有响应,请求就会中止,并且调用ontimeout事件。但这时候,readystate属性值可能变成4了,在请求中止后再访问有关xhr对象的属性,就会报错,因此,我们需要将这个语句放入try-catch语句中。

需要注意的是,对于超时设定,浏览器的兼容性并不好

overrideMimeType方法

该方法用于设置响应的MIME媒体类型,浏览器会根据响应的MIME类型来决定如何处理响应中的主体内容。

var xhr = new XMLHttpRequest();
xhr.open('get','a.php',true);
xhr.overrideMimeType('text/xml');
xhr.send(null);

有的时候,响应中的MIME类型与主体中的内容出现不一致,导致xhr对象不能够正确处理响应内容,通过这个方法,我们就可以修正MIME类型来确保xhr对象能够正确处理。

8、xhr对象的事件

loadstart :在接收到响应数据的第一个字节时触发
progress : 在接受到响应数据期间持续不断地触发
error : 在请求发生错误的时候触发
abort :当请求被取消时触发
load :在接收到完整的响应数据时触发
loadend : 在通信完成或者触发error,abort或load事件时触发。

8.1、onload事件

onload会在接受完响应后触发

var xhr = new XMLHttpRequest();
xhr.onload = function () {
	if (xhr.status === 200) {
		alert(xhr.responseText);
	}
}
xhr.open('post','a.php',true);
xhr.send(null);

与onreadystatechange事件不同的是,每一个阶段的变化,onreadystatechange事件都会被调用,所以需要进一步判断readyState是否等于4来确定是否接受完响应了,而onload事件不需要这么麻烦,这个事件会在接受完响应后被调用。

8.2、progress事件

这个事件会在浏览器接受数据时不停地被触发,该处理程序可以接受一个事件对象,该事件对象中包含四个属性:
target属性代表xhr对象
lengthComputeable属性的值是一个布尔值,代表进度信息是否可用。
position属性表示已经接受的字节数。
tatalSize属性表示需要传输的数据的总字节数。

但是,各浏览器的实现与标准并不一致,例如在谷歌和火狐中,tatal属性表示的是接受数据的总大小,而用loaded表示当前已经接受的字节数。
下面的例子中,将使用ajax获取一张图片,然后显示在页面中。

var xhr;
xhr = new XMLHttpRequest();
xhr.onprogress = function (e) {
     console.log(e.loaded) //11827
       console.log(e.total) //11827
       console.log((e.loaded/e.total*100)+'%') //100%
}
xhr.open("GET","1.jpg",true);
xhr.responseType = "blob";
var img = document.createElement("img");
xhr.onload = function(){
    if (xhr.status == 200) {
        var blob = xhr.response;
        img.onload = function(e) {
            window.URL.revokeObjectURL(img.src); 
        };
        img.src = window.URL.createObjectURL(blob);
        document.body.appendChild(img); 
    }
}
xhr.send();

这个例子中有许多陌生的东西,以下我们将一一解答:

1、xhr.responseType = ‘blob’;
xhr的responseType属性可以用来设置服务端返回的数据类型,这里我们需要获取的是图片,所以需要将值设置为blob,表示服务端需要返回一个包含二进制数据的blob文件对象。

2、var blob = this.response;
注意,这里不可以写成responseText,response和responsetext的区别在于,responseText返回的是文本形式的数据类型。而response则根据responseType值的不同,返回多种数据类型的其中一种,包括blob类型和文本数据类型。因此,我们需要通过response属性来获取blob文件对象。

3、img.src = window.URL.createObjectURL(blob);
createObjectURL方法可接受一个file或blob对象,返回该参数对象的资源地址。这里将这个URL赋值给了img的src,用于在页面上显示图片。
在图像加载完毕后,出于性能和内存方面的考虑,我们需要使用revokeObjectURL来释放那些不需要再使用的URL对象。

在上面是监控下载进度的示例,以下,我们将提供监控上传进度的示例
html:



js:

var file = document.getElementById('file');
var button = document.getElementById('button');

var xhr = new XMLHttpRequest();
xhr.open('post','a.php',true);

xhr.upload.onprogress = function (e) {
	console.log(e.loaded)//2761
	console.log(e.total)//2761
	console.log(((e.loaded/e.total*100)+'%') //100% 
}

button.addEventListener('click',() => {
	var data = file.files[0];
	var formData = new FormData();
	formData.append('file',data);
	xhr.send(formData);
})

监控上传进度,需要监听upload对象中的onprogress事件,在事件处理函数中可访问相关的进度信息。

9、跨域

我们都知道,通过ajax技术,我们可以访问到服务器上的资源,但这并不意味着你能够访问到任何一台服务器上的资源,事实上,在浏览器的环境中,你的访问权限很有限,只能访问与发出请求的文件所存在同一个域中的资源。当请求的地址与请求来源地址之间的协议,域名,端口号都相同的情况下,不会发生跨域,否则只要有其中一项不同,那么就会造成跨域。

如: http://www.abc.com/index.html 向 http://www.abc.com/index.php 发出请求,这就不造成跨域,否则,除了路径之外,其他部分只要有一项不同,都会造成跨域。
这是浏览器的一种安全策略。可以防止其他域对服务器可能产生的恶意行为。在web开发中,难免会遇到需要跨域的行为,下面,我们提供了几种对应的解决方案。

9.1、cors

cors称为跨域资源共享,是w3c的标准,为开发者们提供了更加便捷与稳固的跨域方案。这种方案非常简单,前端方面甚至都不需要修改代码。
当浏览器发现请求的地址,与发出请求的页面之间的域名,端口号,协议有不同之处,如:
http://www.a.com/ajax.html 向 http://www.b.com/ajax.php发出请求。
这种情况下将造成跨域,基于这种情况,浏览器会自动在请求头信息中加入origin字段,字段值为发出请求的页面的来源地址,如:
Origin:http://www.a.com
在请求报文送达到服务器上之后,服务器会根据这个来源地址信息来决定你是否有访问权限。如果服务端方面允许访问,则需要在响应信息头中添加Access-Control-Allow-Origin字段,字段值为允许访问的域名,如:
http://www.a.com
字段值也可以是一个*号,这表示允许任意的域名的访问。
如果响应头中没有包含以上两个字段中的一个,或者字段值不匹配,那么就会报错,跨域失败。

在php中设置头信息:

header("Access-Control-Allow-Origin: *"); //允许所有域名的访问 
header("Access-Control-Allow-Origin: ".$_SERVER['HTTP_REFERER']); //允许请求来源的域名的访问

需要注意的是,cors请求默认是不携带cookie的,如果需要处理cookie,首先,需要服务器方面的同意,这需要服务端在响应头中添加Access-Control-Allow-Credentials字段,当字段值为true的时候,表示服务端允许请求中携带cookie。
另外在前端方面,需要将xhr对象中的withCredentials属性设置为true,告诉浏览器允许在请求中携带cookie。
在需要对cookie进行处理的时候,这两步的设置是必须的。需要注意的是,ie10以下都不支持withCredentials属性。

需要注意的是,当Access-Control-Allow-Origin字段的值为*号的时候,是不可以携带cookie的。
在有的浏览器中,即使withCredentials属性并未设置,cookie也同样会发出,如果需要禁止携带cookie,可以将withCredentials设置为false。

cors固然方便,但兼容性却不好,ie10及以下版本都不支持。如果你需要考虑兼容低版本浏览器,请求考虑以下提供的方式:

ie8中引入了XDomainRequest对象,它部分实现了cors的规范,同样能够进行跨域通信。但是,它的功能却比较匮乏。比如:
不能够携带cookie
只能设置请求头中的Content-Type字段
不能访问响应头中的信息
相对于cors能够支持所有请求类型来说,xdr只支持get与post方式显得格外寒酸

var xdr = new XDomainRequest();
xdr.onload = function () {
	console.log(xdr.responseText);
}
xdr.open('get','http://xxx.xxx.com/index.php');
xdr.send(null);

xdr的使用方法与xhr非常相似,不同的是,xdr的open方法只支持两个参数,就是开启异步与同步的第三个布尔值参数被去掉了,因为xdr只支持异步。
xdr也不提供访问响应状态码的途径,我们只需在onload事件中获取响应主体内容即可。

9.3、cors的兼容方法

通过withCredentials属性,与XDomainRequest对象,我们可以通过能力检测来编写兼容方法:

function creataCorsRequest(method,url) {
	
	var xhr = new XMLHttpRequest();

	if (xhr.withCredentials) {
		xhr.open(method,url,true);
	}else if (typeof XDomainRequest != 'undefined') {
		xhr = new XDomainRequest();
		xhr.open(method,url);
	}

	return xhr;
}

var corsRequest = createCorsRequest('get','http://www.a.com/index.php');

corsRequest.onload = function () {
	console.log(corsRequest.responseText);
}
corsRequest.onerror = function () {
	console.log('error');
}
corsRequest.send(null);

9.4 利用img标签发起跨域请求

通过img标签,我们能从其他站点上获取到图片资源并在自己的页面上展示出来,从其他站点上获取资源,这一属于跨域的行为却没有被浏览器阻止,这正是浏览器为img标签开通的特殊权限,通过这个特点,我们就能够利用img标签发起简单的get跨域请求,要知道,src属性中并不是只能够放上一张图片的链接,完全可以是其他类型的资源文件,例如php文件,需要注意的是,img标签只支持get请求,并且,img标签发起的请求,无法获取响应的内容。


9.5、 jsonp

与img标签具有同样特殊权限的是script标签,这是为什么我们能够通过script的src属性,就能随意获取到其他域中的js脚本。虽然script标签也只支持get方式,但script标签却拥有着比img标签更强大的能力,那就是script标签能够解析执行响应中有效的js代码字符串。

	

                    
                    

你可能感兴趣的:(ajax)