zoukankan      html  css  js  c++  java
  • 第三十五课: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,具体实现方案,我们将在下一课代码讲解。

    加油!

  • 相关阅读:
    Leetcode 笔记 110
    Leetcode 笔记 100
    Leetcode 笔记 99
    Leetcode 笔记 98
    Leetcode 笔记 101
    Leetcode 笔记 36
    Leetcode 笔记 35
    Leetcode 笔记 117
    Leetcode 笔记 116
    android加载速度优化,通过项目的优化过程分析
  • 原文地址:https://www.cnblogs.com/chaojidan/p/4201742.html
Copyright © 2011-2022 走看看