本课主要教大家如何书写一个完整的ajax模块,讲解的代码主要跟ajax有关,而jQuery的ajax模块添加了Deferred异步编程的机制,因此对ajax的理解难度增大,还是忽略掉。但是我要讲解的代码跟jQuery的ajax模块思路是一样的,只是没有加入Deferred异步编程的思想,这样更有利于大家理解ajax的原理。

$.ajax = function(opts){    //大家如果用过jQuery的ajax,应该记得$.ajax({url:...,data:....,type:'POST',success:function(){}}),就可以进行一次ajax请求,这里的ajax方法也是一样,接收一个json对象。

  if(!opts || !opts.url){

    $.error("传入的参数必须为json对象,并且此对象要有url属性");

  }

  opts = setOptions(opts);   //处理用户传入的参数,比如:把type属性值大写化,把data的数据json对象转换成字符串格式等。

  var dummyXHR = new $.XMLHttpRequest(opts);    //创建一个xhr

  "complete success error".replace(/\S+/g, function(match){   //match = complete,success,error

    if(typeof opts[match] =="function") {   //如果传入的json对象中有此回调方法

      dummyXHR.bind(name, opts[name]);    //就给此xhr绑定此回调方法

      delete opts[name];  

    }

  })  

  if(opts.contentType){     //如果有设置请求内容类型的字段,就设置

    dummyXHR.setRequestHeader("Content-Type", opts.contentType);

  }

  for(var i in opts.headers){   //如果传入了请求头的字段集合,就设置

    dummyXHR.setRequestHeader(i, opts.headers[i]);

  }

  if(opts.async && opts.timeout){   //如果是异步请求,并且有超时字段

    dummyXHR.timeoutID = setTimeout(function(){

      dummyXHR.abort();

    }, opts.timeout);    //  超时后,将执行回调方法,把请求中断

  }

  dummyXHR.request();   //发送请求

  return dummyXHR;

}

$.XMLHttpRequest = function(opts){

  this.readyState = 0;

  this.options = opts;

  this._events = {};

  this.requestHeaders:{}

}

$.XMLHttpRequest.prototype = {

  constructor: $.XMLHttpRequest,

  bind: function(type,callback){

    var listeners = this._events[type];    //如果此类型的事件已经绑定过事件回调函数,那么就直接添加到数组中就行了

    if(listeners){

      listeners.push(callback);

    }else{

      this._events[type] = [callback];

    }

    return this;

  },

  setRequestHeader:function(name,value){

    this.requestHeaders[name] = value;

    return this;

  },

  request:function(){

    var opts = this.options;

    var xhr = this.xhr = new $.xhr();  //这里上一课已经讲了它的兼容性写法,因此这里不再书写

    if(opts.async){   //如果是异步请求,需要添加事件监听函数

      if(xhr.onerror === null){   //如果浏览器支持最新的xhr的接口,不支持的话,这里是undefined。

        var self = this;

        xhr.onload = xhr.onerror = function(e){

          this.readyState = 4;   //强制把状态变成4,兼容IE9+,IE浏览器可能会出现3,或4的情况,因此这里强制设置,兼容处理。

          self.respond();    //请求完成后,执行回调方法  

        }      

      }else{

        xhr.onreadystatechange = function(){

          self.respond();

        }

      }

    }

    if(opts.crossDomain && !("withCredentials" in  xhr)){

      $.error("本浏览器不支持跨域");

    }

    if(opts.username){     //调用xhr对象的open方法,打开连接,这里如果是get请求,在setOptions方法中,已经把data中的数据添加到url后面了。

      xhr.open(opts.type,opts.url,opts.async,opts.username,opts.password);

    }else{

      xhr.open(opts.type,opts.url,opts.async);

    }

    for(var i in this.requestHeaders){

      xhr.setRequestHeader(i,this.requestHeaders[i]);  //设置真正的xhr对象的请求头

    }

    xhr.send(opts.data || null);     //如果是post请求,这里就会有data数据,如果是get请求,这里就没有data属性,返回undefined,因此send(null)。

  },

  respond : function(forceAbort){

    var xhr = this.xhr;

    if(!xhr) return;   //onreadystatechange会执行多次,因此通过这个变量来判断是否已经执行过了。

    try{

      var completed = xhr.readyState ===4;   //状态为4时,就代表请求完成

      if(completed || forceAbort){  //如果超时,就会强制取消请求

        xhr.onerror = xhr.onload = xhr.onreadystatechange = null;

        if(forceAbort){

          xhr.abort();

        }else{

          var status = xhr.status;

          this.responseText = xhr.responseText;

          try{

            var xml = xhr.responseXML;   //以防返回的xml是一个不正规的xml,浏览器解析时会生成一个DOMException对象,访问时,会抛错。

          }catch(e){}

          if(xml && xml.documentElement){  //如果是xml文档

            this.responseXML = xml;

          }         

          try{

            var statusText = xhr.statusText;   //跨域情况下,火狐访问它会抛错

          }catch(e){

            statusText = "火狐访问错误";

          }

          this.dispatch(status,statusText);

        }  

      }

    }catch(e){   //如果网络出现问题,访问xhr的属性,在火狐下会抛错。

      this.dispatch(500,e+"");

    }

  },

  abort:function(){

    this.respond(true);

    return this;

  },

  dispatch:function(status,statusText){

    this.readyState = 4;

    var eventType = "error";

    if(status >=200 && status < 300 || status ===304 ||status ===1223||status ===0){  //status=204,代表请求成功,但是没有内容返回。

      eventType = "success";

      if(status == 204 ||status ===1223||status ===0 ){

        statusText = "noContent";

      }else if(status == 304){

        statusText = "noModified"

      }

      else{

        var dataType = this.xhr.getResponseHeader("Content-Type") || "text"; //得到数据的类型

        try{

          this.response = $.ajaxConverters[dataType].call(this,this.responseText,this.responseXML);  //处理不同数据

        }catch(e){

          eventType = "error";      //如果数据解析出错

          statusText = "parsererror:"+e;

        }

      }

    }

    this.status = status;

    this.statusText = statusText;

    if(this.timeoutID){     //清除定时器

      clearTimeout(this.timeoutID);

      delete this.timeoutID;

    }

    if(eventType === "success"){

      this.fire(eventType, this, statusText , this.response);   //如果请求成功,就触发成功的回调函数

    }else{

      this.fire(eventType, this, statusText);

    }

    this.fire("complete", this, statusText);   //不管成功或者失败,只要请求完成后,都会调用complete回调方法。

    delete this.xhr;

  },

  fire:function(type){

    var listeners = this._events[type] || [];

    if(listeners.length){   //如果有此类型的回调方法

      var args = [].slice.call(arguments);

      for(var i=0,callback;callback = listeners[i++];){

        callback.apply(window,args);

      }

    }  

  }

}

$.ajaxConverters = {

  text:function(text){

    return text || "";

  },

  xml:function(text,xml){

    return xml != undefined ? xml : $.parseXML(text);

  },

  html:function(text){

    return $.parseHTML(text);

  },

  json:function(text){

    return $.parseJSON(text);

  },

  script:function(text){

    return $.parseJS(text);

  }

}

jsonp原理,请前端开发人员必须去看,很容易理解,但是非常重要。面试必问,而且还有一个问题,也是面试官非常喜欢问的,就是解析一个url的方法。一般进入方法里面,需要一个正则来匹配这个url是否是一个正确的url,写出这个正则,就基本上可以得80分了。

当我们要给url后面添加查询字符串时,我们可以用url + (url.test(/\?/) ? "&" : "?") + name + "=" +value;    //这里没有考虑有hash的情况,如果url有?,就代表它本身有查询字符串,那么只要在后面添加&name=value就行了。如果没有,就需要在url添加?name=value。

最后,我们来讲一下,上一节课留下的问题,如何模拟老版本浏览器进行FormData的ajax请求。

请看源代码:

function request = function(opts){

  var form = opts.form;   //form指向的是页面上的form元素

  var ID = "iframe-upload";

  var iframe = createIframe(ID);   //创建一个新的id=ID,name =ID的iframe,并添加到页面中。但是这个iframe在页面中是隐藏的,不会显示在页面上

  var backups = {   //先把form元素的这些属性值保存起来,因为提交form表时,需要重写这些属性

    target:form.target ||"",

    action:form.action||"",

    enctype:form.enctype,

    method:form.method

  };

  var fields = opts.data ? addDataToForm(form, opts.data) : [];  //如果同时还需要提交其他数据,那么需要把这些数据放到form元素中。

  form.target = ID;   //以防提交时,刷新当前页面,现在只会刷新隐藏的iframe。

  form.action = opts.url;

  form.method = "POST";       //必须指定method与enctype,不然在Firefox下会报错。同时,如果form中包含文件域(<input type=file>)时,如果缺少method="POST",以及enctype = "multipart/form-data",文件将不会被发送给url。

  form.enctype = "multipart/form-data";   //form元素的enctype属性值,1:application/x-www-form-urlencoded    在发送前,编码所有字符(post请求默认就是此值)。2:text/plain  不对特殊字符编码。3:multipart/form-data  不对字符编码,在使用包含文件上传控件的表单时,必须使用该值。

  $.bind(iframe,"load",function(e){  //绑定iframe的load事件,当form表提交后,会触发iframe的刷新,这时就会触发iframe的load事件

    respond(e,iframe);

  });

  form.submit();    //提交form表

  for(var i in backups){

    form[i] = backups[i];   //恢复form元素的那些属性值

  }

  fields.forEach(function(input){

    form.removeChild(input);  //移除之前添加的隐藏的input元素

  })

}

function createIframe(ID){

  var iframe = $.parseHTML("<iframe "+"id='" + ID + "'"+ " name='"+ ID + "'" + " style='position:absolute;left:-9999px;top:-9999px;' />").firstChild;

  return (document.body || document.documentElement).insertBefore(iframe);   //把新创建的iframe添加到页面的最后面(第二个参数不写或写成null),并返回这个iframe。

}

function addDataToForm(form,data){

  var el,ret=[];

  for(var d in data){

    el = document.createElement("input");

    el.type = "hidden";  //隐藏的input

    el.name = d;

    el.value = data[d];

    form.appendChild(el);   //添加到form元素中

    ret.push(el);

  }

  return ret;

}

function respond(e,iframe){

  var node = iframe;

  var responseText;

  if(e && e.type == "load"){

    var doc = node.contentWindow.document;   //取得iframe中的document对象,这里的document对象就是url返回的数据

    responseText = doc;

    if(doc.body){  //如果返回的数据存在body,说明返回的不是xml。

      responseText = doc.body.innerHTML;

    }

    dispatch(200,"success",responseText);   //请求成功,执行回调函数  

    $.unbind(node,"load",function(e){  //绑定iframe的load事件,当form表提交后,会触发iframe的刷新,这时就会触发iframe的load事件

      respond(e,iframe);

    });

    setTimeout(function(){

      node.parentNode.removeChild(node);   //移除页面上的iframe。

    });

  }

}

这一课难度还是蛮大的,ajax这一章节也已经讲完,下一课,将讲解动画引擎。

加油!

第三十六课:如何书写一个完整的ajax模块的更多相关文章

  1. NeHe OpenGL教程 第三十六课:从渲染到纹理

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  2. JAVA学习第三十六课(经常使用对象API)— Set集合:HashSet集合演示

    随着Java学习的深入,感觉大一时搞了一年的ACM,简直是明智之举,Java里非常多数据结构.算法类的东西,理解起来就轻松多了 Set集合下有两大子类开发经常使用 HashSet集合 .TreeSet ...

  3. python第三十六课——2.迭代器对象

    满足前提: 1).必须是一个可迭代对象 2).可以被next()所作用的 举例: generator... 高效的检测一个对象是否是迭代器对象 需要使用collections模块中的Iterator类 ...

  4. python第三十六课——1.可迭代对象

    1.可迭代对象: 满足前提: 只要能被循环操作的对象,就可以可迭代对象 举例: str.list.tuple.set.dict.range.generator... 高效的检测一个对象是否是可迭代对象 ...

  5. 潭州课堂25班:Ph201805201 django 项目 第三十六课 后台文章管理(课堂笔记)

    get 请求, 1,获取文章标签 , 2,拿到前台传来的值, 3,根据前台传来的值在数据库中查询 4.,返回数据到前台,渲染, 分页算法 : 在 utils 下创建  paginator_script ...

  6. centos shell脚本编程2 if 判断 case判断 shell脚本中的循环 for while shell中的函数 break continue test 命令 第三十六节课

    centos  shell脚本编程2 if 判断  case判断   shell脚本中的循环  for   while   shell中的函数  break  continue  test 命令   ...

  7. 风炫安全web安全学习第三十六节课-15种上传漏洞讲解(一)

    风炫安全web安全学习第三十六节课 15种上传漏洞讲解(一) 文件上传漏洞 0x01 漏洞描述和原理 文件上传漏洞可以说是日常渗透测试用得最多的一个漏洞,因为用它获得服务器权限最快最直接.但是想真正把 ...

  8. NeHe OpenGL教程 第二十六课:反射

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  9. NeHe OpenGL教程 第十六课:雾

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

随机推荐

  1. 将text 文件转为List

    Integer 类型 ArrayList<Integer> Mlist = new ArrayList<Integer>(); Scanner scM = new Scanne ...

  2. html不使用cache数据

    <HEAD>      <META   HTTP-EQUIV="Pragma"   CONTENT="no-cache">     &l ...

  3. selenium更改readonly属性

    1.用js实现 JavascriptExecutor removeAttribute = (JavascriptExecutor)dr;        //remove readonly attrib ...

  4. readonly与const

    readonly与const 在C#中,readonly 与 const 都是定义常量,但不同之处在于:readonly 是运行时常量,而 const 是编译时常量. ; public void Te ...

  5. HDU 4865 Peter's Hobby --概率DP

    题意:第i天的天气会一定概率地影响第i+1天的天气,也会一定概率地影响这一天的湿度.概率在表中给出.给出n天的湿度,推测概率最大的这n天的天气. 分析:这是引自机器学习中隐马尔科夫模型的入门模型,其实 ...

  6. 2014 Super Training #10 G Nostop --矩阵快速幂

    原题: FZU 2173 http://acm.fzu.edu.cn/problem.php?pid=2173 一开始看到这个题毫无头绪,根本没想到是矩阵快速幂,其实看见k那么大,就应该想到用快速幂什 ...

  7. XUtils===XUtils3框架的基本使用方法

    转载自:http://blog.csdn.NET/a1002450926/article/details/50341173 今天给大家带来XUtils3的基本介绍,本文章的案例都是基于XUtils3的 ...

  8. nginx 与 tomcat 集群 一二事 (0) - 简单介绍

    最近看了nginx以及tomcat的集群,通俗的做一下简单总结吧 nginx 是一个http服务器,是由俄罗斯人发明的,目前主流的服务器,作为负载均衡服务器,性能非常好,最高支持5万个并发连接数,在淘 ...

  9. Linux 安装oracle客户端

    环境: CentOS release 6.4 (Final) 一.下载文件 1.首先要查看oracle版本,对应的SQL如下:  select * from V$version ; 2.根据oracl ...

  10. meate 标签使用介绍

    //禁止浏览器从本地计算机的缓存中访问内容 <meta http-equiv="pragma" content="no-cache"> //清楚缓存 ...