AJAX
AJAX已经在Django中使用jQuery发送过了,当时原生的AJAX方法没有学。现在通过JS,多了解一下原生AJAX以及从AJAX来的跨域请求。 在2005年,Jesse James Garrett发表的一篇文章《Ajax: A New Approach to Web Applications》,里边给当时还没有名字的AJAX技术起了AJAX这个名字。虽然AJAX技术并不是他发明的,但从那之后,这个技术就得到了广泛的使用。很多人说如今的Web应用及前后端开发技术始于2005年,因为这一年Google推出了Google地球服务,第一次让“Web应用”深入人心,另外就是这篇文章带来了AJAX这样一个概念。 实际的AJAX技术要比这篇文章诞生的早的多。最早的时候JS通过Jave applet或FLash等中间层发送请求,后来把这种技术叫做远程脚本,再后来才有了AJAX这个名字。AJAX技术的核心是XMLHttpRequest
对象(简称XHR对象),首先由微软引入。在JS中使用AJAX技术,就是操作XHR对象的一系列API。
XMLHttpRequest
对象
这里有一篇阮一峰的XMLHttpRequest教程可供参考。
抛开老旧的IE6,现代浏览器都支持原生的XHR对象创建方法:
let xhr = new XMLHttpRequest()发送AJAX请求的阶段分为如下阶段:建立请求对象--->发送请求--->收到响应--->处理响应结果。
建立请求
在使用XHR对象的时候,第一个需要调用的方法是.open(request, URL, async?)
第一个参数是HTTP请求类型,是用字符串表示的"get"或者"post";第二个参数是用字符串表示的请求发送到的URL,可以使用绝对或相对路径;第三个表示是否采用异步发送,true表示使用异步发送,false表示使用同步发送。
如果要同步发送一个GET请求到"/example/",就写成:
xhr.open("get",'/exmaple/',false)关于向哪个URL发送请求,这里有一个概念,就是同源。如果URL与发送请求的AJAX代码所在的URL不属于同源,则无法发送该请求。
发送请求
发送请求需要在已经执行过.open()
方法的XHR对象上执行.send(data)
方法:
参数data是发送的数据,一般可以用序列化后的JSON字符串。如果不需要发送数据,参数可以设置为null。
let xhr = new XMLHttpRequest(); xhr.open("get",'/exmaple/',false); xhr.send("ajaxrequest");注意,这个示例代码里采用了同步发送,一般在主进程里,不要使用同步发送。如果将URL改成比如"http://www.baidu.com",就可以在浏览器控制台看到跨域请求被阻止了。
收到响应
代码设置为了同步响应,所以JS会一直阻塞到服务器响应。但实际上一般都发送异步响应,在实际开发中也应该发送异步的AJAX响应。在收到响应后,XHR的对象的几个属性会被响应的数据填充。属性名 | 说明 |
---|---|
responseText | 作为响应主体被返回的文本 |
responseXML | 如果响应的内容类型是"text/xml"或"application/xml",这个属性里是XML DOM文档 |
status | HTTP状态码 |
statusText | HTTP状态的说明 |
readyState | 异步发送时整个过程的阶段码,0表示未调用open方法,1表示已经调用open但未send,2表示以及send但未收到响应,3表示收到部分响应,4表示收到全部响应数据且就绪。 |
处理响应结果
只要readyState
属性的值有变动,都会触发一次readystatechange
事件。为了确保浏览器兼容性,一定要在调用.open()
方法之前给XHR对象绑定readystatechange
事件处理程序
从上边的属性可以看出来,发送完异步AJAX请求之后,首先是一定要监听readystatechange
事件,每次触发事件都要去检测readyState
属性的值。当值=4的时候,再检测HTTP的状态码。都通过检查之后,再去拿到响应文本进行处理。
典型的处理XHR响应的程序写法是:
xhr.onreadystatechange= function(){ if ( xhr.readyState === 4 && xhr.status === 200 ) { //响应成功的处理 alert( xhr.responseText ); } else { //响应失败的处理 alert( xhr.statusText ); } };一般给XHR对象绑定事件,就用DOM 0级的绑定时间即可,兼容性比较好。 在异步的请求还没有接收到之前,还可以调用
.abort()
方法来取消异步请求,调用之后,XHR对象的事件触发会停止,所有响应属性也没有了。
无论是正常收到了请求还是终止了请求,都要将XHR对象设置成null,释放内存,不要重用XHR对象。每次使用最好都新建XHR对象。
HTTP头部信息
之前介绍完了AJAX请求发送的全流程和事件监控。如果要用AJAX发情请求,就必须知道HTTP的GET和POST方法,现在来看一看XHR对象还能够做的事情,就是对HTTP请求头部进行操作。 默认情况下,XHR由于发送的是HTTP请求,默认会发送下列头部信息:头部字段 | 说明 |
---|---|
Accept | 浏览器能够处理的内容类型 |
Accept-Charset | 浏览器能够显示的字符集 |
Accept-Encoding | 浏览器处理的压缩编码 |
Accept-Language | 浏览器当前设置的语言 |
Connection | 浏览器与服务器之间连接的类型 |
Cookie | 当前页面设置的Cookie |
Host | 发出请求所在的域 |
Referer | 注意,这个英文正确拼法是referrer,但HTTP1.1规范里写的就是referer,所以只能将错就错。 |
User-agent | 用户浏览器信息 |
.setRequestHeader(key,value)
的方法来设置头部。实际开发中,用于向头部添加自行设置的头部字段和值,不要修改默认的HTTP请求,否则可能影响服务器的正常工作。
还有两个方法 getResponseHeader(header-key)
和 getAllResponse()
用于获得返回的响应的头部。
GET请求
通过AJAX发送GET请求需要在.open()方法中将第一个参数设置为"get"。查询字符串则需要拼接到URL内当做第二个参数内。 查询字符串的形式是?key1=value1&key2=value2
,所有的字符串都要通过 encodeURIComponent()
方法编码之后放到URL的末尾。
这里可以用一个小函数给GET请求添加键值对:
function addURLParam(url, name, value) { if (url.indexOf("?") === -1){ url += "?" } else { url+="&" } let string = url + encodeURIComponent(name) + "=" + encodeURIComponent(value); return string }上边这个函数将想要附加的查询字符串加到URL上之后,再调用
.open()
方法,这样才能将查询字符串发到后端,Django用 request.GET.get
就可以拿到数据了。在发送GET请求的时候用.sent()
方法传的数据没什么意义,后端是靠拿查询字符串获得数据的。
POST请求
POST请求与GET请求最大的区别就是,必须使用.send()
方法来发送实际数据,而不是采取URL拼接查询字符串的方式。
这里需要注意的是POST请求的实际数据格式与查询字符串相同。但是必须修改HTTP头部 Conten-type
字段的值为 application/x-www-form-urlencoded
。然后将不带?的查询字符串拼接好放到send里即可。Django里就可以用request.POST.get来取到键值对了。
XHR 2级 中的快速发送POST请求
由于通过POST请求发送表单非常常用,如果每次都去自行序列化然后修改HTTP请求头再手工发送比较麻烦,浏览器都支持使用全局对象 FormData() 生成一个表单数据对象,然后用其.append(key,value)
方法一次添加一个键值对,最后直接用.send()把这个对象发送走即可,不用再设置任何HTTP请求头。
实际发送AJAX请求
学完了AJAX的基础知识以及HTTP请求的知识,已经知道了绑定事件,现在来实践操作一下原生JS发送AJAX请求。发送请求的时候需要用到上边的给URL增加查询字符串的函数addURLParam
。
AJAX发送GET请求
建立一个按钮,给按钮绑定点击事件用来发送AJAX请求。<button type="button" class="btn btn-primary" id="ajax">原生发送AJAX</button> <script> //给按钮绑定事件 let xhrbutton = document.getElementById("ajax"); xhrbutton.onclick = sendajax; //发送AJAX请求的函数 function sendajax() { // 用let新建XHR对象,在函数执行完毕后会被销毁。不重用XHR对象。 let xhr = new XMLHttpRequest(); // onreadystatechange事件必须在open方法执行之前绑定 xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { console.log("响应阶段是:"+xhr.readyState+' 状态码是:'+xhr.status); console.log("成功收到响应的内容:" + xhr.responseText); } else { console.log("响应阶段是:"+xhr.readyState+' 状态码是:'+xhr.status) } }; //设置请求提交到的后端基础路径 let url = "/ajax_test/"; //用刚才编写的函数添加GET请求查询字符串 url = addURLParam(url,"name","jenny"); url = addURLParam(url,"name2","cony"); //准备AJAX请求 xhr.open("get", url, true); //发送AJAX请求 xhr.send(null); } </script>运行之后可以看到后端成功的通过
request.GET.get()
拿到了name 和name2的值。前端浏览器窗口内依次打印出1-4的请求状态码以及HTTP状态码,最后是得到的字符串。
AJAX发送POST请求
发送POST请求的方式与GET不同之处在于需要设置请求头部。如果直接将.open()的参数改成post,后端拿不到数据,因为.send()方法发送的是null,这里还需要注意的是send发送的字符串不能包含开头的"?"字符。发送POST的函数修改如下:<script> let xhrbutton = document.getElementById("ajax"); xhrbutton.onclick = sendajax; function sendajax() { let xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { console.log("响应阶段是:"+xhr.readyState+' 状态码是:'+xhr.status); console.log("成功收到响应的内容:" + xhr.responseText); } else { console.log("响应阶段是:"+xhr.readyState+' 状态码是:'+xhr.status) } }; //无需提交URL地址,只需要提交查询字符串,所以用空字符串开始拼接 let url = ""; //用刚才编写的函数添加GET请求查询字符串,最后去掉开始的"?"字符 url = addURLParam(url,"name","jenny"); url = addURLParam(url,"name2","cony"); url = url.slice(1); //第二个参数只是要接收POST请求的URL,无需拼接字符串 xhr.open("post", "/ajax_test/", true); //设置请求头 xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); //将数据字符串作为send方法的参数 xhr.send(url); } </script>仅对修改的部分进行了注释。由此可见GET请求和POST请求的不同之处在于三个地方:
- .open方法的第二个参数
- 请求头的设置
- .send方法参数发送的数据
用FormData()
对象简化POST请求发送
用 FormData()
就可以省略调用外部函数拼接字符串和修改HTTP请求头两步操作,简化后的代码如下:
<script> let xhrbutton = document.getElementById("ajax"); xhrbutton.onclick = sendajax; function sendajax() { let xhr = new XMLHttpRequest(); // 建立FormData对象并且添加键值对 let data = new FormData(); data.append("name","jenny"); data.append("name2","cony"); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { console.log("响应阶段是:"+xhr.readyState+' 状态码是:'+xhr.status); console.log("成功收到响应的内容:" + xhr.responseText); } else { console.log("响应阶段是:"+xhr.readyState+' 状态码是:'+xhr.status) } }; xhr.open("post", "/ajax_test/", true); //将数据字符串作为send方法的参数 xhr.send(data); } </script>
跨域资源共享
刚才介绍过了同源的概念,所谓跨域,就是用XHR朝不同源的域发请求,然后收到回复。
一开始由于浏览器的同源安全策略,为了实现跨域资源共享,开发了很多技术绕过浏览器,成为事实上的一种成熟技术,后来W3C就制订了CORS(Cross-Origin Resource Sharing)工作草案,规定了如何进行跨域资源共享。可以参考阮一峰的跨域资源共享 CORS 详解。
由于CORS需要服务器和客户端同时支持,所以目前了解即可,需要用到该技术的时候再来查阅。