Ajax原理和封装 Ajax全称为 Asynchronous JavaScript and XML (异步JavaScript和XML) ,使用Ajax可以无需刷新页面即可从服务器获取到数据,带来更好的用户体验。Ajax技术的核心是XMLHttpRequest对象,虽然名字中含有XML,但Ajax通信与数据格式无关,从服务器获取的数据可以是XML格式,也可以是JSON格式。目前来说,我们一般使用JSON格式的数据。
表单的基本知识 在介绍XMLHttpRequest对象之前,我们先来了解一下表单基本知识,在Ajax出现之前,网页通常使用表单提交数据,但是这种方式在提交数据时会刷新页面,用户体验不太好。
表单的数据提交有两种方式:get、post,这两种方式在提交数据时有一点区别
action : 数据提交的地址,默认是当前页面
method : 数据提交的方式,默认是get方式 get: 数据以查询字符串的方式传递到服务器(username=gongyz&age=a123) post:数据放在请求体中传递到服务器
enctype : 提交的数据的编码格式,默认是application/x-www-form-urlencoded
application/x-www-form-urlencoded
multipart/form-data
text/plain
get方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <!DOCTYPE HTML> <html > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" > <title > 无标题文档</title > </head > <body > <form action ="get.php" enctype ="application/x-www-form-urlencoded" > <input type ="text" name ="username" /> <input type ="text" name ="age" /> <input type ="submit" value ="提交" /> </form > </body > </html >
1 2 3 4 5 6 7 8 <?php header('content-type:text/html;charset="utf-8"' ); error_reporting(0 ); $username = $_GET['username' ]; $age = $_GET['age' ]; echo "你的名字:{$username},年龄:{$age}" ;
post方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE HTML> <html > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" > <title > 无标题文档</title > </head > <body > <form action ="post.php" method ="post" > <input type ="text" name ="username" /> <input type ="text" name ="age" /> <input type ="submit" value ="提交" /> </form > </body > </html >
1 2 3 4 5 6 7 8 <?php header('content-type:text/html;charset="utf-8"' ); error_reporting(0 ); $username = $_POST['username' ]; $age = $_POST['age' ]; echo "你的名字:{$username},年龄:{$age}" ;
XMLHttpRequest对象 下面的代码是简单的的XMLHttpRequest对象的使用方法,try/catch是用来兼容IE6和以前的版本。在XMLHttpRequest 2级中,该对象有了一些新的特性,而且现在开发人员也不会去兼容IE6和它之前的版本了,取而代之的是一些Ajax库,比较常用的像Jquery的ajax方法、axios等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var xhr = null ;try { xhr = new XMLHttpRequest(); } catch (e) { xhr = new ActiveXObject('Microsoft.XMLHTTP' ); } xhr.onreadystatechange = function ( ) { if ( xhr.readyState == 4 ) { if ( xhr.status == 200 ) { console .log(xhr.responseText) } else { console .log('Error:' , xhr.status, xhr.statusText); } } } xhr.open('get' ,'getList.php' ,true ); xhr.send(null );
1 2 3 4 5 <?php header('content-type:text/html;charset="utf-8"' ); error_reporting(0 ); $arr1 = array (1 , 2 , 3 ); echo json_encode($arr1);
下面会对XMLHttpRequest属性和一些新特性做一些说明,虽然不用原生的XMLHttpRequest进行开发,但了解这些东西对我们还是有帮助的。
XHR用法 open方法
在使用XHR对象时,要调用的第一个方法是open方法,该方法接受三个参数
请求类型: get/post
请求URL
是否异步发送请求(一般默认为true,不会去阻塞下面代码的执行)
注意:调用open方法并不会发送请求,而是启动一个请求以备发送
send方法
send方法接收一个参数,即要作为请求主体发送的数据,调用send方法后,请求就会发送到服务器,在收到响应后,相应的数据会自动填充XHR对象的属性
responseText:作为响应主体被返回的文本
responseXML:如果响应的内容是XML数据,这个属性中将会保存包含响应数据的XML DOM文档,否则为null
status:http状态码
statusText:http状态说明
readyState:
该属性标识请求/响应响应过程当前所处的状态,该属性有5个取值,该值改变时会触发onreadystatechange事件
0: 未初始化化。尚未调用open方法。
1: 启动。已经调用open方法,但尚未调用send方法
2: 发送。已经调用send方法,但尚未接收到响应
3: 接收。已经接收的部分数据
4:完成。已经接收到全部响应数据,并且可以在客户端使用
对于同步请求,可以等到请求完成在进行其他操作,但通常情况,我们使用的是异步请求,所以需要在onreadystatechange事件中监测每次状态变化后readyState值。所以我们只对readyState值为4的情况感兴趣。
注意:为了保证浏览器兼容性,应该在调用open方法之前指定onreadystatechange事件的处理函数(目前没碰上过这种兼容性问题,但是建议这样处理,可能该问题在低版本浏览器会出现)
abort方法
调用该方法后,XHR对象会停止触发事件,而且也不允许再访问任何与响应有关的对象属性。在终止操作后,还应该对XHR对象进行解引用操作。由于内存原因,不建议重用XHR对象。
get、post区别和处理 使用XHR对象发送get请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var xhr = null ;try { xhr = new XMLHttpRequest(); } catch (e) { xhr = new ActiveXObject('Microsoft.XMLHTTP' ); } xhr.onreadystatechange = function ( ) { if ( xhr.readyState == 4 ) { if ( xhr.status == 200 ) { console .log( xhr.responseText ); } else { console .log('Error:' , xhr.status, xhr.statusText); } } } xhr.open('get' ,'test.php?username=' +encodeURI ('刘伟' )+'&age=30&' + new Date ().getTime(),true ); xhr.send();
使用XHR对象发送post请求
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 var xhr = null ;try { xhr = new XMLHttpRequest(); } catch (e) { xhr = new ActiveXObject('Microsoft.XMLHTTP' ); } xhr.onreadystatechange = function ( ) { if ( xhr.readyState == 4 ) { if ( xhr.status == 200 ) { console .log( xhr.responseText ); } else { console .log('Error:' , xhr.status, xhr.statusText); } } } xhr.open('post' ,'test.php' ,true ); xhr.setRequestHeader('content-type' , 'application/x-www-form-urlencoded' ); xhr.send('username=刘伟&age=30' );
XHR新特性 FormData
FormData对象用来创建于与表单格式相同的数据(用于通过XHR传输),使用了FormData之后,不需要设置请求头的content-type,XHR能够识别传入的数据类型是FormData实例,并配置适当的头部信息。
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 var xhr = null ;try { xhr = new XMLHttpRequest(); } catch (e) { xhr = new ActiveXObject('Microsoft.XMLHTTP' ); } xhr.onreadystatechange = function ( ) { if ( xhr.readyState == 4 ) { if ( xhr.status == 200 ) { console .log( xhr.responseText ); } else { console .log('Error:' , xhr.status, xhr.statusText); } } } xhr.open('post' ,'test.php' ,true ); let data = new FormData()data.append('username' , 'gongyz' ) data.append('age' , '123' ) xhr.send(data);
load事件: 在接收到完整的响应数据时触发,可以替换上面的onreadystatechange事件
progress事件: 可以用来制作精度条
lengthComputable:表示进度信息是否可用
loaded:已经上传的字节数
total:总字节数
下面是一个Ajax上传文件的例子,我们通过这个例子来学习load和progress事件
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <!DOCTYPE HTML> <html > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" > <title > 无标题文档</title > <style > #div1 {width: 300px; height: 30px; border: 1px solid #000; position: relative;} #div2 {width: 0; height: 30px; background: #CCC;} #div3 {width: 300px; height: 30px; line-height: 30px; text-align: center; position: absolute; left: 0; top: 0;} </style > <script > window .onload = function ( ) { var oBtn = document .getElementById('btn' ); var oMyFile = document .getElementById('myFile' ); var oDiv1 = document .getElementById('div1' ); var oDiv2 = document .getElementById('div2' ); var oDiv3 = document .getElementById('div3' ); oBtn.onclick = function ( ) { var xhr = new XMLHttpRequest(); xhr.onload = function ( ) { console .log('OK,上传完成' ); } xhr.upload.onprogress = function (ev ) { if (lengthComputable) { var iScale = ev.loaded / ev.total; oDiv2.style.width = 300 * iScale + 'px' ; oDiv3.innerHTML = iScale * 100 + '%' ; } } xhr.open('post' , 'post_file.php' , true ); xhr.setRequestHeader('X-Request-With' , 'XMLHttpRequest' ); var oFormData = new FormData(); oFormData.append('file' , oMyFile.files[0 ]); xhr.send(oFormData); } } </script > </head > <body > <input type ="file" id ="myFile" /> <input type ="button" id ="btn" value ="上传" /> <div id ="div1" > <div id ="div2" > </div > <div id ="div3" > 0%</div > </div > </body > </html >
注意,progress事件不是定义在xhr,而是定义在xhr.upload,因为这里需要区分下载和上传,下载也有一个progress事件
小结 Ajax的基本知识基本上都在上面,新增的特性还有一些,感兴趣的可以去看JavaScript高级程序设计(目前是第三版)。补充一句,关于Ajax文件上传可以去看看大家可以去看看我之前的文章从 HTML5 拖放事件 探究文件上传功能 和Blob API的使用 ,另外阮一峰老师也有一篇博客文件上传的渐进式增强 ,也推荐大家看一看。
Ajax跨域解决方案 JSONP 在XMLHttpRequest 2 级之前,JSONP是最常用的跨域解决方案,JSONP由两部分组成,回调函数和数据。回调函数是当响应来到是应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的JSON数据。
下面是一个JSONP的示例:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <!DOCTYPE HTML> <html > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" > <title > 无标题文档</title > <style > #q {width: 300px; height: 30px; padding: 5px; border:1px solid #f90; font-size: 16px;} #ul1 {border:1px solid #f90; width: 310px; margin: 0;padding: 0; display: none;} li a { line-height: 30px; padding: 5px; text-decoration: none; color: black; display: block;} li a:hover{ background: #f90; color: white; } </style > <script > function handleResponse (data ) { var oUl = document .getElementById('ul1' ); var html = '' ; if (data.s.length) { oUl.style.display = 'block' ; for (var i=0 ; i<data.s.length; i++) { html += '<li > <a target ="_blank" href ="http://www.baidu.com/s?wd='+data.s[i]+'" > '+ data.s[i] +'</a > </li > '; } oUl.innerHTML = html; } else { oUl.style.display = 'none' ; } } window .onload = function ( ) { var oQ = document .getElementById('q' ); var oUl = document .getElementById('ul1' ); oQ.onkeyup = function ( ) { if ( this .value != '' ) { var oScript = document .createElement('script' ); oScript.src = 'http://suggestion.baidu.com/su?wd=' +this .value+'&cb=handleResponse' ; document .body.appendChild(oScript); } else { oUl.style.display = 'none' ; } } } </script > </head > <body > <input type ="text" id ="q" /> <ul id ="ul1" > </ul > </body > </html >
CORS 跨域资源共享是XMLHttpRequest 2级加入了W3C的规范中,目前大部分主流浏览器都支持,CORS的基本思想就是使用HTTP头部让浏览器和服务器进行沟通,从而决定跨域请求时成功还是失败。
当发送一个HTTP请求时,浏览器检测到这是一个跨域请求,会给该请求添加一个额外的Origin头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给与响应。下面是一个Origin头部的示例:
1 Origin : http://www.nczoline.net
如果服务器认为这个请求可以接受,就在Access-Control-Allow-Origin头部中回发出相同的原信息(如果是公共资源,可以回发’*’)。例如:
1 Access-Control-Allow-Origin : http://www.nczoline.net
如果没有这个头部,或者有这个头部但是源信息不匹配,浏览器就会驳回请求。正常情况下浏览器会处理请求。
注意:对于跨域请求,cookie不会再客户端和服务端之前传递(为了安全)
代码实例
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 29 30 31 32 33 34 35 36 37 38 <!DOCTYPE HTML> <html > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=utf-8" > <title > 无标题文档</title > <script > window .onload = function ( ) { /* 实现跨域请求,还需要后端的相关配合才可以 XDomainRequest: XDomainRequest是在IE8和IE9上的 CORS 的实现,在IE10中被包含CORS的 XMLHttpRequest 取代了 */ var oBtn = document .getElementById('btn' ); oBtn.onclick = function ( ) { var xhr = new XMLHttpRequest(); xhr.onload = function ( ) { console .log(xhr.responseText) } xhr.open('get' , 'http://www.b.com/ajax.php' , true ); xhr.send(); } } </script > </head > <body > <input type ="button" value ="跨域请求" id ="btn" /> </body > </html >
1 2 3 <?php header('Access-Control-Allow-Origin: http://www.a.com' ); echo 'hello' ;