Ajax API 详解
第一步:创建 XHR 对象
Ajax 技术的核心是 XMLHttpRequest 对象(简称 XHR ),这是由微软首先引入的一个特性,其他浏览器提供商后来都提供了相同的实现。XHR 为向服务器发送请求和解析服务器响应提供了流畅的接口,能够以异步方式从服务器取得更多信息,意味着用户单击后,可以不必刷新页面也能取得新数据。
IE5 是第一款引入 XHR 对象的浏览器。在 IE5 中,XHR 对象是通过 MSXML 库中的一个 ActiveX 对象实现的,而IE7+ 及其他标准浏览器都支持原生的 XHR 对象。
创建一个 XHR 对象,也叫实例化一个 XHR 对象,因为 XMLHTTPRequest() 是一个构造函数。
// 兼容写法
var xhr = null;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest(); // 标准浏览器
}else{
xhr = new ActiveXObject('Microsoft.XMLHTTP'); // IE6兼容
}
// 如果要建立N个不同的请求,需使用N个不同的XHR对象。
// 当然可以重用已存在的XHR对象,但这会终止之前通过该对象挂起的任何请求。
第二步:发送 HTTP 请求
[ 请求参数分析 ] 在使用 XHR 对象时,要调用的第一个方法是 open(),该方法接受 3 个参数;然后是 send() 方法,该方法接收一个参数,即,要作为请求主体发送的数据,调用 send() 方法后,请求被分派到服务器。
xhr.open("get","example.php", true);
// 如果是GET方法,send()方法无参数,或参数为null;如果是POST方法,send()方法的参数为要发送的数据
xhr.send(null); // 具体写法,参考下方不同请求方式的代码示例
- open() 方法的第一个参数:用于指定发送请求的方式,这个字符串,不区分大小写,但通常使用大写字母。
// 请求方式( & Ajax 函数封装)?! — “GET” 和 “POST” 是得到广泛支持的:
(1)"GET"用于常规请求,它适用于当URL完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存的情况下。 (2)"POST"方法常用于HTML表单。它在请求主体中包含额外数据且这些数据常存储到服务器上的数据库中。相同URL的重复POST请求从服务器得到的响应可能不同,同时不应该缓存使用这个方法的请求。 // 除了"GET"和"POST"之外,参数还可以是"HEAD"、"OPTIONS"、"PUT"。而由于安全风险的原因,"CONNECT"、"TRACE"、"TRACK"被禁止使用
- open() 方法的第二个参数:是 URL,该 URL 相对于执行代码的当前页面,且只能向同一个域中使用相同端口和协议的 URL 发送请求。如果 URL 与启动请求的页面有任何差别,都会引发安全错误。
- open() 方法的第三个参数:是表示是否异步发送请求的布尔值,如果不填写,默认为 true,表示异步发送。
如果请求一个受密码保护的 URL,把用于认证的用户名和密码作为第 4 和第 5 个参数传递给 open() 方法。
[1] 对于 get 请求
1. 准备发送
// 如果是get请求,那么请求参数必须在url中传递
// xhr.open('get','example.php?username='+uname+'&password='+pw,true);
// get请求在IE中的兼容(encodeURI()用来对中文参数进行编码,防止乱码)
var param = 'username='+uname+'&password='+pw;
xhr.open('get','example.php?'+encodeURI(param),true);
2. 执行发送动作
xhr.send(null); // 如果是get请求,这里需要添加一个null参数
[2] 对于 post 请求 // 头部信息?!
1. 准备发送
var param = 'username='+uname+'&password='+pw;
xhr.open('post','example.php?',true);
2. 执行发送动作 // 区别:post请求参数通过send传递,不需要通过encodeURI()转码;必须设置请求头信息
xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded'); // post请求需设置头部信息
xhr.send(param); // post请求参数在这里传递,且不需要进行转码
第三步:接收响应
一个完整的 HTTP 响应由状态码、响应头集合和响应主体组成。
在收到响应后,这些都可以通过 XHR 对象的属性和方法使用,主要有以下 4 个属性:
responseText: 作为响应主体被返回的文本(文本形式) responseXML: 如果响应的内容类型是'text/xml'或'application/xml',这个属性中将保存着响应数据的XML DOM文档(document形式) status: HTTP状态码(数字形式) statusText: HTTP状态说明(文本形式)在接收到响应后,第一步是检查status属性,以确定响应已经成功返回。一般来说,可以将HTTP状态码为200作为成功的标志。此时,responseText属性的内容已经就绪,而且在内容类型正确的情况下,responseXML也可以访问了。此外,状态码为304表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本;当然,也意味着响应有效。
- 无论内容类型是什么,响应主体的内容都会保存到responseText属性中;
- 而对于非XML数据而言,responseXML属性的值将为nul。
[1] 响应状态分析
[
同步] 如果接受的是同步响应,则需要将open()方法的第三个参数设置为false,那么send()方法将阻塞直到请求完成。一旦send()返回,仅需要检查XHR对象的status和responseText属性即可。同步请求是吸引人的,但应该避免使用它们。客户端javascript是单线程的,当send()方法阻塞时,它通常会导致整个浏览器UI冻结。如果连接的服务器响应慢,那么用户的浏览器将冻结。
[ 异步 ] 若需要接收的是异步响应,就需要检测 xhr.readyState 属性,该属性表示请求/响应过程的当前活动阶段。
// 响应状态分析(xhr.readyState可取的值有:) xhr.readyState = 0 (UNSENT):xhr对象创建完成,未初始化。即,尚未调用open()方法 xhr.readyState = 1 (OPENED):准备发送。已经调用open()方法,但尚未调用send()方法 xhr.onreadystatechange() 函数调用的条件就是readyState状态值发生变化(不包括从0变成1),由浏览器自动调用。 2 (HEADERS_RECEIVED) - 执行发送动作。己经调用send()方法,且接收到头信息 3 (LOADING) - 已经接收到部分响应主体信息,正在解析数据。 4 (DONE) - 已经接收到全部响应数据,可以使用了通常,我们对readyState值为4的阶段感兴趣,因为这时所有数据都已就绪。
xhr.onreadystatechange = function(callback){ // 4 表示服务器返回的数据已经可以使用了,但是这个数据不一定是正常的 if(xhr.readyState == 4){ // http 常见状态码:200表示响应成功、404表示没有找到请求的资源、500表示服务器端错误 // if(xhr.status >=200 && xhr.status<300 || xhr.status == 304){ // 200表示数据是正常的 if(xhr.status == 200){ callback(xhr.responseText); } }else{ alert('Request was unsuccessful' + xhr.status); } }
[2] 响应主体解码:接收到的响应主体类型可以是多种形式的,包括字符串 String、ArrayBuffer 对象、二进制 Blob 对象、JSON 对象、JS 文件及表示 XML 文档的 Document 对象等。
[ 属性 ] 在学习响应解码之前,要先了解 XHR 对象的属性。一般地,如果接受的数据是字符串,使用responseText即可,这也是最常用的用于接收数据的属性。但如果获取了其他类型的数据,使用responseText就不太合适了。
- responseText 属性:返回从服务器接收到的字符串,该属性为只读。如果本次请求没有成功或数据不完整,该属性等于 null。如果服务器返回的数据格式是 JSON、字符串、JS 或 XML,都可以使用 responseText 属性。
- response 属性:只读,返回接收到的数据体。它的类型可以是ArrayBuffer、Blob、Document、JSON对象、或者一个字符串,这由XMLHttpRequest.responseType属性的值决定;如果本次请求没有成功或者数据不完整,该属性就会等于null;IE9- 浏览器不支持。
- responseXML属性:返回从服务器接收到的Document对象,该属性为只读。如果本次请求没有成功,或者数据不完整,或者不能被解析为XML或HTML,该属性等于null。
- responseType 属性:用来指定服务器返回数据(xhr.response)的类型
“”:字符串(默认值) “arraybuffer”:ArrayBuffer对象 “blob”:Blob对象 “document”:Document对象 “json”:JSON对象 “text”:字符串
以下是针对不同的主体类型,进行相应的响应解码:
[ 字符串 ] 如果服务器返回的结果是一个字符串,则直接使用 responseText 属性解析即可。
// 使用 ajax() 函数的封装,直接调用 ajax.js 即可 <button id="btn">取得响应</button> <div id="result"></div> <script> btn.onclick = function(){ ajax({ url:'p1.php', callback:function(data){ result.innerHTML = data; } }) } </script><?php //设置页面内容的html编码格式是utf-8,内容是纯文本 header("Content-Type:text/plain;charset=utf-8"); echo '你好,世界'; ?>
[ JSON ] 使用 ajax 最常用的传输方式就是使用 JSON 字符串,直接使用 responseText 属性解析即可
<button id="btn">取得响应</button> <div id="result"></div> <script> btn.onclick = function(){ ajax({ url:'p2.php', callback:function(data){ var obj = JSON.parse(data); var html = ''; for(var i = 0; i < obj.length; i++){ html+= '<div>' + obj[i].title + ':' + obj[i].data + '</div>'; } result.innerHTML = html; html = null; } }) } </script><?php header("Content-Type:application/json;charset=utf-8"); $arr = [['title'=>'颜色','data'=>'红色'],['title'=>'尺寸','data'=>'英寸'],['title'=>'重量','data'=>'公斤']]; echo json_encode($arr); ?>
[ XML ] XML 在 JSON 出现之前,是网络上常用的数据传输格式,但由于其格式较笨重,所以用的较少;接收 XML 文档时,使用 responseXML 来对数据进行解析。
<button id="btn">取得响应</button> <div id="result"></div> <script> btn.onclick = function(){ ajax({ url:'p3.xml', callback:function(data){ var obj = data.getElementsByTagName('CD'); var html = ''; for(var i = 0; i < obj.length; i++){ html += '<div>唱片:' + obj[i].getElementsByTagName('TITLE')[0].innerHTML + ';歌手:' + obj[i].getElementsByTagName('ARTIST')[0].innerHTML + '</div>'; } result.innerHTML = html; html = null; } }) } function ajax(obj){ //method为ajax提交的方式,默认为'get'方法 obj.method = obj.method || 'get'; //创建xhr对象 var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } //异步接受响应 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ //callback为回调函数,如果不设置则无回调 obj.callback && obj.callback(xhr.responseXML); } } } //创建数据字符串,用来保存要提交的数据 var strData = ''; obj.data = true; if(obj.method == 'post'){ for(var key in obj.data){ strData += '&' + key + "=" + obj.data[key]; } //去掉多余的'&' strData = strData.substring(1); xhr.open('post',obj.url,true); //设置请求头 xhr.setRequestHeader("content-type","application/x-www-form-urlencoded"); //发送请求 xhr.send(strData); }else{ //如果是get方式,则对字符进行编成 for(var key in obj.data){ strData += '&' + encodeURIComponent(key) + "=" + encodeURIComponent(obj.data[key]); } //去掉多余的'&',并增加随机数,防止缓存 strData = strData.substring(1) + '&'+Number(new Date()); xhr.open('get',obj.url+'?'+strData,true); //发送请求 xhr.send(); } } </script><CATALOG data-livestyle-extension="available"> <CD> <TITLE>迷迭香</TITLE> <ARTIST>周杰伦</ARTIST> </CD> <CD> <TITLE>成都</TITLE> <ARTIST>赵雷</ARTIST> </CD> <CD> <TITLE>是时候</TITLE> <ARTIST>孙燕姿</ARTIST> </CD> </CATALOG>
[ JS ] 使用 ajax 也可以接收 js 文件,仍然使用 responseText 来接收数据,但要使用 eval() 来执行代码。
<button id="btn">取得响应</button> <div id="result"></div> <script> btn.onclick = function(){ ajax({ url:'p4.js', callback:function(data){ eval(data); var html = ''; for(var key in obj){ html += '<div>' + key + ':' + obj[key] + '</div>'; } result.innerHTML = html; html = null; } }) } </script>var obj = { '姓名':'小明', '年龄':18, '性别':'女' }
[ blob ] 在 JS 中,Blob通常表示二进制数据。但在实际Web应用中,Blob更多是图片二进制形式的上传与下载,虽然其可以实现几乎任意文件的二进制传输。使用ajax接收blob数据,需要使用response来接收,并且将responseType设置为'blob'。另,要完全兼容IE10+浏览器,需要将xhr.responseType设置在xhr.open()和xhr.send()方法之间。
<button id="btn">取得响应</button> <div id="result"></div> <script> btn.onclick = function(){ ajax({ url:'p5.gif', callback:function(data){ var img = document.createElement('img'); img.onload = function(){ URL.revokeObjectURL(img.src); } img.src = URL.createObjectURL(data); if(!result.innerHTML){ result.appendChild(img); } }, method:'post' }) } function ajax(obj){ //method为ajax提交的方式,默认为'get'方法 obj.method = obj.method || 'get'; //创建xhr对象 var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } //异步接受响应 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ //callback为回调函数,如果不设置则无回调 obj.callback && obj.callback(xhr.response); } } } //创建数据字符串,用来保存要提交的数据 var strData = ''; obj.data = true; if(obj.method == 'post'){ for(var key in obj.data){ strData += '&' + key + "=" + obj.data[key]; } //去掉多余的'&' strData = strData.substring(1); xhr.open('post',obj.url,true); xhr.responseType = 'blob'; //设置请求头 xhr.setRequestHeader("content-type","application/x-www-form-urlencoded"); //发送请求 xhr.send(strData); }else{ //如果是get方式,则对字符进行编成 for(var key in obj.data){ strData += '&' + encodeURIComponent(key) + "=" + encodeURIComponent(obj.data[key]); } //去掉多余的'&',并增加随机数,防止缓存 strData = strData.substring(1) + '&'+Number(new Date()); xhr.open('get',obj.url+'?'+strData,true); xhr.responseType = 'blob'; //发送请求 xhr.send(); } } </script>
[ arraybuffer ] 代表储存二进制数据的一段内存,而 blob 则用于表示二进制数据。通过 ajax 接收 arraybuffer,然后将其转换为 blob 数据,从而进行进一步的操作。responseType 设置为 arraybuffer,然后将 response 作为 new Blob() 构造函数的参数传递,生成 blob 对象。
<button id="btn">取得响应</button> <div id="result"></div> <script> btn.onclick = function(){ ajax({ url:'p5.gif', callback:function(data){ var img = document.createElement('img'); img.onload = function(){ URL.revokeObjectURL(img.src); } img.src = URL.createObjectURL(new Blob([data])); if(!result.innerHTML){ result.appendChild(img); } } }) } function ajax(obj){ //method为ajax提交的方式,默认为'get'方法 obj.method = obj.method || 'get'; //创建xhr对象 var xhr; if(window.XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } //异步接受响应 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if(xhr.status == 200){ //callback为回调函数,如果不设置则无回调 obj.callback && obj.callback(xhr.response); } } } //创建数据字符串,用来保存要提交的数据 var strData = ''; obj.data = true; if(obj.method == 'post'){ for(var key in obj.data){ strData += '&' + key + "=" + obj.data[key]; } //去掉多余的'&' strData = strData.substring(1); xhr.open('post',obj.url,true); //设置请求头 xhr.setRequestHeader("content-type","application/x-www-form-urlencoded"); xhr.responseType = 'arraybuffer'; //发送请求 xhr.send(strData); }else{ //如果是get方式,则对字符进行编成 for(var key in obj.data){ strData += '&' + encodeURIComponent(key) + "=" + encodeURIComponent(obj.data[key]); } //去掉多余的'&',并增加随机数,防止缓存 strData = strData.substring(1) + '&'+Number(new Date()); xhr.open('get',obj.url+'?'+strData,true); xhr.responseType = 'arraybuffer'; //发送请求 xhr.send(); } } </script>
超时(可选)
[ 进度事件?! ] XHR 对象的 timeout 属性等于一个整数,表示多少毫秒后,如果请求仍然没有得到结果,就会自动终止。该属性默认等于 0,表示没有时间限制。如果请求超时,将触发 ontimeout 事件。 // IE8-不支持
xhr.open('post','test.php',true); xhr.ontimeout = function(){ console.log('The request timed out.'); } xhr.timeout = 1000; xhr.send();
优化(推荐)
使用AJAX接收数据时,由于网络和数据大小的原因,并不是立刻就可以在页面中显示出来。所以,更好的做法是,在接受数据的过程中,显示一个类似loading的小图片,并且禁用按钮;当数据完全接收后,再隐藏该图片,并启用按钮。
<button id="btn">获取信息</button>
<img id="img" height="16" style="display:none" src="data:image/gif;base64,R0lGODlhIAAgALMAAP///7Ozs/v7+9bW1uHh4fLy8rq6uoGBgTQ0NAEBARsbG8TExJeXl/39/VRUVAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFBQAAACwAAAAAIAAgAAAE5xDISSlLrOrNp0pKNRCdFhxVolJLEJQUoSgOpSYT4RowNSsvyW1icA16k8MMMRkCBjskBTFDAZyuAEkqCfxIQ2hgQRFvAQEEIjNxVDW6XNE4YagRjuBCwe60smQUDnd4Rz1ZAQZnFAGDd0hihh12CEE9kjAEVlycXIg7BAsMB6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YEvpJivxNaGmLHT0VnOgGYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHQjYKhKP1oZmADdEAAAh+QQFBQAAACwAAAAAGAAXAAAEchDISasKNeuJFKoHs4mUYlJIkmjIV54Soypsa0wmLSnqoTEtBw52mG0AjhYpBxioEqRNy8V0qFzNw+GGwlJki4lBqx1IBgjMkRIghwjrzcDti2/Gh7D9qN774wQGAYOEfwCChIV/gYmDho+QkZKTR3p7EQAh+QQFBQAAACwBAAAAHQAOAAAEchDISWdANesNHHJZwE2DUSEo5SjKKB2HOKGYFLD1CB/DnEoIlkti2PlyuKGEATMBaAACSyGbEDYD4zN1YIEmh0SCQQgYehNmTNNaKsQJXmBuuEYPi9ECAU/UFnNzeUp9VBQEBoFOLmFxWHNoQw6RWEocEQAh+QQFBQAAACwHAAAAGQARAAAEaRDICdZZNOvNDsvfBhBDdpwZgohBgE3nQaki0AYEjEqOGmqDlkEnAzBUjhrA0CoBYhLVSkm4SaAAWkahCFAWTU0A4RxzFWJnzXFWJJWb9pTihRu5dvghl+/7NQmBggo/fYKHCX8AiAmEEQAh+QQFBQAAACwOAAAAEgAYAAAEZXCwAaq9ODAMDOUAI17McYDhWA3mCYpb1RooXBktmsbt944BU6zCQCBQiwPB4jAihiCK86irTB20qvWp7Xq/FYV4TNWNz4oqWoEIgL0HX/eQSLi69boCikTkE2VVDAp5d1p0CW4RACH5BAUFAAAALA4AAAASAB4AAASAkBgCqr3YBIMXvkEIMsxXhcFFpiZqBaTXisBClibgAnd+ijYGq2I4HAamwXBgNHJ8BEbzgPNNjz7LwpnFDLvgLGJMdnw/5DRCrHaE3xbKm6FQwOt1xDnpwCvcJgcJMgEIeCYOCQlrF4YmBIoJVV2CCXZvCooHbwGRcAiKcmFUJhEAIfkEBQUAAAAsDwABABEAHwAABHsQyAkGoRivELInnOFlBjeM1BCiFBdcbMUtKQdTN0CUJru5NJQrYMh5VIFTTKJcOj2HqJQRhEqvqGuU+uw6AwgEwxkOO55lxIihoDjKY8pBoThPxmpAYi+hKzoeewkTdHkZghMIdCOIhIuHfBMOjxiNLR4KCW1ODAlxSxEAIfkEBQUAAAAsCAAOABgAEgAABGwQyEkrCDgbYvvMoOF5ILaNaIoGKroch9hacD3MFMHUBzMHiBtgwJMBFolDB4GoGGBCACKRcAAUWAmzOWJQExysQsJgWj0KqvKalTiYPhp1LBFTtp10Is6mT5gdVFx1bRN8FTsVCAqDOB9+KhEAIfkEBQUAAAAsAgASAB0ADgAABHgQyEmrBePS4bQdQZBdR5IcHmWEgUFQgWKaKbWwwSIhc4LonsXhBSCsQoOSScGQDJiWwOHQnAxWBIYJNXEoFCiEWDI9jCzESey7GwMM5doEwW4jJoypQQ743u1WcTV0CgFzbhJ5XClfHYd/EwZnHoYVDgiOfHKQNREAIfkEBQUAAAAsAAAPABkAEQAABGeQqUQruDjrW3vaYCZ5X2ie6EkcKaooTAsi7ytnTq046BBsNcTvItz4AotMwKZBIC6H6CVAJaCcT0CUBTgaTg5nTCu9GKiDEMPJg5YBBOpwlnVzLwtqyKnZagZWahoMB2M3GgsHSRsRACH5BAUFAAAALAEACAARABgAAARcMKR0gL34npkUyyCAcAmyhBijkGi2UW02VHFt33iu7yiDIDaD4/erEYGDlu/nuBAOJ9Dvc2EcDgFAYIuaXS3bbOh6MIC5IAP5Eh5fk2exC4tpgwZyiyFgvhEMBBEAIfkEBQUAAAAsAAACAA4AHQAABHMQyAnYoViSlFDGXBJ808Ep5KRwV8qEg+pRCOeoioKMwJK0Ekcu54h9AoghKgXIMZgAApQZcCCu2Ax2O6NUud2pmJcyHA4L0uDM/ljYDCnGfGakJQE5YH0wUBYBAUYfBIFkHwaBgxkDgX5lgXpHAXcpBIsRADs=" alt="loading">
<div id="result"></div>
<script>
var add = (function(){
var counter = 0;
return function(){
return ++counter;
}
})();
btn.onclick = function(){
img.style.display = 'inline-block';
btn.setAttribute('disabled','');
//创建xhr对象
var xhr;
if(window.XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}
//异步接受响应
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status == 200){
img.style.display = 'none';
btn.removeAttribute('disabled');
var data = JSON.parse(xhr.responseText);
var sum = add() - 1;
if(sum < data.length){
result.innerHTML += data[sum];
}
}
}
}
//发送请求
xhr.open('get','data.php',true);
xhr.send();
}
</script>
<?php echo json_encode([1,2,3,4,5]); ?>