本章是第六章Ajax相关的内容。
Ajax是一种流行的前后端数据交互的方式,通过异步请求就可以在不需要刷新页面的情况下,达到局部刷新的效果。
Ajax并非是一种全新的技术,而是由以下技术组合而成:
在学完后,希望掌握下面知识点:
Ajax的基本原理是通过XMLHttpRequest对象向服务器发送异步请求,获取服务器返回的数据后,利用DOM的操作来更新页面。
下面是Ajax的执行流程:
其中最核心的部分就是XMLHttpRequest对象。它是一个JavaScript对象,支持异步请求,可以及时向服务器发送请求和处理响应,并且不阻塞用户,达到不刷新页面的效果。
下面重点讲解XMLHttpRequest对象相关知识点。
XMLHttpRequest对象从创建到销毁存在一个完整的生命周期,在生命周期的每个阶段会调用XMLHttpRequest对象的不同函数,在函数中需要通过XMLHttpRequest对象的特定属性来判断函数执行情况。
下面就会记录XMLHttpRequest对象的函数和属性
abort()
:如果请求已经发送,则停止当前请求getAllResponseHeaders()
:获取所有HTTP请求的响应头部,作为键值对返回;如果没有收到回应,则返回nullgetResponseHeader("key")
:获取特定key的HTTP响应头,没有收到响应或不存在则返回nullopen("method","URL","[asyncFlag]","[userName]","[password]")
:建立对服务器的调用。method表示请求方式,可以为GET
、POST
或PUT
;URL表示请求路径,绝对路径或相对路径均可;后面三个参数可选,表示是否异步、用户名、密码。asyncFlag=true表示异步,false表示同步,默认为true异步send(content)
:向服务器发送请求setRequestHeader("key","value")
:设置请求头属性为key的值为value。在设置请求头之前需要先调用open()
函数,设置的header将随着send()
函数一起发送onreadystatechange
:状态改变的事件触发器。每个状态改变时都会触发这个事件处理器,通常会调用一个JavaScript函数readyState
:请求的状态。有 5 个可取的值:
0
:未初始化,XMLHttpRequest对象已创建1
:open()函数已调用,send()函数未调用,请求还未发送2
:send()函数已调用,HTTP请求已发送到服务器,未收到响应3
:所有响应头接收完成,响应体开始接收但未完成4
:HTTP响应接收完成responseText
:接受的数据文本格式的服务器响应体(不包括响应头)responseXML
:服务器的响应,兼容DOM的XML对象,解析后可得到DOM对象status
:服务器返回的HTTP状态码,用数字表示,如200表示成功,404表示资源未找到statusText
:HttP状态码的文本表示,如200时对应"OK",404时对应"Not Found"由于浏览器的差异性,创建XMLHttpRequest对象时需要使用不同的方法,主要体现在IE浏览器与其他浏览器之间。
由于现在IE已经基本弃用,因此就不会再记录相关知识。
下面是一个标准的XMLHttpRequest创建方法。
function createXMLHttp() {
var xmlhttp;
// code for IE7+, firefox, chrome, opera, safari
if (window.XMLHttpRequest) {
xmlhttp = new XMLHttpRequest();
}
// code for IE5, IE6 省略
return xmlhttp;
}
当XMLHttPRequest对象创建完毕后,便可以通过open()
函数建立连接,它指定了请求的url地址以及通过url传递的参数;数据传输方式,默认值为true,表示采用异步传输方式
var xhr = createXMLHttp();
xhr.open('post', '/admin/w/saceUser', true);
在使用open()
函数建立连接后,便可以使用send()
函数发送请求,并传递数据content。由于传递的数据并不是必须的,所以content值可以为空
var content = {userName:'kingx', password:'123456'};
xhr.send(content);
在XMLHttpRequest对象中有一个很重要的onreadystatechange属性,它表示XMLHttpRequest对象状态改变的事件触发器,每次readyState的取值变化时,属性onreadystatechange对应的函数都会被执行一次。
当readyState的值为4时代表响应接收完成,需要注意的是响应接收完成并不代表请求是成功的,我们需要通过HTTP请求status状态码来判断,当status值为200时代表请求成功。
因此在onreadystatechange()
回调函数中,我们需要同时判断readyState和status两个值才能对响应值做正确的处理
xhr.onreadystatechange = function(){
// 当readyState为4,且状态码为200时代表请求成功
if (xhr.readyState === 4 && xhr.status === 200){
// 处理响应值
document.write(xhr.responseText);
}
}
这块不过多赘述
form表单默认提交方式会刷新页面,而且会在页面之间进行跳转。
使用Ajax提交form表单就可以在不刷新页面的情况下提交请求,然后在处理响应时通过JavaScript操作DOM,并展示后台处理的信息。
使用Ajax提交form表单时,需要对form表单进行特殊处理,包括:
下面是一个例子:
<form name="userForm" id="userForm">
<div class="form-group">
<label for="username">用户名label>
<input type="text" class="form-control" name="username" id="username">
div>
<div class="form-group">
<label for="password">密码label>
<input type="password" class="form-control" name="password" id="password">
div>
<div class="form-group">
<label for="telphone">电话label>
<input type="text" class="form-control" name="telphone" id="telphone">
div>
<div class="form-group">
<label for="email">邮箱label>
<input type="text" class="form-control" name="email" id="email">
div>
<div class="text-center">
<input type="button" class="btn btn-default btn-primary" value="提交" id="submit">
<input type="button" class="btn btn-default" value="取消" id="cancel">
div>
form>
使用原生Ajax提交form表单包含以下过程:
在单击提交按钮时触发Ajax请求的操作,将整个Ajax操作封装在ajaxSubmitForm()函数里。按钮获取与事件绑定使用原生的JS语法。
var submitBtn = document.getElementById('submit');
submitBtn.addEventListener('click',function(){
ajaxSubmitForm();
})
同样是使用6.1.2小节中封装的函数即可
function createXMLHttp() {
var xmlhttp;
if (window.XMLHttpRequest){
xmlhttp = new XMLHttpRequest();
}
return xmlhttp;
}
var xhr = createXMLHttp();
本实例可以理解为一个用户的注册操作,发送的请求为POST秦秋,使用异步处理方式
xhr.open('post', '/saveUser', true);
由于本实例中发送的是POST请求,需要设置数据传输格式,即设置Content-type属性值。可以通过setRequestHeader()
函数对其进行设置,将其值设置为比较普遍的 JSON数据格式
xhr.setRequestHeader('Content-type', 'application/json; charset=UTF-8');
通过原生的DOM操作方式获取页面填写的数据
var username = document.getElementById('username').value;
var password = document.getElementById('password').value;
var telphone = document.getElementById('telphone').value;
var email = document.getElementById('email').value;
var content = {
username: username, password: password, telphone: telphone, email: email
};
因为在请求头中设置了数据传输格式为json,所以需要将content对象处理为json字符串
content = JSON.stringify(content);
只需要调用send()
函数就可以发送请求
xhr.send(content);
设置onreadystatechange属性对应的回调函数,在回调函数中进行判断。当响应接收完毕,readyState为4,同时请求状态码status为200时,即表示请求成功,然后就可以编写对应的处理逻辑
xhr.onreadystatechange = function () {
// 当readyStatew为4,且状态码为200时代表请求成功
if (xhr.readyState === 4 && xhr.status === 200) {
// 处理响应值
document.write(xhr.responseText);
}
}
在使用原生Ajax提交form表单内容时,需要考虑浏览器兼容性问题,并且该方式的代码冗余度高,需要经常进行状态的判断,因此这并不是一种很好的处理form表单的方式。
使用jQuery处理Ajax请求,解决了浏览器兼容性的问题,对原生Ajax请求的高度封装也使得代码变得精简。我们只需要关注在使用Ajax时需要什么,然后传递对应的参数,处理不同的回调即可。
但因为也不用,所以不记录了。
表单的序列化,表示的是可以自动将表单内填写的内容自动处理为字符串或者对象格式,便于与服务端进行传递,从而避免重复性地通过代码获取单个表单元素输入值。
同样因为不用,不记录了。
FormData对象是HTML5中新增的对象,服务于Ajax请求,用于发送数据。
FormData对象将数据编译成key-value类型的键值对,以便于XMLHttpRequest对象发送数据。其主要用于发送form表单数据,但也可以独立于form表单,发送带有键的数据。
FormData对象提交的最大的优势是可以异步上传文件。
FormData对象的提交既可以支持原生Ajax请求,也可以支持jQuery请求。jQuery就不记录了。
下面记录一下原生Ajax请求使用FormData对象发送form表单数据:
其他部分,例如XMLHttpRequest对象的创建和请求发送,以及请求成功的回调,这里就不做详细描述,我们重点来看FormData对象的使用
(1)请求头设置
使用原生Ajax请求发送带有文件流的FormData数据时,需要对请求头进行对应的设置,即将Content-type属性设置为application/x-www-form-urlencoded
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
(2)生成FormData实例
var formData = new FormData();
(3)添加数据
通过append()函数向formData对象中添加需要发送的数据,不管是简单的文本类型数据还是文件类型数据,都可以添加到formData对象中
(4)发送数据
调用send()函数传递FormData对象
xhr.send(formData);
Ajax请求通常会有get和post两种方式
使用get和post都可以向服务器发送请求,但是发送机制不同:
send()
函数时传递的参数为null,即xhr.send()
send()
函数时传递的参数为data,即xhr.send(data)
Request.query
来获取参数;而使用 post请求时需要添加中间件,同时通过Request.body
来获取参数encodeURIComponent()
函数处理xhr.open('get', '/getUser?username='+encodeURIComponent(username), true)
xhr.setRequestHeader('content-type','application/x-www-form-urlencoded')
在之前的内容里,我们有讲到通过监听readystatechange事件,在回调函数中获取readyState和status的值并判断请求是否成功。在XHR2草案中,增加了Ajax请求进度事件Progress Events规范,使得XMLHttpRequest对象能在请求的不同阶段触发不同类型的事件,所以我们可以不再需要判断readyState的属性,也可以处理请求成功和失败的操作。
在Progress Events规范中增加了 7 个进度事件:
loadstart
:在开始接收响应时触发progress
:在接收响应期间不断触发,直至请求完成error
:在请求失败时触发abort
:在主动调用abort()
函数时触发,表示请求终止load
:在数据接收完成时触发loadend
:在通信完成或者error、abort、load事件后触发timeout
:在请求超时时触发一个完整的ajax请求都会从loadstart事件开始,然后不间断地触发progress事件,然后触发load、abort、timeout或者error事件的其中一个,最后触发loadend事件。
只要浏览器接收到了服务器的响应,不管其状态如何都会触发load事件。例如,对于状态码为404的请求,仍然会触发load事件,所以在进行请求成功的处理时,需要判断status的值。一般我们判断status值大于等于200且小于300,或者status值等于304时,都是当作请求成功进行处理。
在loadstart、load等事件的回调函数中,都会接收一个event对象,通过event对象的target属性可以获取到XMLHttpRequest对象的实例,因此可以访问到XMLHttpRequest对象的所有属性和函数
progress事件会在浏览器接收数据的过程中周期性调用。progress事件处理程序会接收一个event对象,通过它的target属性同样可以获取到XMLHttpRequest对象的实例,而且在event对象中增加了3个有用的属性,分别是lengthComputable、loaded和total
lengthComputable
:表示进度信息是否可用。是一个布尔值loaded
:表示已经接收到的字节数,它的值是根据Content-Length响应头部确定的预期字节数total
:表示响应的实际字节数通过loaded和total属性值可以计算出接收响应的数据百分比,从而实现进度条
JSON数据在网络传输时存在两种类型,一种是JSON对象类型,一种是JSON字符串类型。两种类型的转换涉及JSON序列化和反序列化的知识。
JSON序列化即将JSON对象处理为JSON字符串的过程,以方便数据的传输。
有两种方式实现:
JSON.stringify(value[,replacer[,space]])
value
:待处理成JSON字符串的JavaScript值,通常为对象或者数组replacer
:是一个可选参数。如果是一个函数,则表示在序列化过程中,被序列化值的每个属性都会经过该函数处理;如果为一个数组,则表示只有包含在这个数组中的属性名才会被序列化到最终JSON字符串中;如果为null或未传递,则value参数对应值的所有属性都会被序列化space
:是一个可选参数,用于指定缩进用的空白字符串,美化输出。如果是数字,则代表对应个数空格,上限为10;如果小于1则没有空格;如果为字符串则取字符串的前10个字符作为空格;如果为null或未传入则没有空格在JSON序列化时,如果属性值为对象或者数组,则会继续序列化该属性值,直到属性值为基本类型、函数或者Symbol类型才结束。
如果一个被序列化的对象拥有toJSON()函数,那么toJSON()函数就会覆盖默认的序列化行为,被序列化的值将不再是原来的属性值,而是toJSON()函数的返回值。
toJSON()函数用于更精确的控制序列化,可以看作是对stringify()函数的补充。
JSON反序列化即将JSON字符串转换为JSON对象的过程,得到的结果用于在JavaScript中做逻辑处理。
有两种方式实现:
JSON.parse()函数用来解析JSON字符串,构造由字符串描述的JavaScript值或对象
JSON.parse(text[,reviver])
text
:带解析的JSON字符串reviver
:是可选参数。如果是一个函数,则规定了原始值在返回之前如何被解析改造。如果被解析的JSON字符串是非法的,则会抛出异常JSON.parse('[1,2,3,true]'); // Array [1, 2, 3, true]
JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}
JSON.parse('true'); // true
JSON.parse('123.45'); // 123.45
当使用JSON.parse()函数解析JSON字符串时,需要注意两点;
eval()函数用于计算JavaScript字符串,并把它作为脚本来执行。
在使用eval()函数进行JSON反序列化时,其语法如下所示:
eval("(" + str + ")")
str
:待处理的字符串因为JSON字符串是以{}
开始和结束的,在JavaScript中它会被当作一个语句块来处理,所以必须强制将它处理成一个表达式,所以采用括号
var json1 = '{"name":"kingx"}';
var json2 = '{"address":["beijing","shanghai"]}';
console.log(eval("(" + json1 + ")"));// {name: "kingx"}
console.log(eval("(" + json2 + ")"));// {address: ["beijing", "shanghai"]}
浏览器同源策略是浏览器最基本也是最核心的安全功能,它约定客户端脚本在没有明确授权的情况下,不能读写不同源的目标资源。
同源明确地表示为相同协议、域名和端口号,如果两个资源路径在协议、域名、端口号上有任何一点不同,则它们就不属于同源的资源。
在同源策略上,又分为 2 种表现形式:
主要是由没有遵守浏览器的同源策略引起的,浏览器对跨域访问的限制,可以在很大程度上保护用户的隐私数据安全。
下面是两个实际场景
(1)没有DOM同源策略限制
假如浏览器没有DOM同源策略限制,那么不同域的iframe可以相互访问,黑客就可以采用以下的方式发起攻击:
(2)没有XMLHttpRequest同源策略限制
假如浏览器没有XMLHttpRequest同源策略限制,那么黑客可以进行跨站请求伪造CSRF攻击,具体方式如下:
虽然浏览器有跨域访问的限制,但是在某些实际的业务场景中,不可避免地需要进行跨域访问。
CORS(CrossOrigin Resource Sharing,跨域资源共享)。主要实现方式是服务端通过对响应头的设置,接收跨域请求处理。
不同的服务端框架采用的处理方式不同。
通过服务端的处理不会对前端代码做任何修改,但是由于服务端采用的语言、框架多变,处理方式会依赖各种语言的特性。
JSONP是客户端与服务器端跨域通信最常用的解决办法,它的特点是简单适用、兼容老式浏览器、对服务器端影响小。
主要思想可以分 2 步理解:
构建JSONP请求实际是创建一个新的script元素,通过src属性指定跨域请求的url,并在url中携带请求成功的回调函数作为参数。
回调函数必须设置为全局函数。因为服务端在响应后会从全局查找回调函数,所以如果回调函数不是定义在全局作用域中,那么会报错。
(1)优点
(2)缺点