第三十六课:Ajax详解2

本课主要教大家如何书写一个完整的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这一章节也已经讲完,下一课,将讲解动画引擎。

加油!

时间: 2024-08-22 17:12:46

第三十六课:Ajax详解2的相关文章

Java进阶(三十二) HttpClient使用详解

Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们再讨论),它不仅是客户端发送Http请求变得容易,而且也方便了开发人员测试接口(基于Http协议的),即提高了开发的效率,也方便提高代码的健壮性.因此熟练掌握HttpClient是很重要的必修内容,掌握HttpClient后,相信对于Http协议的了解会更加深入. 一.简介 HttpClient是A

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

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第三十六课:从渲染到纹理 放射模糊和渲染到纹理: 如何实现放射状的滤镜效果呢,看上去很难,其实很简单.把渲染得图像作为纹理提取出来,在利用OpenGL本身自带的纹理过滤,就能实现这种效果,不信,你试试. 嗨,我是Dario Corn

2018-08-24 第三十六课

第三十六课 非关系统型数据库-mangodb 目录 二十四 mongodb介绍 二十五 mongodb安装 二十六 连接mongodb 二十七 mongodb用户管理 二十八 mongodb创建集合.数据管理 二十九 php的mongodb扩展 三十 php的mongo扩展 三十一 mongodb副本集介绍 三十二 mongodb副本集搭建 三十三 mongodb副本集测试 三十四 mongodb分片介绍 三十五 mongodb分片搭建 三十六 mongodb分片测试 三十七 mongodb备份

第三十六课 Spark之TaskScheduler Spark shell案例运行日志详解、TaskScheduler和SchedulerBackend、FIFO与FAIR、Task运行时本地性算法详

</pre></h2><div><p>本节课内容:</p><p>1.     TaskSchedulerBackend与SchedulerBackend</p><p>2.     FIFO与FAIR两种调度模式</p><p>3.     Task数据本地性资源的分配</p></div><h3>一.Scheduler运行过程(Spark-shell角度)

第三十六课、文本编辑器中的功能交互

一 .判断未保存的数据 1.QPlainTextEdit能够触发与编辑功能相关的信号 2.解决方案 (1).定义槽函数void onTextChanged() (2).映射textChanged()到槽函数 (3).定义成员变量bool m_isTextChanged = false; (4).当文本框内容发生改变时, m_isTextChanged = true; (5).当m_isTextChanged 为真时,则保存数据 二.文本编辑器的持续开发 1.文件打开操作 2.文件新建操作 #if

shell学习三十四天----printf详解

printf 先来看一个简单的例子:使用命令printf "hello,world\n", 输出:hello,world 再使用echo "hello,world\n",输出为:hello,world\n 案例二:使用命令printf "%s\n" hello,world 输出结果为:hello,world printf命令的完整语法有两个部分: printg format-string [arguments] 第一部分为描述格式规格的字符串,他

(待续)文件IO详解(十六)---fcntl函数详解

fcntl函数是用来在进程中实现对文件的多种操作的函数,通过不同的命令可以实现不同的操作.常用的操作有复制文件描述符.为文件设置建议锁和获取设置文件控制标志等. ======================================================= 函数原型: ======================================================= 操作一:复制文件描述符实现文件共享 函数参数: fd:要操作的文件描述符 cmd:F_DUPFD ar

JAVA学习第三十六课(常用对象API)- 集合框架(四)— Set集合:HashSet集合演示

随着Java学习的深入,感觉大一时搞了一年的ACM,简直是明智之举,Java里很多数据结构.算法类的东西,理解起来就轻松多了 Set集合下有两大子类开发常用 HashSet集合 .TreeSet集合 Set集合的元素是不重复且无序 一.HashSet集合 API文档解释:此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持.它不保证 set 的迭代顺序:特别是它不保证该顺序恒久不变.此类允许使用null 元素. 此类为基本操作提供了稳定性能,注意,此实现不是同步的. 由上可

AGG第三十六课 gsv_text_outline 渲染环绕的字符

agg::rendering_buffer &rbuf = rbuf_window(); agg::pixfmt_bgr24 pixf(rbuf); typedef agg::renderer_base<agg::pixfmt_bgr24> renderer_base_type; renderer_base_type renb(pixf); typedef agg::renderer_scanline_bin_solid<renderer_base_type> render