之前我们使用的是jQuery的Ajax,这是一种极为便捷的Ajax操作方式,但是我们还需要对Ajax技术进行进一步的了解。
<input type="text" id="i1" /> + <input type="text" id="i2" /> = <input type="text" id="i3" /> <input type="button" id="btn1" value="jQuery Ajax" onclick="add1();" /> function add1(){ $.ajax({ url: '/add1/', type: 'POST', data: {'i1': $('#i1').val(),'i2': $('#i2').val()}, success:function(arg){ $('#i3').val(arg); } }) } #后台进行数值相加 def index(request): return render(request,'index.html') def add1(request): a1 = int(request.POST.get('i1')) a2 = int(request.POST.get('i2')) return HttpResponse(a1 + a2)
json序列化
我们使用多种语言进行web开发,并且前后端使用的语言一般也不相同,怎么进行前后端的数据交互也成了一个问题。这里我们使用json来完成数据交互。
JSON(JavaScript Object Notation,JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
一幅图对比出js,json和python的数据类型对比:
python的json模块可以进行json格式的序列化和反序列化操作。JavaScript使用parse和stringify方法进行json的数据转换:
JSON.parse(): 用于将一个 JSON 字符串转换为 JavaScript 对象 eg: console.log(JSON.parse('{"name":"Yuan"}')); console.log(JSON.parse('{name:"Yuan"}')) ; // 错误 console.log(JSON.parse('[12,undefined]')) ; // 错误 JSON.stringify(): 用于将 JavaScript 值转换为 JSON 字符串。 eg: console.log(JSON.stringify({'name':"egon"})) ;
#合格json对象 ["one", "two", "three"] { "one": 1, "two": 2, "three": 3 } {"names": ["张三", "李四"] } [ { "name": "张三"}, {"name": "李四"} ] #不合格json对象 { name: "张三", 'age': 32 } // 属性名必须使用双引号 [32, 64, 128, 0xFFF] // 不能使用十六进制值 { "name": "张三", "age": undefined } // 不能使用undefined { "name": "张三", "birthday": new Date('Fri, 26 Aug 2011 07:13:10 GMT'), "getName": function() {return this.name;} // 不能使用函数和日期对象 }
json的出现让我们的数据传输更快更节约带宽(json数据比xml数据更精简)。
<?xml version="1.0" encoding="utf-8"?> <country> <name>中国</name> <province> <name>黑龙江</name> <cities> <city>哈尔滨</city> <city>大庆</city> </cities> </province> <province> <name>广东</name> <cities> <city>广州</city> <city>深圳</city> <city>珠海</city> </cities> </province> <province> <name>*</name> <cities> <city>台北</city> <city>*</city> </cities> </province> <province> <name>*</name> <cities> <city>乌鲁木齐</city> </cities> </province> </country>
我们使用Ajax技术主要是因为:Ajax是异步交互并且浏览器页面局部刷新;(这一特点给用户的感受是在不知不觉中完成请求和响应过程),这一特性在前面帮我们解决了表单提交错误时保留上次填写信息的功能。
Ajax传递csrf问题
关于为什么要传递csrf的问题,这里就不详细说明了,前面写过关于跨站请求伪造的介绍。我们之前的csrf码都是跟随form表单一起提交到后台的。当然,我们使用Ajax也可以把csrf传递到后台。
#当js与html文件分离时,{{ csrf_token }} 无法被渲染失去作用 $.ajaxSetup({ data: {csrfmiddlewaretoken: '{{ csrf_token }}' }, }); $.ajax({
#data: {csrfmiddlewaretoken: '{{ csrf_token }}' },//写在这也能传
})
#html body标签中必须存在{% csrf_token %} {% csrf_token %} $.ajax({ url:"", type:"POST", data:{ csrfmiddlewaretoken:$("[name='csrfmiddlewaretoken']").val(), } })
#写到cookie基本通用 //<script src="{% static 'js/jquery.cookie.js' %}"></script> 需要下载对应文件 <script src="https://cdn.bootcss.com/jquery-cookie/1.4.1/jquery.cookie.js"></script> //要在前端进行cookie操作,可以使用这个插件 $.ajax({ headers:{"X-CSRFToken":$.cookie('csrftoken')},//必须加入请求头里面传递,django认证字段
//"X-CSRFToken"
})
jQuery.serialize()
serialize()
函数用于序列化一组表单元素,将表单内容编码为用于提交的字符串。serialize()
函数常用于将表单内容序列化,以便用于AJAX提交。该函数主要根据用于提交的有效表单控件的name和value,将它们拼接为一个可直接用于表单提交的文本字符串,该字符串已经过标准的URL编码处理(字符集编码为UTF-8)。该函数不会序列化不需要提交的表单控件,这和常规的表单提交行为是一致的。例如:不在<form>标签内的表单控件不会被提交、没有name属性的表单控件不会被提交、带有disabled属性的表单控件不会被提交、没有被选中的表单控件不会被提交。
简单总结:就是说我们如果有很多input标签内容需要提交总不会全部写在data中罗列出来吧,这就用到serialize()
函数了可以帮我们一次性提交数据到客户端。
<form name="myForm" action="http://www.365mini.com" method="post"> <input name="uid" type="hidden" value="1" /> <input name="username" type="text" value="张三" /> <input name="password" type="text" value="123456" /> <select name="grade" id="grade"> <option value="1">一年级</option> <option value="2">二年级</option> <option value="3" selected="selected">三年级</option> <option value="4">四年级</option> <option value="5">五年级</option> <option value="6">六年级</option> </select> <input name="sex" type="radio" checked="checked" value="1" />男 <input name="sex" type="radio" value="0" />女 <input name="hobby" type="checkbox" checked="checked" value="1" />游泳 <input name="hobby" type="checkbox" checked="checked" value="2" />跑步 <input name="hobby" type="checkbox" value="3" />羽毛球 <input name="btn" id="btn" type="button" value="点击" /> </form> //对于上面的form表单元素进行序列化可以直接序列化其内部的所有表单元素。 //序列化所有:$("form").serialize() uid=1&username=%E5%BC%A0%E4%B8%89&password=123456&grade=3&sex=1&hobby=1&hobby=2 //部分序列化:$(":text, select, :checkbox").serialize() username=%E5%BC%A0%E4%B8%89&password=123456&grade=3&hobby=1&hobby=2 在后台要取到form发回来的信息,只需要按照一般方式就可以取回来 request.POST.get("name") //input中的name属性
对于原生的Ajax来说,其实就是基于XMLHttpRequest对象进行相关的操作。
a. void open(String method,String url,Boolen async)
用于创建请求
参数:
method: 请求方式(字符串类型),如:POST、GET、DELETE...
url: 要请求的地址(字符串类型)
async: 是否异步(布尔类型)
b. void send(String body)
用于发送请求
参数:
body: 要发送的数据(字符串类型)
c. void setRequestHeader(String header,String value)
用于设置请求头
参数:
header: 请求头的key(字符串类型)
vlaue: 请求头的value(字符串类型)
d. String getAllResponseHeaders()
获取所有响应头
返回值:
响应头数据(字符串类型)
e. String getResponseHeader(String header)
获取响应头中指定header的值
参数:
header: 响应头的key(字符串类型)
返回值:
响应头中指定的header对应的值
f. void abort()
终止请求
a. Number readyState 状态值(整数) 详细: 0-未初始化,尚未调用open()方法; 1-启动,调用了open()方法,未调用send()方法; 2-发送,已经调用了send()方法,未接收到响应; 3-接收,已经接收到部分响应数据; 4-完成,已经接收到全部响应数据; b. Function onreadystatechange 当readyState的值改变时自动触发执行其对应的函数(回调函数) c. String responseText 服务器返回的数据(字符串类型) d. XmlDocument responseXML 服务器返回的数据(Xml对象) e. Number states 状态码(整数),如:200、404... f. String statesText 状态文本(字符串),如:OK、NotFound...
<input type="text" id="i1" /> + <input type="text" id="i2" /> = <input type="text" id="i3" /> <input type="button" id="btn2" value="原生Ajax" onclick="add2();" /> function add2(){ var xhr = new XMLHttpRequest();//创建新对象 xhr.onreadystatechange = function(){//回调函数,后台有响应就执行 if(xhr.readyState == 4){ alert(xhr.responseText);//接收后台发回来的数据 } }; xhr.open('GET','/add2/?i1=12&i2=19');//默认异步,用get传给后台数据在url中 xhr.send();}
post方式的Ajax相对要复杂一些,因为涉及到请求体与request.POST的相关操作。
var xhr = new XMLHttpRequest();//创建新对象 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ alert(xhr.responseText); } }; xhr.open('POST','/add2/'); xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');//请求头里必须有这个,post才能从请求体里拿到值 xhr.send("i1=12&i2=19");//在请求体里传值,只有send那么后台request.body中会传值但post中没值,post里的值也必须是这个格式 }//post实际上是从body中拿值转化成字典形式。csrf可以放到setheader里面
除了XMLHttpRequest对象的5种状态的读取服务器响应结束时,客户端才会做出改变,我们还要关心服务器响应的状态码是否为200,其服务器响应为404,或500,那么就表示请求失败了。我们可以通过XMLHttpRequest对象的status属性得到服务器的状态码。
xmlHttp.onreadystatechange = function() { if(xmlHttp.readyState == 4 && xmlHttp.status == 200) { alert(xmlHttp.responseText); } };
伪Ajax(iframe+form)
iframe标签可以不刷新发送HTTP请求(伪造局部刷新效果),同时他需要使用到form的数据打包功能,两者合一就是我们需要的Ajax默默地往后台发数据的功能。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div> <input type="text" id="txt1" /> <input type="button" value="查看" onclick="changeScr();"/> </div> <iframe id="ifr" style="width: 1000px;height: 2000px;" src="http://www.autohome.com.cn"></iframe> <script> function changeScr(){ var inp = document.getElementById('txt1').value; alert(inp); document.getElementById('ifr').src = inp; } </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <form id="f1" method="POST" action="/fake_ajax/" target="ifr">{# target指定form的提交方式 #} <iframe id="ifr" name="ifr" style="display: none"></iframe>{# 写哪都行,实际上它里面刷新了,内容就是返回值 #} <input type="text" name="user" /> <a onclick="submitForm();">提交</a> </form> <script> function submitForm(){ document.getElementById('ifr').onload = loadIframe;//加载的时候执行这个函数 document.getElementById('f1').submit();//js提交 } function loadIframe(){ var content = document.getElementById('ifr').contentWindow.document.body.innerText;//iframe标签取里面的值要使用这种方式 alert(content); } </script> </body> </html>
Ajax上传文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <h1>原生Ajax上传文件</h1> <input type="file" id="i1" /> <a onclick="upload1();">上传</a> <div id="container1"></div> <h1>jQuery Ajax上传文件</h1> <input type="file" id="i2" /> <a onclick="upload2();">上传</a> <div id="container2"></div> <h1>伪 Ajax上传文件</h1> <form id="f1" method="POST" action="/upload/" target="ifr" enctype="multipart/form-data"> <iframe id="ifr" name="ifr" style="display: none"></iframe> <input type="file" name="fafafa" /> <a onclick="upload3();">上传</a> </form> <div id="container3"></div> <script src="/static/jquery-1.12.4.js"></script> <script> function upload1(){ var formData = new FormData(); formData.append('k1','v1'); formData.append('fafafa',document.getElementById('i1').files[0]);//document.getElementById('i1').files[0]拿到文件对象 var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ var file_path = xhr.responseText;//xhr.responseText拿到后台传过来的数据 var tag = document.createElement('img'); tag.src = "/"+ file_path; document.getElementById('container1').appendChild(tag); } }; xhr.open('POST','/upload/'); xhr.send(formData); } function upload2(){ var formData = new FormData(); formData.append('k1','v1'); // formData.append('fafafa',document.getElementById('i1').files[0]); formData.append('fafafa',$('#i2')[0].files[0]); // $('#i2') -> $('#i2')[0]转化成dom对象 // document.getElementById('i1') -> $(document.getElementById('i1'))转化成jQuery $.ajax({ url: '/upload/', type: 'POST', data: formData, contentType:false, processData:false, success:function(arg){ var tag = document.createElement('img'); tag.src = "/"+ arg; $('#container2').append(tag); } }) } function upload3(){ document.getElementById('ifr').onload = loadIframe; document.getElementById('f1').submit(); } function loadIframe(){ var content = document.getElementById('ifr').contentWindow.document.body.innerText; var tag = document.createElement('img'); tag.src = "/"+ content; $('#container3').append(tag); } </script> </body> </html>
JSONP技术
我们使用的Ajax技术只能访问自己的域名,如果我们使用Ajax向其他域名发送GET请求,由于浏览器的同源策略,我们并不会得到其他域名返回的html,jsonp则可以帮助我们做到(如果没有同源策略限制Ajax可以拿到任意的数据)。
同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
当我们需要用Ajax与其他域的url进行数据交互时,Ajax跨域发送请求默认在数据回来时浏览器拒绝接受。但是浏览器的同源策略对于带有src属性的标签(script标签),并没有阻止。jsonp就是利用的这一点完成的跨域。
首先在jsonp的js中定义函数,
function list(arg){ console.log(arg); }
然后将请求的html地址放在script标签中,这样浏览器就没有办法阻止我们了。
<script src='http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403'></script>
并且这个地址传给我们的返回值应当是执行我们定义的函数list,并且给list传了值的。
我们没使用Ajax,而是jsonp,页面未刷新就获得了其他域名的值了(当然你要使用Ajax肯定是没问题的)。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <script src="/static/commons.js"></script> </head> <body> <a onclick="sendMsg();">发送</a> <script> function sendMsg(){ var tag = document.createElement('script'); tag.src = "http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403"; document.head.appendChild(tag); } </script> </body> </html>
注:JSONP一定是GET请求。