详解 Ajax

最后更新:
阅读次数:

现在流行用 Fetch API,而不是 Ajax。(2017.3.5)

简介

AJAX [wiki] 即 Asynchronous JavaScript and XML(异步的 JavaScript 与 XML 技术),指的是一套综合了多项技术、创建更好更快以及交互性更强的网页应用的浏览器端网页开发技术。

Ajax 不是指一种单一的技术,而是有机地利用了一系列相关的技术。虽然其名称包含 XML,但实际上数据格式可以由 JSON 代替,进一步减少数据量,形成所谓的 AJAJ

Ajax 使用到了以下技术:

  • XHTML+CSS:标准化呈现数据
  • XMLHttpRequest对象:用来与 Web 服务器进行异步数据通信
  • 文档对象模型 (DOM):用来与所显示的信息动态进行交互并显示该信息
  • XML和XSLT 等:用于数据交换和处理
  • JavaScript:用于绑定数据请求和信息显示

基本工作原理

Ajax 的工作原理相当于在用户和服务器之间加了一个中间层( AJAX引擎 ),使用户的操作与服务器响应异步化。并不是所有的用户请求都提交给服务器,像—些数据验证和数据处理等都交给 Ajax 引擎自己来做, 只有确定需要从服务器读取新数据时再由 Ajax 引擎代为向服务器提交请求。

传统的网页(不使用 AJAX)如果需要更新网页内容,必须重新载入整个网页页面,并且期间用户无法与 Web 应用程序进行交互。

no-ajax

使用了 ajax 后,就可以在不重新加载整个网页的情况下,对网页的某部分内容进行更新,并且不会中断用户与 Web 应用程序的交互。

use-ajax

XMLHttpRequest 对象

Ajax 的核心是 XMLHttpRequest 对象

XMLHttpRequest 是一个 JavaScript 对象,它最初由微软设计,随后被 Mozilla、Apple 和 Google 采纳。 如今,该对象已经被 W3C 组织标准化。通过它,你可以很容易的 取回一个URL上的资源数据 (它可以取回所有类型的数据资源,并不局限于 XML)。

创建一个 XMLHttpRequest 实例:

var request = new XMLHttpRequest();
  • XMLHttpRequest 方法概述

    • void abort():如果请求已经被发送,则立刻中止请求
    • DOMString getAllResponseHeaders():返回所有响应头信息(响应头名和值),若未响应,则返回 null
    • DOMString getResponseHeader(DOMString header):返回指定的响应头的值,若未响应,则返回 null
    • void open(method,url,async,user,password):初始化一个请求
      • DOMString method:请求所使用的 HTTP 方法,如 GET POST PUT
      • DOMString url:该请求所要访问的 URL
      • optional boolean async:是否执行异步操作,默认为 true
      • optional DOMString user:用户名,默认参数为空 string
      • optional DOMString password:密码,默认参数为空 string
    • void setRequestHeader(DOMString header, DOMString value):给指定的 HTTP 请求头赋值
    • void sent(...):发送请求(详细用法见 XMLHttpRequest [MDN])
  • XMLHttpRequest 属性

    • onreadystatechange:它是一个 Javascript 函数对象当 readyState 属性改变时会调用它
    • readyState:请求的状态,共 5 种状态,有 5 个可取值
      • 0UNSENT (未打开),表示 open() 方法还未被调用
      • 1OPENED (未发送),表示 send() 方法还未被调用
      • 2:_HEADERSRECEIVED (已获取响应头),表示 send() 方法已经被调用,响应头和响应状态已经返回
      • 3LOADING (正在下载响应体),表示响应体正在下载中, responseText 中已经获取了部分数据
      • 4DONE (请求完成),表示整个请求过程已经完毕
    • responseText:返回此次响应请求的文本数据,数据类型为 DOMString
    • responseXML:返回此次响应的 XML 文档对象,数据类型为 Document
    • status:返回该请求的响应状态码,(例如, 状态码 200 表示一个成功的请求)
    • statusText:返回请求的响应状态信息(类型为 DOMString),包含一个状态码和原因短语 (例如 “200 OK”)
    • withCredentials:在使用 cookie 跨域通信时,需要将这个属性设置为 true,否则通信失败

XMLHttpRequest 2 级(2012 年正式标准化)

XMLHttpRequest 2 级相对老版本的优势

  • 可以设置 HTTP 请求的时间限制。
  • 可以使用 FormData 对象管理表单数据。
  • 可以上传文件。
  • 可以请求不同域名下的数据(跨域请求)。
  • 可以获取服务器端的二进制数据。
  • 可以获得数据传输的进度信息。
  • 新添加的属性

    • timeout: 表示请求在等待响应多少毫秒之后就终止。如果在规定时间内接收到响应,那么就会触发 timeout 事件
    • responseType 返回响应内容的类型。可以手动更改它的值,从而方便接收相应类型的内容。常见类型有:arraybuffer,blob,document,json,text(默认值)
    • response: 返回响应内容的主体
  • FormData 类型

利用 FormData 对象,我们可以通过 JavaScript 用一些键值对来模拟一系列表单控件,我们还可以使用 XMLHttpRequest 的 send() 方法来异步的提交这个模拟的表单。比起普通的 ajax,使用 FormData 的最大优点就是我们可以异步上传一个二进制文件。

// 不要设置
setRequestHeader("Content-type", "application/x-www-form-urlencoded");
var myForm = new FormData([form]);
// form 可选值,一个HTML表单元素,可以包含任何形式的表单控件
// 若有上面的 form 值,myForm 则自动获得页面表单的值

//给当前FormData对象添加一个键/值对
myForm.append("name", "percy");

// 最后通过 XMLHttpRequest 的实例来发送它
xhr.send(myForm);

Using FormData To Send Forms With XHR As Key/Value Pairs

  • 接收二进制数据
var xhr = new XMLHttpRequest();
xhr.open("GET", "/path/to/image.png");
xhr.responseType = "blob"; // 设置成相应的类型

// 接收数据
var blob = new Blob([xhr.response], { type: "image/png" });

更多内容参见 Sending and Receiving Binary Data

  • 进度事件(Progress Events)

Progress Events 定义了客户端服务器通信相关的事件。

6 个进度事件

  • loadstart: 在接收到响应数据的第一个字节时触发
  • progress: 在接收响应数据期间持续不断地触发
  • error: 在请求发生错误时触发
  • abort: 在因为调用 abort() 方法终止连接时触发
  • load: 在接收到完整的响应数据时触发
  • loadend: 在通信完成或者在触发 error、abort 或 load 事件后触发
  • load 事件

load 事件旨在替换 readystatechange 事件,load 事件的事件对象的 target 属性指向 XMLHttpRequest 对象实例。

即使使用 load 事件替换 readystatechange 事件,也要检测响应状态,即检测 status 属性。

var xhr = new XMLHttpRequest();

xhr.onload = function() {
if (xhr.status === 200) {
alert(xhr.responseText);
}
};
  • progress 事件的事件对象
    • target: 指向 XMLHttpRequest 对象
    • lengthComputable: 一个布尔值,表明总共需要完成的工作量和已经完成的工作是否可以被底层所计算到
    • loaded: 表示底层进程已经执行的工作量
    • total: 表示底层进程正在执行的工作总量

为了保证正常执行,必须在调用 open() 方法之前添加 onprogress 事件处理程序。

progress 事件用来返回客户端与服务端之间的进度信息。进度信息又有两种情况,上传和下载。下载时的 progress 事件属于 XMLHttpRequest 对象,而上传时的 progress 事件则属于 XMLHttpRequest.upload 对象。

let xhr = new XMLHttpRequest();

// 用来读取服务器向客户端发送的数据的进度信息(下载)
xhr.onprogress = function() {};

// 用来读取客户端向服务器发送的数据的进度信息(上传)
xhr.upload.onprogress = function() {};

所以呢,我们可以利用上面的信息来监控客户端向服务器上传文件的进度信息,并将进度信息显示在页面上。

本文内容仅供参考,一切以官方文档为标准:XMLHttpRequest Living Standard

优点与不足

  • 优点
    • 最大优点:在不更新整个页面的前提下更新部分网页的数据。
    • 增强用户体验,使 web应用 更接近于 native应用
    • Ajax按需操作数据,可以最大程度的减少冗余请求,从而减轻服务器和带宽的负担,并且把一些服务器负担的工作转嫁到客户端。

Ajax下面的优点都是基于它的最大优点衍生出来的,有木有 ^_^

  • 不足
    • Ajax 破坏了浏览器的后退按钮功能。在 Ajax 动态更新页面的情况下,用户无法回到前一个页面状态,这是因为浏览器仅能记下历史记录中的静态页面。
      解决方案:在用户单击后退按钮访问历史记录时,通过建立或使用一个隐藏的 IFRAME 来重现页面上的变更。【Gmail 上浏览器的后退键就可以正常使用,它就是用的这种方法】
    • 进行 Ajax 开发时,因为 网络延迟,服务器响应过慢,若不给予用户明确的回应,这样会使用户感到厌烦。
      解决方案使用一个可视化的组件 来告诉用户系统正在进行后台操作并且正在读取数据和内容。【细心的 guy 一定注意到过这种情况】
    • 搜索引擎对 Ajax 很不友好。因为搜索引擎在抓取页面的时候会屏蔽掉所有 javascript 代码。
      解决方法

网络延迟即用户发出请求到服务器发出响应之间的间隔

一个利用 Ajax 的简单例子

这个例子是用 Ajax 技术从服务器的一个文件(profile.json)中取回数据,并显示在用户界面。

/*    profile.json    */

{
"name": "percy",
"age": 21,
"phone_number": "12345678",
"skills": ["HTML", "CSS", "JavaScript", "Ajax"]
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Ajax example</title>
<script>
function createDiv() {
var div = document.createElement("div");
div.id = "profile";
document.body.appendChild(div);
}

function showProfile(url) {
createDiv();

var profileDom = document.getElementById("profile");

// 创建 ajax 核心对象的实例
var request = new XMLHttpRequest();

request.onreadystatechange = function() {
// 请求状态发生变化时,函数被回调
if (request.readyState === 4) {
// 判断请求成功完成
if (request.status === 200) {
// 判断响应结果

// 处理响应的数据
var profile = JSON.parse(request.responseText);
var temp = "";
for (var value in profile) {
temp += value + " :" + profile[value] + "<br/>";
}
profileDom.innerHTML = temp;

// add some styles
profileDom.style.fontSize = "25px";
profileDom.style.color = "blue";
profileDom.style.lineHeight = "1.5";
} else {
// 响应失败,根据响应码判断失败原因
profileDom.innerHTML = "Error: " + request.status;
}
} else {
// HTTP请求还在继续...
}
};

request.open("GET", url, true); // 初始化一个请求
request.send(); // 发送请求

alert("请求已发送,请等待响应...");
}
</script>
</head>

<body>
<button onclick="showProfile('profile.json')">个人简介</button>
</body>
</html>

注意事项

默认情况下,服务器对 POST 请求和提交 Web 表单的请求不会一视同仁。因此,服务器必须有程序来读取发送过来的原始数据,并从中解析出有用的部分。

不过,我们可以使用 XMLHttpRequest 来模仿表单提交时的内容类型:首先将 Content-Type 头部信息设置为 application/x-www-form-urlencoded,也就是表单提交时的内容类型,其次是以适当的格式创建一个字符串。

request.open("POST", url, true); // 初始化一个请求
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
request.send(str); // 发送请求

参考资料