本课主要教大家如何书写一个完整的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. java 使用POI批量导入excel数据

    一.定义 Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能. 二.所需jar包: 三.简单的一个读取e ...

  2. 【读书笔记《Android游戏编程之从零开始》】9.游戏开发基础(如何快速的进入 Android 游戏开发)

    1.不可盲目看API文档很多人在接触学习一门新的平台语言时,总喜欢先去探究一番API文档.先不说成效如何,至少编者认为这种方式不适合大部分人来效仿,主要原因在于 API 领域广泛,牵涉到的知识点太多, ...

  3. bfs简单题-poj2251

    宽搜基础题 思路很简单,注意细节. 走过的节点一定要打上标记//tag数组 三维字符串输入一定要注意 #include <stdio.h> #include <iostream> ...

  4. excel相关

    1.excel怎样筛选重复数据 打开Excel文件,选中待处理的数据区域,然后分别点击菜单开始--条件格式--突出显示单元格规则--重复值. 确认以Excel默认的格式突出显示重复值.之后,重复的数据 ...

  5. POJ 1515 Street Directions --一道连通题的双连通和强连通两种解法

    题意:将一个无向图中的双向边改成单向边使图强连通,问最多能改多少条边,输出改造后的图. 分析: 1.双连通做法: 双连通图转强连通图的算法:对双连通图进行dfs,在搜索的过程中就能按照搜索的方向给所有 ...

  6. SVN代码的回滚二

    SVN代码的回滚: 不丢失新建的文件,获得最新的SVN版本控制.TortoiseSVN-ShowLog-选中你要回滚的版本-右键-Export,之后将修改的文件覆盖到你的最新版本,commit即可. ...

  7. javascript单元测试工具

    单元测试关注的是验证一个模块或一段代码的执行效果是否和设计或预期一样.有些开发人员认为,编写测试用例浪费时间而宁愿去编写新的模块.然而,在处理大型应用程序时,单元测试实际上会节省时间:它能帮助您跟踪问 ...

  8. Javascript中call和apply的区别与详解

    在js中call和apply它们的作用都是将函数绑定到另外一个对象上去运行,两者仅在定义参数方式有所区别,下面我来给大家介绍一下call和apply用法: 在web前端开发过程中,我们经常需要改变th ...

  9. shell+curl监控网站页面(域名访问状态),并利用sedemail发送邮件

    应领导要求,对公司几个主要站点的域名访问情况进行监控.下面分享一个监控脚本,并利用sendemail进行邮件发送. 监控脚本如下:下面是写了一个多线程的网站状态检测脚本,直接从文件中读出站点地址,然后 ...

  10. 26Spring_的注解实际应用_关键整理一下之前的注解

    写一个银行转账案例, 案例结构如下: