超文本传输协议(http)规定web浏览器如何从web服务器获取文档和向web服务器提交表单内容,以及web服务器如何响应这些请求和提交。通常http并不在脚本的控制下,只是当用户单击链接,提交表单和输入URL时才发生
但是用js来操纵http是可行的,当脚本设置window对象的location属性(当然改变location的hash锚点不算)或调用表单对象的submit方法时,都会初始化http请求。
ajax的主要特点是使用脚本操纵http和web服务器进行交互,不会导致页面重载。
而在comet中,web服务器发起通信并发送消息到客户端,也就是服务器向客户端推数据
ajax
ajax的核心是XMLHttpRequest,也就是我们常说的xhr对象。一个http请求包括4部分:请求方法或动作,正在请求的URL,一个可选的请求头信息(可能包括身份验证的cookie等),一个可选的请求主体。而服务器返回的http响应包括3部分:一个数字和文字组成的状态码(用来显示请求的成功或失败),一个响应头集合,响应主题。浏览器需要考虑cookie,重定向,缓存和代理,但代码只需要关心请求和响应。
在创建了xhr对象之后,发起http请求的下一步是调用xhr对象的open()方法取指定这个请求的两个必需部分:方法和URL。open的第三个参数代表同步或者异步,如果是同步的话,多传一个false参数
//request这里代表xhr对象
request.open('GET', '/xxx/xxx');
如果有请求头的话,下一步就是设置它,比如POST请求需要设定MIME类型
request.setRequestHeader('Content-Type', 'text/plain');
如果对相同的头调用setRequestHeader()多次,新值不会取代之前指定的值。你不能通过这个方法自己指定'Content-Length'
, 'Date'
,'Referer'
,'User-Agent'
, cookie
这些头部,因为浏览器会根据xhr对象自动添加,防止你伪造他们。
使用xhr发起http请求的最后一步是指定可选的请求主体并向服务器发送它。使用send()
方法
//send中包含主体,如果是GET请求没有主体就传null
request.send(null);
一个发送纯文本请求的ajaxPOST请求
function postMessage(){
//新请求,这里省略兼容性写法
var request = new XMLHttpRequest();
request.open('POST', '/xx/xx');
//请求主体将是纯文本
request.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
request.send(msg);
}
发送请求之后,send()方法立即返回。为了响应准备就绪时得到通知,必须监听xhr对象上的readystatechange事件。readyState是一个整数,它的值为0代表open()尚未调用,值为1代表open()已经调用但尚未调用send方法,值为2代表send方法已经调用,值为3代表接收到部分数据,值为4代表响应完成。每次readyState属性改变都会出发readystatechange事件。
request.onreadystatechange = function(){
//request.status代表响应的http状态
//request.statusText是http状态的说明
if(request.readyState === 4 && request.status === 200){
var type = request.getResponseHeader('Content-Type');
//确保响应是文本
if(type.match(/^text/)){
callback(request.responseText);
}
//如果是json数据
if(type === 'application/json'){
callback(JSON.parse(request.responseText))
}
}
}
编码请求主体
http POST请求包括一个请求主体,它包含客户端传递给服务器的数据。
当用户提交表单时,表单中的数据(key和value)编码到一个字符串中并随请求发送。一个简单表单的编码像如下这样
name=sysuzhyupeng&age=24&birthday=19930227
表单的表码格式有一个正式的MIME类型
application/x-www-form-urlencoded
当使用POST方法提交这种顺序的表单数据时,必须设置Content-Type请求头为这个值。(如果直接在HTML里面提交而不是使用ajax,浏览器会自动把表单的数据编码并设置头部)。服务器得到的数据变成js对象的格式可能是
{
name: 'sysuzhyupeng',
age: '24',
birthday: '19930227'
}
如果使用GET请求,则在open的请求上加上查询字符串
request.open('GET', url + '?' + encodeFormData(data));
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
除了表单编码我们可以使用json编码
request.setRequestHeader('Content-Type', 'application/json');
request.send(JSON.stringify(data));
也可以是xml编码的请求,这里就不再叙述。
HTML表达的特性之一是当用户通过< input type=”file” />元素选择文件的时候,表单将在它产生的POST请求主体上发送文件内容了。而在xhr对象中
if(input.type == 'file'){
input.addEventListener('change', function(){
//每个<input type="file" />元素都有一个files属性
//假设只选择单个文件
var file = this.files[0];
if(!file) return;
var request = new XMLHttpRequest();
request.open('POST', url);
//文件类型是更通用的二进制大对象(Blob)类型中的一个子类型,因此直接扔进去就行
request.send(file);
}, false);
}
当HTML表单同时包含文件上传元素和其他元素时,浏览器不能使用普通的表单编码而必须使用'multipart/form-data'
的特殊Content-Type来用POST方法提交表单。xhr2定义了新的FormData API,使用下列代码我们不用设置Content-Type
var formdata = new FormData();
//假设data中包含文件和其他元素
for(name in data){
//跳过继承的属性
if(!data.hasOwnProperty(name)) continue;
var value = data[name];
//跳过方法
if(typeof value === 'function') continue;
//这里允许file对象
formdata.append(name, value);
}
//当传入formdata对象时,send()会自动设置Content-Type头
request.send(formdata);
中止请求和超时
可以使用xhr对象的abort
方法来取消正在进行的http请求,调用abort的主要原因是完成取消或超时请求消耗的时间太长。
var request = new XMLHttpRequest();
var timeout = false;
var timer = setTimeout(function(){
timeout = true;
request.abort();
}, 1000);
request.onreadystatechange = function(){
if(request.readState !== 4) return;
if(timeout) return;
clearTimeout(timer);
//请求成功
if(request.status === 200){
callback(request.responseText);
}
}
JSONP跨域
使用JSONP进行ajax传输的一个主要原因是,它不受同源策略的影响,因此可以使用他们从其他源的服务器请求数据,第二个原因是包含JSON编码数据的响应体会自动解码。
我们源页面是在a.com,想要获取b.com的数据,我们可以动态插入来源于b.com的脚本:
function jsonpServer(url) {
var script = document.createElement("script");
script.setAttribute("type", "text/javascript");
script.setAttribute("src", url);
document.body.appendChild(script);
}
function JSON_CALLBACK(data) {
console.log(data);
}
b.com页面接受到这个请求时,如果没有标识为JSONP, 会正常返回json的数据结果,像这样:
[{"id":"1","name":"syzuzhyupeng"},{"id":"2","name":"zhyupeng"}]
而利用JSONP,服务端会接受这个callback参数(不然请求的页面无法进行下一步操作),然后用这个参数值包装要返回的数据:
<?php
$data = '[{"id":"1","name":"syzuzhyupeng"},{"id":"2","name":"zhyupeng"}]';
$data = "JSON_CALLBACK(" . $data . ")";
echo $data;
?>
包裹后的响应会成为< script>元素的内容。
那么我们怎么告知服务器使用JSONP的响应而不是普通的JSON响应呢,这个可以通过在URL中添加一个查询参数来实现,例如追加'?json'
或'&json'
虽然JSONP在跨域ajax请求方面有很强的能力,但是它也有一些缺陷。首先,它没有关于JSONP调用的错误处理,一旦回调函数调用失败,浏览器没有反应。
domain
二级域名的情况下,domain属性可以设置为对应的一级域名。比如,当前域名是sub.example.com,则domain属性可以设置为example.com。除此之外的写入,都是不可以的。