chaojidan

导航

第三十五课:Ajax详解

一个完整的Ajax请求:

var xhr = new (self.XMLHttpRequest || ActiveXObject)("Microsoft.XMLHTTP");   //new XMLHttpRequest()传入参数不影响。

xhr.onreadystatechange = function(){    //请求状态改变就会执行这个函数,状态有0,1,2,3,4五个状态。

  if(this.readyState === 4 && this.status === 200){     

    alert(this.responseText);

  }

}

xhr.open("post","url",true);     //第三个参数如果为true,就代表异步请求,如果为false就代表是同步。

xhr.setRequestHeader("","");   //设置请求头

xhr.send("key=val&key2=val2");      //发送数据

大家可能知道,在new ActiveXObject()时,针对IE不同版本的浏览器,传入的参数也不一样。根据IE官方建议,我们只要检测这四个参数:"Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP",Microsoft.XMLHTTP。其中"Microsoft.XMLHTTP"是针对IE5以及以前版本的。

为了达到兼容性,我们需要这样做:

var xhr = (function(){

  var fns = [

    function(){return new XMLHttpRequest();},

    function(){return new ActiveXObject("Msxml2.XMLHTTP");},

    function(){return new ActiveXObject("Msxml2.XMLHTTP.6.0");},

    function(){return new ActiveXObject("Msxml2.XMLHTTP.3.0");},

    function(){return new ActiveXObject("Microsoft.XMLHTTP");}    //这里兼容IE5,现在的程序都不需要兼容IE5了,因此可以去掉。

  ], xhrTemp;

  for(var i =0,len = fns.length ; i< len;i++){

    try{

      fns[i]();   //调用每一个创建xhr对象的函数,如果成功,就把生成xhr对象的函数返回给xhr变量。

      xhrTemp = fns[i];

      break;

    }catch(){}

  }

  return xhrTemp;

})();

还有一部分人可能知道,其实IE6,7已经支持XMLHttpRequest对象了,但是为什么还需要用ActiveXObject对象呢,因为IE6,7的XMLHttpRequest对象有兼容性问题,比如:IE7下的xhr对象不支持本地file协议,会出现拒绝访问,需要倒退到ActiveXObject对象来操作本地file协议的功能。而且它们的xhr对象不是一个javascript对象,也就是说在IE6,7下,你通过var xhr = new XMLHttpRequest();生成的xhr对象不是js对象,而是ActiveXObject对象。

总结以上,我们可以在我们框架中,写出以下的最终版本:

window.$ = {};

var s = ["XMLHttpRequest", "ActiveXObject('Msxml2.XMLHTTP.6.0')", "ActiveXObject('Msxml2.XMLHTTP.3.0')", "ActiveXObject('Msxml2.XMLHTTP')"];

if(!"1"[0]){    //IE6,7下,"1"[0]返回undefined,其他浏览器返回"1"。

  s[0] = location.protocol === "file:" ? "!" : s[0];      //如果是本地协议的操作,那么就覆盖XMLHttpRequest,解决IE6,7兼容性问题。

}

for(var i =0, xhr; xhr = s[i++];){

  try{

    if(eval("new " +xhr)){     //判断是否能够new出一个xhr对象,这里的eval可以把字符串解析成js代码执行

      $.xhr = new Function("return new " + xhr);    //新建一个Function实例对象,传入的参数是一个字符串。如果xhr="XMLHttpRequest",这段代码实际上会变成这样:function(){ return new XMLHttpRequest();}

      break;

    }

  }

  catch(){}

}

xhr对象的事件回调函数的绑定与状态分析

针对xhr对象的事件回调函数,我们一般使用onreadysatechange。但是XMLHttpRequest2(XMLHttpRequest新版本)添加了多个事件回调函数。如下:

loadstart:在请求开始时触发

progress:在请求发送数据以及接收数据期间,在服务器指定的时间,间隔触发

abort:在请求被取消时触发,例如:在调用了xhr.abort()方法时。

error:在请求失败时触发

load:在请求成功时触发

timeout:超时触发

loadend:在请求完成时触发,无论是成功还是失败

对于xhr请求的状态,我们一般通过判断xhr.status的值。

2xx状态与304我们都可以看成是成功状态。但是有一些兼容性问题需要处理:IE(非原生的xhr对象,也就是使用ActiveXObject实现的xhr)中,会将204设置为1223,Opera会在取得204后将status设置为0。因此,成功状态的写法如下:

if((xhr.status >=200 && xhr.status < 300) || xhr.status ==304 || xhr.status ==1223 || xhr.status ==0)

还有一个特例:在Firefox下,本地使用XMLHttpRequest,成功返回时,status等于0。由于很少在本地发请求,因此很多框架都没有对这个进行处理。

xhr发送请求和数据

open方法有5个参数,open(method,url,async,username,password)

method代表请求的HTTP方法,值包括:GET,POST,PUT,DELETE.HEAD,OPTION等。有些浏览器还支持你自定义method。

url是请求的地址,浏览器对这个url会进行同源安全策略,也就是会要求这个URL与包含脚本的文件具有相同的域名,端口,协议。在GET请求下,我们需要把传给url的数据,放到url?的后面,只能是字符串。

async指示请求使用的是异步还是同步,如果这个参数是false,请求是同步的,那么只要在send方法后,取xhr的响应数据就行了。如果这个参数是true或省略,请求是异步的,那么就需要在open方法之前在xhr对象上添加一个onreadystatechange事件回调函数。

username,password参数是可选的,为url所需的授权提供认证资格。

发送数据时,我们使用的是send方法,get方法一般传入null(在url?后面已经传入数据了),而post请求传入你要发给此url的数据。

早期的xhr对象,send方法中只能传入字符串,现代的xhr可以传FormData,document,ArrayBuffer,Blob。

FormData是一个什么样的对象呢,大家都知道,我们点击按钮提交表单时,浏览器会自动将表单的所有disabled为false的input,textarea,select,button元素的name与value抽取出来,变成一个querystring字符串。但是,我们在做ajax请求时,浏览器不会自动将表单的所有这些元素进行这样的处理,因此,我们需要用代码手动来实现这样的功能。jQuery中的serialize方法就是实现这样功能的一个方法。而现代浏览器的FormData对象也可以实现这样的功能。举个例子:

var formdata = new FormData();

formdata.append("name", "chaojidan");

formdata.append("age", 26);

xhr.send(formdata);

FormData也可以这样用:

var formobj = document.getElementById("form");   //得到页面上form元素的对象。

var formdata = formobj.getFormData();   //得到此form表单中所有disabled为false的input,textarea,select,button元素的name与value。

xhr.send(formdata);

FormData还可以这样用:

var formobj = document.getElementById("form");   //得到页面上form元素的对象。

var formdata =new FormData(formobj);   //得到此form表单中所有disabled为false的input,textarea,select,button元素的name与value。

xhr.send(formdata);

如果send的是一个document,我们需要把这个document赋值为一个xml对象,然后send(document)。

至于ArrayBuffer和Blob对象,大家可以去看js高级程序编程,里面说的很详细。

xhr接收数据

早期的xhr对象拥有两种接收数据的属性,xhr.responseText对应解码后的字符串(默认解码为utf-8),xhr.responseXML对应一个XML文档。IE还支持xhr.responseBody对应未解码的二进制数据。现在流行json数据的传输,因此服务器会通过responseText传json格式的字符串给前端,而我们前端需要对这个json格式的字符串进行解析,解析成js对象。至于如何解析,大家可以去看ajax hacks这本书。

至于后端返回什么类型的数据,我们可以通过xhr.getResponseHeader("Content-Type");

XMLHttpRequest2新增了responseType和response属性。

responseType,在发送请求前,根据你的数据需要,可以将xhr.responseType设置为text,arraybuffer,blob或document。忽略此设置,默认将响应设为text。

在请求完成后,xhr.response的属性值为DOMString或ArrayBuffer或Blob或Document,具体取决于responseType的设置。

xhr还有一个overrideMimeType方法,比如:xhr.overrideMimeType("text/plain; charset = x-user-defined"),此段代码的意思是,重写了默认的MIME类型,强制浏览器将该响应当成纯文本文件来对待,并且使用一个用户自定义的字符集(这样就是告诉浏览器,不要去解析数据,直接返回未处理过的字节码)。

XMLHttpRequest实现文件上传

 假设页面上有一个id为upload的input(type = file),还有一个id为progress用于显示进度的SPAN元素。

window.addEventListener("load", function(){

  var el = document.querySelector("#upload");

  var progress = document.getElementById("#progress");

  el.addEventListener("change",function(){

    var file = this.files[0];     //点击input,会弹出一个选择文件的框,用户选择好文件后,点击确定按钮,这时就会触发input元素的change事件,并且input元素有一个files属性,此属性值就是用户要上传的文件数组。input.files[1]代表第二个文件。

    if(file){  

      var xhr = new XMLHttpRequest();

      xhr.upload.addEventListener("progress",function(){    //当用ajax请求发送文件时,会触发xhr.upload的progress事件

        var done = e.position || e.loaded, total = e.totalSize || e.total;    //兼容性写法

        progress.innerHTML = (Math.floor(done/total *1000)/10) + "%";    //用百分比的形式显示,文件已经发送了百分之多少?

      });  

      xhr.addEventListener("load",function(){    //当ajax请求完成后,会触发load事件。

        progress.innerHTML = "上传成功";

      });

      xhr.open("put", "/upload", true);   //打开这个请求,异步请求

      xhr.setRequestHeader("X-File-Name", encodeURIComponent(file.fileName || file.name));  //把文件的名字放到请求头的X-File-Name字段中,需要编码

      xhr.setRequestHeader("Content-Type", "application/octet-stream");  //设置ajax请求的数据类型

      xhr.send(file);     //发送文件。

    }

  });

})

如果文件太大,我们可以用文件对象的slice方法对文件进行切割,然后分块上传。比如:上面例子中的file,我们可以调用var file1 = file.slice(0,1024),上传file1,然后再var file2 = file.slice(1024,file.size),上传file2。file.size可以其实就是得到file的文件大小。

那么,如何在老版本IE下上传文件,由于我们无法序列化二进制文件(上传文件其实就是二进制数据的上传),只好通过传统的form提交方法实现,为了防止提交后,刷新本页面,我们需要创建一个临时的iframe,具体实现方案,我们将在下一课代码讲解。

 

 

 

加油!

posted on 2015-01-04 19:16  chaojidan  阅读(892)  评论(0编辑  收藏  举报