天天看点

JavaScript基础——Ajax与Comet

Ajax是无需刷新页面就能够从服务器取得数据的一种方法。

关于Ajax,可以从以下几个方面来总结以下:

1)负责Ajax运作的核心对象是XMLHttpRequest对象。

2)XHR对象由微软最早在IE5中引入,用于通过JavaScript从服务器取得XML数据。

3)在此之后,Firefox、Safari、Chrome和Opera都实现了相同的特性,使XHR成为了Web的一个事实标准。

4)虽然实现之间存在差异,但XHR对象的基本用法在不同浏览器还是相对规范的,因此可以放心地用在Web开发中。

同源策略是对XHR的一个主要约束,它为通信设置了“相同的域、相同的端口、相同的协议”这一限制。

试图访问上述限制之外的资源,都会引发安全错误,除非采用被认可的跨域解决方案。

这个解决方案叫做CORS(跨域资源共享),IE8通过XDomainRequest对象支持CORS,其他浏览器通过XHR对象原生支持CORS。图像Ping和JSONP是另外两种跨域通信的技术,但不如CORS稳妥。

Comet是对Ajax的进一步扩展,让服务器几乎能够实时地向客户端推送数据。

实现Comet的手段主要有两个:长轮询和HTTP流。所有浏览器都支持长轮询,而只有部分浏览器原生支持HTTP流。

SSE(服务器发送事件)是一种实现Comet交互的浏览器API,既支持长轮询,也支持HTTP流。

Web Sockets是一种与服务器进行全双工、双向通信的信道。与其他方案不同,Web Sockets不使用HTTP协议,而是用一种自定义的协议。这种协议专门为快速传输小数据设计。虽然要求使用不同的Web服务器,但却有速度上的优势。

各方面对Ajax和Comet的鼓吹吸引了越来越多的开发人员学习JavaScript,人们对Web开发的关注也再度升温。

与Ajax有关的概念都还相对比较新,这些概念会随着时间推移继续发展。

<!DOCTYPE html>
<html  xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Ajax与Comet</title>
</head>
<form action="postexample.php" method="post" id="user-info">
    <input type="text" name="name" value="Jason"/>
    <input type="password" name="psw" value="123456"/>
</form>
<div id="status"></div>
<body>
<script src="l21.js"></script>
</body>
</html>
           
/**
 * Ajax 与Comet
 */
function cl(x){
    console.log(x);
}
/**
 * 21.1 XMLHttpRequest对象
 */
//创建XMLHttpRequest对象
function createXHR(){
    if(typeof XMLHttpRequest !="undefined"){    //IE7+ W3C
        return new XMLHttpRequest();
    }else {
        throw new Error("不存在XHR对象");
    }
}
var xhr=new createXHR();
//21.1.1 XHR的用法
//在收到响应后,响应的数据会自动填充XHR对象的属性
//responseText:作为响应主体被返回的文本
//responseXML:如果响应的内容类型是"text/xml"或"application/xml",这个属性中将保存包含着响应数据的XML DOM文档。
//status:响应的HTTP状态
//statusText:HTTP状态的说明
//readyState:表示请求响应过程的当前活动阶段
//为确保接收到适当的响应,应该检查是否接收到全部响应数据,以及两种状态代码
xhr.onreadystatechange=function(){
    if(xhr.readyState==4){
        if((xhr.status>=200 && xhr.status<300) || xhr.status==304){
            cl(xhr.responseText);
        }else{
            cl("Request was unsuccessful:"+xhr.status);
        }
    }
};
//启动异步请求
xhr.open("get","example.txt",true);
//发送请求
xhr.send(null);
//取消异步请求
xhr.abort();

//21.1.2 HTTP头部信息
//默认情况下,在发送XHR请求的同时,还会发送下列头部信息
//Accept:浏览器能够处理的内容类型
//Accept-Charset:浏览器能够显示的字符集
//Accept-Encoding:浏览器能够处理的压缩编码
//Accept-Language:浏览器当前设置的语言
//Connection:浏览器与服务器之间连接的类型
//Cookie:当前页面设置的任何Cookie
//Host:发送请求的页面所在的域
//Referer:发送请求的页面URI
//User-Agent:浏览器的用户代理字符串
var xhr=new createXHR();
xhr.onreadystatechange=function(){
    if(xhr.readyState==4){
        if((xhr.status>=200 && xhr.status<300) || xhr.status==304){
            cl(xhr.responseText);
        }else{
            cl("Request was unsuccessful:"+xhr.status);
        }
    }
};
//启动异步请求
xhr.open("get","example.php",true);
//设置自定义的请求头部信息
xhr.setRequestHeader("myHeader","myValue");
//发送请求
xhr.send(null);
//取得相应的响应头部信息
var myHeader=xhr.getResponseHeader("myHeader");
//取得包含所有头部信息的长字符串
var allHeaders=xhr.getAllResponseHeaders();

//21.1.3 GET请求
xhr.open("get","example.php?name1=value1&name2=value2",true);
//查询字符串中每个参数的名称和值必须使用encodeURIComponent()进行编码
//下面这个函数可以辅助向现有URL的末尾添加查询字符串参数
function addURLParam(url,name,value){
    url+=(url.indexOf("?")==-1?"?":"&");
    url+=encodeURIComponent(name)+"="+encodeURIComponent(value);
    return url;
}
//构建请求URL的事例
var url="example.php";
//添加参数
url=addURLParam(url,"name","Jason");
url=addURLParam(url,"book","Professional JavaScript");
//初始化请求
xhr.open("get",url,true);

//21.1.4 POST请求
//向服务器提交表单数据
function submitData(){
    var xhr=new createXHR();
    xhr.onreadystatechange=function(){
        if(xhr.readyState==4){
            if((xhr.status>=200 && xhr.status<300) || xhr.status==304){
                cl(xhr.responseText);
            }else{
                cl("Request was unsuccessful:"+xhr.status);
            }
        }
    };
    xhr.open("post","postexample.php",true);
    xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
    var form=document.getElementById("user-info");
    xhr.send(serialize(from));
}
//表单序列化的方法
function serialize(form){
    var parts=[],
        field=null, i,len, j,optLen,option,optValue;
    for(i=0,len=form.elements.length;i<len;i++){
        field=form.elements[i];
        switch(field.type){
            case "select-one":
            case "select-multiple":
                if(field.name.length){
                    for(j=0,optLen=field.options.length;j<optLen;j++){
                        option=field.options[j];
                        if(option.selected){
                            optValue="";
                            if(option.hasAttribute){
                                optValue=(option.hasAttribute("value")?option.value:option.text);
                            }else{
                                optValue=(option.attributes("value").specified?option.value:option.text);
                            }
                            parts.push(encodeURIComponent(field.name)+"="+encodeURIComponent(optValue));
                        }
                    }
                }
                break;
            case undefined:     //字符集
            case "file":        //文件输入
            case "submit":      //提交按钮
            case "reset":       //重置按钮
            case "button":      //自定义按钮
                break;
            case "radio":       //单选按钮
            case "checkbox":    //复选框
                if(!field.checked){
                    break;
                }
            /*执行默认操作 */
            default :
                //不包含没有名字的表单字段
                if(field.name.length){
                    parts.push(encodeURIComponent(field.name)+"="+encodeURIComponent(optValue));
                }
        }
    }
    return parts.join("&");
}

/**
 * 21.2 XMLHttpRequest 2级
 */
//21.2.1 FormData (w3c)
//FormData为序列化表单以及创建与表单格式相同的数据提供了便利var
var data=new FormData();
data.append("name","Jason");
//通过向FormData构造函数中传入表单元素,也可以用表单元素的数据预先向其中填入键值对儿
var data=new FormData(document.forms[0]);
//创建FormData实例后,可以将它直接传给XHR的send()方法
var xhr=new createXHR();
xhr.onreadystatechange=function(){
    if(xhr.readyState==4){
        if((xhr.status>=200 && xhr.status<300) || xhr.status==304){
            cl(xhr.responseText);
        }else{
            cl("Request was unsuccessful:"+xhr.status);
        }
    }
};
xhr.open("post","postexample.php",true);
var form=document.getElementById("user-info");
xhr.send(new FormData(form));

//21.2.2 超时设定(IE8+)
//浏览器在规定的时间内没有接收到响应,就会触发timeout事件
var xhr=new createXHR();
xhr.onreadystatechange=function(){
    if(xhr.readyState==4){
        try{
            if((xhr.status>=200 && xhr.status<300) || xhr.status==304){
                cl(xhr.responseText);
            }else{
                cl("Request was unsuccessful:"+xhr.status);
            }
        }catch(ex){
            ontimeout();
        }

    }
};
xhr.open("get","timeout.php",true);
xhr.timeout=3000;//将超时设置为3秒钟(仅适用于IE8+);
xhr.ontimeout=function(){
    alert("请求在3秒内没有回应");
}
xhr.send(null);

//21.2.3 overrideMimeType()方法  (w3c)
//overrideMimeType()用于重写XHR响应的MIME类型
//通过调用overrideMimeType()方法,可以保证把响应当做XML而非纯文本来处理
var xhr=new createXHR();
xhr.open("get","text.php",true);
xhr.overrideMimeType("text/xml");
xhr.send(null);

/**
 * 21.3 进度事件
 */
//与客户端服务器通信有关的6个进度事件:
//loadstart:在接收到响应数据的第一个字节时触发
//progress:在接收响应期间持续不断地触发
//error:在请求发生错误时触发
//abort:在因为调用abort()方法而终止连接时触发
//load:在接收到完整的响应数据时触发
//loadend:在通信完成或者触发error、abort或load事件后触发

//21.3.1 load事件
var xhr=new createXHR();
xhr.οnlοad=function(event){
    if((xhr.status>=200 && xhr.status<300) || xhr.status==304){
        cl(xhr.responseText);
    }else{
        cl("Request was unsuccessful:"+xhr.status);
    }
};
xhr.open("get","altevents.php",true);
xhr.send(null);

//21.3.2 progress事件
//为用户创建进度指示器的一个示例
var xhr=new createXHR();
xhr.οnlοad=function(event){
    if((xhr.status>=200 && xhr.status<300) || xhr.status==304){
        cl(xhr.responseText);
    }else{
        cl("Request was unsuccessful:"+xhr.status);
    }
};
xhr.οnprοgress=function(event){
    var divStatus=document.getElementById("status");
    if(event.lengthComputable){
        divStatus.innerHTML="Received "+event.position+" of"+event.totalSize+" bytes";
    }
}
xhr.open("get","altevents.php",true);
xhr.send(null);

/**
 * 21.4 跨域资源共享(CORS)
 */
//CORS背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

//21.4.1 IE对CROS的实现
//XDR对象与XHR的一些不同是:
//cookie不会随请求发送,也不会随响应返回
//只能设置请求头部信息中的Content-Type字段
//不能访问响应头部信息
//只支持GET和POST请求
var xdr=new XDomainRequest();
xdr.οnlοad=function(){
    alert(xdr.responseText);
}
xdr.οnerrοr=function(){
    alert("An error occurred.");
}
xdr.timeout=3000;
xdr.ontimeout=function(){
    alert("Request took too long");
}
xdr.open("post","http://www.somewhere-else.com/page/");
xdr.contentType="application/x-www-form-urlencoded";
xdr.send("name1=value&name2=value2");
//终止请求
xdr.abort();

//21.4.2 其他浏览器对CORS的实现
var xhr=new createXHR();
xhr.onreadystatechange=function(){
    if(xhr.readyState==4){
        if((xhr.status>=200 && xhr.status<300) || xhr.status==304){
            cl(xhr.responseText);
        }else{
            cl("Request was unsuccessful:"+xhr.status);
        }
    }
};
xhr.open("get","http://www.somewhere-else.com/page/",true);
xhr.send(null);
//跨域XHR对象为了安全,有一些限制:
//不能使用setRequestHeader()设置自定义头部
//不能发送和接收cookie
//调用getAllResponseHeaders()方法返回空字符串。

//21.4.3 Preflighted Requests (w3c)
//CORS通过一种叫做Preflighted Requests的透明服务器验证机制
// 支持开发人员使用自定义的头部、GET或POST之外的方法,以及不同类型的主体内容

//21.4.4 带凭据的请求 (w3c)
//默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等)。
//通过将withCredentials属性设置为true,可以指定某个请求发送凭据。

//21.4.5 跨浏览器的CORS
function createCORSRequest(method,url){
    var xhr=new XMLHttpRequest();
    if("withCredentials" in xhr){
        xhr.open(method,url,true);
    }else if(typeof XDomainRequest!="undefined"){
        var xhr=new XDomainRequest();
        xhr.open(method,url);
    }else{
        xhr=null;
    }
    return xhr;
}
var request=createCORSRequest("get","http://www.somewhere-else.com/page/");
if(request){
    request.οnlοad=function(){
        //对request.responseText进行处理
        cl(request.responseText);
    };
    request.send();
}

/**
 * 21.5 其他跨域技术
 */
//21.5.1 图像 Ping
//图像 Ping最常用于跟踪用户点击页面或动态广告曝光次数
//图像 Ping只能用于浏览器与服务器间的单向通信
var img=new Image();
img.οnlοad=img.οnerrοr=function(){
    alert("Done!");
};
img.src="http://www.example.com/test?name=Jason";

//21.5.2 JSONP
//JSONP是通过动态<script>元素来使用的,使用时可以为src属性指定一个跨域URL
function handleResponse(response){
    alert("您的IP地址"+response.ip+",在"+response.city+","+response.region_name);
}
var script=document.createElement("script");
script.src="http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script,document.body.firstChild);
//JSONP能够直接访问响应文本,支持在浏览器与服务器之间双向通信
//JSONP是从其他域中加载代码执行,并不一定安全
//要确定JSONP请求是否失败并不容易

//21.5.3 Comet (服务器推送)
//Comet能够让信息近乎实时地推送到页面上,非常适合处理体育比赛的分数和股票报价
//两种实现Comet的方式:长轮询和流
//轮询的优势是所有浏览器都支持,因为使用XHR对象和setTimeout()就能实现
/* php脚本实现HTTP流
<?php
    $i=0;
while(true){
    //输出一些数据,然后立即刷新输出缓存
    echo "Number is $i";
    flush();
    //等几秒钟
    sleep(10);
    $i++;
}
*/
//使用XHR对象实现HTTP流的典型代码:
function createStreamingClient(url,progress,finished){
    var xhr=new XMLHttpRequest(),received=0;
    xhr.open("get",url,true);
    xhr.onreadystatechange=function(){
        var result;
        if(xhr.readyState==3){
            //只取得最新数据并调整计数器
            result=xhr.responseText.substring(received);
            received+=result.length;
            //调用progress回调函数
            progress(result);
        }else if(xhr.readyState==4){
            finished(xhr.responseText);
        }
    };
    xhr.send(null);
    return xhr;
}
var client=createStreamingClient("streaming.php",function(data){
    alert("Received:"+data);
},function(data){
    alert("Done!");
});

//21.5.4 SSE 服务器发送事件  (w3c)
//SSE是围绕只读Comet交互推出的API或者模式。
//21.5.4.1 SSE API
//EventSource有一个readyState属性,值有0、1、2,表示连接服务器的状态
//另外还有三个事件:open、message、error
//SSE适合长轮询和HTTP流
var source=new EventSource("myevents.php");
source.onmessage=function(event){
    var data=event.data;
    //处理数据
}
//强制断开,可调用close()
source.close();
//21.5.4.2 事件流
//所谓的服务器事件会通过一个持久的HTTP响应发送,这个响应的MIME类型为text/event-stream

//21.5.5 Web Sockets
//Web Sockets的目标是在一个单独的持久连接上提供全双工、双向通信。
//Web Sockets使用了自定义的协议,所以URL模式也略有不同。
//21.5.5.1 Web Sockets API
//创建WebSocket对象
var socket=new WebSocket("ws://www.example.com/server.php");
//实例化了WebSocket对象后,浏览器就会麻黄素那个尝试创建连接。
//WebSocket也有一个表示当前状态的readyState属性。
//WebSocket.OPENING(0):正在建立连接
//WebSocket.OPEN(1):已经建立连接
//WebSocket.CLOSING(2):正在关闭连接
//WebSocket.CLOSE(3):已经关闭连接
//关闭WebSocket连接,可以在任何时候调用close()方法
socket.close();
//21.5.5.2 发送和接收数据
var socket=new WebSocket("ws://www.example.com/server.php");
socket.send("Hello world!");
//Web Sockets只能通过连接数据发送纯文本数据,
// 对于复杂的数据结构,连接发送之前,必须进行序列化
var message={
    time:new Date(),
    text:"Hello world",
    clientId:"asdfp8734rew"
};
//将message序列化为JSON字符串再发送
socket.send(JSON.stringify(message));
//当服务器向客户端发来消息时,WebSocket对象就会触发message事件
socket.onmessage=function(event){
    var data=event.data;
    //处理数据
}
//21.5.5.3 其他事件
//open:在成功建立连接时触发
//error:在发生错误时触发,连接不能持续。
//close:在连接关闭时触发
var socket=new WebSocket("ws://www.example.com/server.php");
socket.onopen=function(){
    alert("连接正常");
}
socket.οnerrοr=function(){
    alert("连接错误");
}
socket.onclose=function(event){
    cl("是否已经明确地关闭:"+event.wasClean+
    ",服务器返回的数值状态码="+event.code+
    ",服务器发回的消息:"+event.reason);
}

//21.5.6 SSE与Web Sockets
//面对某个具体的案例:如何选择SSE与SSE与Web Sockets?
//1.你是否有自由度建立和维护Web Sockets服务器
//2.到底需不需要双向通信?

/**
 * 21.6 安全
 */
//CSRF:跨站点请求伪造
//为确保通过XHR访问URL安全,通行的做法就是验证发送请求者是否有权限访问相应的资源。
//有以下几种方式可供选择:
//要求以SSL连接来访问可以通过XHR请求的资源
//要求每一次请求都要附带经过相应算法计算得到的验证码。
//请注意,下列措施对防范CSRF攻击不起作用:
//要求发送POST而不是GET请求——很容易改变。
//检查来源URL以确定是否可信——来源记录很容易伪造。
//基于cookie信息进行验证——同样很容易伪造。
           

继续阅读