这是一篇万字长文,系统梳理了ajax相关的知识体系,几乎囊括了所有ajax的知识点.

原文:http://louiszhai.github.io/2016/11/02/ajax/

导读

Ajax 全称 Asynchronous JavaScript and XML,即异步JS与XML. 它最早在IE5中被使用,然后由Mozilla,Apple,Google推广开来. 典型的代表应用有 Outlook Web Access,以及 GMail. 现代网页中几乎无ajax不欢. 前后端分离也正是建立在ajax异步通信的基础之上.

浏览器为ajax做了什么

现代浏览器中,虽然几乎全部支持ajax,但它们的技术方案却分为两种:

① 标准浏览器通过XMLHttpRequest对象实现了ajax的功能. 只需要通过一行语句便可创建一个用于发送ajax请求的对象.

var xhr = new XMLHttpRequest();

② IE浏览器通过XMLHttpRequest或者ActiveXObject对象同样实现了ajax的功能.

MSXML

鉴于IE系列各种 "神级" 表现,我们先来看看IE浏览器风骚的走位.

IE下的使用环境略显复杂,IE7及更高版本浏览器可以直接使用BOM的 XMLHttpRequest 对象. MSDN传送门:Native XMLHTTPRequest object. IE6及更低版本浏览器只能使用ActiveXObject对象来创建 XMLHttpRequest 对象实例. 创建时需要指明一个类似"Microsoft.XMLHTTP"这样的ProgID. 而实际呢,windows系统环境下,以下ProgID都应该可以创建XMLHTTP对象:

Microsoft.XMLHTTP
Microsoft.XMLHTTP.1.0
Msxml2.ServerXMLHTTP
Msxml2.ServerXMLHTTP.3.0
Msxml2.ServerXMLHTTP.4.0
Msxml2.ServerXMLHTTP.5.0
Msxml2.ServerXMLHTTP.6.0
Msxml2.XMLHTTP
Msxml2.XMLHTTP.3.0
Msxml2.XMLHTTP.4.0
Msxml2.XMLHTTP.5.0
Msxml2.XMLHTTP.6.0

简言之,Microsoft.XMLHTTP 已经非常老了,主要用于提供对历史遗留版本的支持,不建议使用.对于 MSXML4,它已被 MSXML6 替代; 而 MSXML5 又是专门针对office办公场景,在没有安装 Microsoft Office 2003 及更高版本办公软件的情况下,MSXML5 未必可用. 相比之下,MSXML6 具有比 MSXML3 更稳定,更高性能,更安全的优势,同时它也提供了一些 MSXML3 中没有的功能,比如说 XSD schema. 唯一遗憾的是,MSXML6 只在 vista 系统及以上才是默认支持的; 而 MSXML3 在 Win2k SP4及以上系统就是可用的. 因此一般情况下,MSXML3 可以作为 MSXML6 的优雅降级方案,我们通过指定 PorgID 为 Msxml2.XMLHTTP 即可自动映射到 Msxml2.XMLHTTP.3.0. 如下所示:

var xhr = new ActiveXObject("Msxml2.XMLHTTP");// 即MSXML3,等同于如下语句
var xhr = new ActiveXObject("MSXML2.XMLHTTP.3.0");

MSDN有篇文章专门讲解了各个版本的MSXML. 传送门:Using the right version of MSXML in Internet Explorer.

亲测了 IE5,IE5.5,IE6,IE7,IE8,IE9,IE10,IE edge等浏览器,IE5及之后的浏览器均可以通过如下语句获取xhr对象:

// 即MSXML3
var xhr = new ActiveXObject("Microsoft.XMLHTTP");// 很老的api,虽然浏览器支持,功能可能不完善,故不建议使用

以上,思路已经很清晰了,下面给出个全兼容的方法.

全平台兼容的XMLHttpRequest对象

function getXHR(){
  var xhr = null;
  if(window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
  } else if (window.ActiveXObject) {
    try {
      xhr = new ActiveXObject("Msxml2.XMLHTTP");
    } catch (e) {
      try {
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
      } catch (e) { 
        alert("您的浏览器暂不支持Ajax!");
      }
    }
  }
  return xhr;
}

ajax有没有破坏js单线程机制

对于这个问题,我们先看下浏览器线程机制. 一般情况下,浏览器有如下四种线程:

  • GUI渲染线程
  • javascript引擎线程
  • 浏览器事件触发线程
  • HTTP请求线程

那么这么多线程,它们究竟是怎么同js引擎线程交互的呢?

通常,它们的线程间交互以事件的方式发生,通过事件回调的方式予以通知. 而事件回调,又是以先进先出的方式添加任务队列的末尾,等到js引擎空闲时,任务队列中排队的任务将会依次被执行. 这些事件回调包括 setTimeout,setInterval,click,ajax异步请求等回调.

浏览器中,js引擎线程会循环从任务队列中读取事件并且执行,这种运行机制称作Event Loop(事件循环).

对于一个ajax请求,js引擎首先生成XMLHttpRequest实例对象,open过后再调用send方法. 至此,所有的语句都是同步执行. 但从send方法内部开始,浏览器为将要发生的网络请求创建了新的http请求线程,这个线程独立于js引擎线程,于是网络请求异步被发送出去了. 另一方面,js引擎并不会等待 ajax 发起的http请求收到结果,而是直接顺序往下执行.

当ajax请求被服务器响应并且收到response后,浏览器事件触发线程捕获到了ajax的回调事件onreadystatechange(当然也可能触发onload,或者 onerror等等) . 该回调事件并没有被立即执行,而是被添加任务队列的末尾. 直到js引擎空闲了,248)">任务队列的任务才被捞出来,按照添加顺序,挨个执行,当然也包括刚刚append到队列末尾的onreadystatechange事件.

onreadystatechange事件内部,有可能对dom进行操作. 此时浏览器便会挂起js引擎线程,转而执行GUI渲染线程,进行UI重绘(repaint)或者回流(reflow). 当js引擎重新执行时,GUI渲染线程又会被挂起,GUI更新将被保存起来,等到js引擎空闲时立即被执行.

以上整个ajax请求过程中,有涉及到浏览器的4种线程. 其中除了GUI渲染线程js引擎线程是互斥的. 其他线程相互之间,都是可以并行执行的. 通过这样的一种方式,ajax并没有破坏js的单线程机制.

ajax与setTimeout排队问题

通常,ajax 和 setTimeout 的事件回调都被同等的对待,按照顺序自动的被添加代码:

ajax(url,method){
  var xhr = getXHR();
  xhr.onreadystatechange = function(){
      console.log('xhr.readyState:' + this.readyState);
  }
  xhr.onloadstart = function(){
      'onloadStart');
  }
  xhr.onload = function(){
      'onload');
  }
  xhr.open(method,url,true);
  xhr.setRequestHeader('Cache-Control',3600);
  xhr.send();
}
var timer = setTimeout(function(){
  'setTimeout');
},0);
ajax('https://user-gold-cdn.xitu.io/2017/3/15/c6eacd7c2f4307f34cd45e93885d1cb6.png','GET');
console.warn('这里的log并不是最先打印出来的.');

上述代码执行结果如下图:

setTimeout & ajax & 同步

由于ajax异步,setTimeout回调本应该最先被执行,然而实际上,一次ajax请求,并非所有的部分都是异步的,至少"readyState==1"的onreadystatechange回调以及onloadstart回调就是同步执行的. 因此它们的输出排在最前面.

XMLHttpRequest 属性解读

首先在Chrome console下创建一个 XMLHttpRequest 实例对象xhr. 如下所示:

XMLHttpRequest

@H_507_301@inherit