学习参考文章:再也不学 AJAX 了!(二)使用 AJAX,自用!

Ajax 简介

Ajax 即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML)。

Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。

ajax 并非一种新的技术,而是几种原有技术的结合体。它由下列技术组合而成。

  • 使用 CSS 和 XHTML 来表示。
  • 使用 DOM 模型来交互和动态显示。
  • 使用 XMLHttpRequest 来和服务器进行异步通信。
  • 使用 javascript 来绑定和调用。

Ajax 优缺点

Ajax 的优点

  • 页面无刷新更新数据
  • 异步与服务器通信,不需要打断用户的操作
  • 按需取数据,减少服务器的负担
  • AJAX 基于标准化的并被广泛支持的技术,不需要下载浏览器插件或者小程序,只需要客户允许 JavaScript 在浏览器上执行
  • 浏览器的内容和服务端代码进行分离。页面的内容全部由 JAVAScript 来控制,服务端负责逻辑的校验和从数据库中拿数据。

Ajax 缺点

  • 安全问题:将服务端的方法暴露出来,黑客可利用这一点进行攻击
  • AjAX 干掉了 Back 后退和加入收藏书签功能,即对浏览器机制的破坏。
  • Ajax 的无刷新重载,由于页面的变化没有刷新重载那么明显,所以容易给用户带来困扰——用户不太清楚现在的数据是新的还是已经更新过的;通常的解决方案是,使用一个可视化的组件来告诉用户系统正在进行后台操作并且正在读取数据和内容

XMLHttpRequest 对象

简介

为了使用 JavaScript 向服务器发出 HTTP 请求,需要一个提供此功能的类的实例。这就是 XMLHttpRequest 的由来。显而易见 XMLHttpRequest 类是 AJAX 技术的核心。

XMLHttpRequest 只是一个 JavaScript 对象,确切的说,是一个构造函数。它是由浏览器提供的,而不是 JavaScript 原生的,除此之外,它有属性,有方法,需要通过 new 关键字进行实例化

const xhr = new XMLHttpRequest()
1

方法

  • .open():初始化一个请求。该方法只能 JavaScript 代码中使用,若要在 native code 中初始化请求,请使用 openRequest();
  • .setRequestHeader():设置 HTTP 请求头的值。您必须在 open()之后、send()之前调用 setRequestHeader()这个方法;
  • .send():发送请求。如果请求是异步的(默认),那么该方法将在请求发送后立即返回;
  • .getResponseHeader():返回包含指定响应头的字符串,如果响应尚未收到或响应中不存在该报头,则返回 null。;
  • .getAllResponseHeader():如果请求已经被发送,则立刻中止请求;
  • .abort():如果请求已经被发送,则立刻中止请求;
  • .overrideMimeType(): 重写由服务器返回的 MIME type。

属性

  • .response: 返回 ArrayBuffer、Blob、Document、DOMString},具体是哪种类型取决于 XMLHttpRequest.responseType 的值。其中包含响应体 body。
  • .responseText:返回一个 DOMString},该 DOMString}包含对请求的响应,如果请求未成功或尚未发送,则返回 null。;
  • .responseXML:如果响应的内容类型时 text/xml 或 application/xml,该属性将保存包含着相应数据的 XML DOM 文档;
  • .status:响应的 HTTP 状态;
  • .statusText:HTTP 状态的说明;
  • .readyState:返回 一个 unsigned short 即无符号短整型,请求的状态码。
  • .onreadystatechange: 当 readyState 属性发生变化时调用的 EventHandler;
  • ......

初始化请求

XMLHttpRequest.open() 方法初始化一个请求。该方法有三个参数.

要使用的 HTTP 方法

详细分类

  • GET
    • 作用:标识该操作是用于获取服务端的资源,可以理解为 select 操作
    • 特点:GET 方式提交的数据最多只能是 2KB 字节;数据通过 browser 地址栏进行传递,用户信息会暴露在 browser 地址了,不安全
  • POST
    • 作用:用于向服务端新增数据,常用于提交表单。可以理解为 insert 操作
    • 特点:理论上 POST 方式,对提交的数据大小没有限制;数据通过 http 协议的 body 体中进行传递,不会暴露用户信息,相对安全
  • PUT
    • 作用:用于向服务端更新数据,与 post 的使用很相似。可以理解为 update 操作。
  • Delete
    • 作用:标识该操作是:用于删除服务端的资源,可以理解为 delete 操作
  • HEAD
    • HEAD 和 GET 本质是一样的,区别在于 HEAD 不含有呈现数据,而仅仅是 HTTP 头信息。作用:只请求页面首部,响应报文中没有实体的主体部分(没有 body 体)
  • Options
    • 它用于获取当前 URL 所支持的方法。若请求成功,则它会在 HTTP 头中包含一个名为“Allow”的头,值是所支持的方法,如“GET, POST”。跨域 cors 的时候会遇到.
  • TRACE
    • 是 HTTP(超文本传输)协议定义的一种协议调试方法,该方法使得服务器原样返回任何客户端请求的内容。启用 TRACE 方法存在安全风险!

平时用 get 和 post 基本能实现所有功能.

GET 请求

查询参数是指一个由?号起始,由&符号分割的包含相应键值对的字符串。用来告知浏览器所要查询的特定资源。

const query = 'example.php?name=tom&age=24'; // "?name=tom&age=24"即是一个查询参数
1

需要注意的是,查询字符串中每个参数的名和值都必须使用encodeURIComponent()进行编码(这是因为 URL 中有些字符会引起歧义,例如“&”)。

POST 请求

需要发送的数据会作为.send()方法的参数最终被发往服务器,该数据可以是任意大小,任意类型。

这里需要注意以下两点:

  • .send()方法的参数是不可为空的,对于不需要发送任何数据的 GET 请求,也需要在调用.send()方法时,向其传入 null 值;
  • 表单提交以及发送 POST 请求,服务器对待这两种方式并不一视同仁,这意味着服务器需要有相应的代码专门处理 POST 请求发送来的原始数据。

请求 URL 地址

这里需要注意若使用相对路径,请求 URL 是相对于执行代码的当前页面。

同步请求或异步请求

“同步”意味着一旦请求发出,任何后续的 JavaScript 代码不会再执行,“异步”则是当请求发出后,后续的 JavaScript 代码会继续执行,当请求成功后,会调用相应的回调函数。

默认是异步,既是 true

设置请求头

默认情况下,当发送 AJAX 请求时,会附带以下头部信息:

  • Accept:浏览器能够处理的内容类型;
  • Accept-Charset: 浏览器能够显示的字符集;
  • Accept-Encoding:浏览器能够处理的压缩编码;
  • Accept-Language:浏览器当前设置的语言;
  • Connection:浏览器与服务器之间连接的类型;
  • Cookie:当前页面设置的任何 Cookie;
  • Host:发出请求的页面所在的域;
  • Referer:发出请求的页面 URI;
  • User-Agent:浏览器的用户代理字符串;

部分浏览器不允许使用.setRequestHeader()方法重写默认请求头信息,因此自定义请求头信息是更加安全的方法:

// 自定义请求头
xhr.setRequestHeader('myHeader', 'MyValue');
1
2

发送请求

get

const xhr = new XMLHttpRequest();
xhr.open('get', 'example.php');
xhr.setRequestHeader('myHeader', 'goodHeader');
xhr.send(null);
1
2
3
4

post

const xhr = new XMLHttpRequest();
xhr.open('post', 'example.php');
xhr.setRequestHeader('myHeader', 'bestHeader');
xhr.send(some_data);
1
2
3
4

处理响应

处理同步请求

const xhr = new XMLHttpRequest();
xhr.open('get', 'example.php', false);
xhr.setRequestHeader('myHeader', 'goodHeader');
xhr.send(null);
// 由于是同步的AJAX请求,因此只有当服务器响应后才会继续执行下面的代码
// 因此xhr.status的值一定不为默认值
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
  alert(xhr.responseText);
} else {
  alert('Request was unsuccessful: ' + xhr.status);
}
1
2
3
4
5
6
7
8
9
10
11

处理异步请求

为 XMLHTTPRequest 实例添加 onreadystatechange 事件处理程序(也可以直接使用 addEventListener()方法,但是 IE8 不支持该方法)。

xhr 实例的 readystatechange 事件会监听 xhr.readyState 属性的变化,其可取的值如下:

  • 0:未初始化 -- 尚未调用.open()方法;
  • 1:启动 -- 已经调用.open()方法,但尚未调用.send()方法;
  • 2:发送 -- 已经调用.send()方法,但尚未接收到响应;
  • 3:接收 -- 已经接收到部分响应数据;
  • 4:完成 -- 已经接收到全部响应数据,而且已经可以在客户端使用了;
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => {
  if (xhr.readystate == 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
      alert(xhr.responseText);
    } else {
      alert('Request was unsuccessful: ' + xhr.status);
    }
  }
};
xhr.open('get', 'example.php', true);
xhr.send(null);
1
2
3
4
5
6
7
8
9
10
11
12

注意:为了确保跨浏览器的兼容性,必须要在调用.open()方法之前指定事件处理程序.

取消异步请求

如果该请求已被发出,XMLHttpRequest.abort() 方法将终止该请求。当一个请求被终止,它的 readyState 属性将被置为 0( UNSENT ),当终止 AJAX 请求后,你需要手动对 XHR 对象实例进行解绑以释放内存空间。

var xhr = new XMLHttpRequest(),
  method = 'GET',
  url = 'https://developer.mozilla.org/';
xhr.open(method, url, true);

xhr.send();

xhr.abort();

xhr = null;
1
2
3
4
5
6
7
8
9
10

下面学习参考自:XMLHttpRequest Level 2 使用指南

XMLHttpRequest Level 2

新版本的 XMLHttpRequest 对象,针对老版本的缺点,做出了大幅改进。

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

老版本缺点:

  • 只支持文本数据的传送,无法用来读取和上传二进制文件。
  • 传送和接收数据时,没有进度信息,只能提示有没有完成。
  • 受到"同域限制"(Same Origin Policy),只能向同一域名的服务器。

请求的时限

新版本的 XMLHttpRequest 对象,增加了 timeout 属性,可以设置 HTTP 请求的时限。

xhr.timeout = 3000; //3000毫秒
1

与之配套的还有一个 timeout 事件,用来指定回调函数。

xhr.ontimeout = function(event){
  alert('请求超时!');
}
```js
注意,当请求终止时,会调用ontimeout事件处理程序,此时xhr的readyState属性的值可能已变为4,这意味着会继续调用onreadystatechange事件处理程序,但是当超时中止请求后再访问xhr的status属性会使浏览器抛出一个错误,因此需要将检查status属性的语句放入try-catch语句中。
## FormData对象
HTML 5新增了一个FormData对象,可以模拟表单。
```js
var formData = new FormData();
formData.append('username', '张三');
formData.append('id', 123456);
xhr.send(formData);
1
2
3
4
5
6
7
8
9
10
11
12

上传文件

新版 XMLHttpRequest 对象,不仅可以发送文本信息,还可以上传文件。

假定 files 是一个"选择文件"的表单元素(input[type="file"]),我们将它装入 FormData 对象。

var formData = new FormData();
for (var i = 0; i < files.length; i++) {
  formData.append('files[]', files[i]);
}
xhr.send(formData);
1
2
3
4
5

跨域资源共享(CORS)

新版本的 XMLHttpRequest 对象,可以向不同域名的服务器发出 HTTP 请求。这叫做"跨域资源共享"(Cross-origin resource sharing,简称 CORS)。

使用"跨域资源共享"的前提,是浏览器必须支持这个功能,而且服务器端必须同意这种"跨域"。如果能够满足上面的条件,则代码的写法与不跨域的请求完全一样。

xhr.open('GET', 'http://other.server/and/path/to/script');
1

目前,除了 IE 8 和 IE 9,主流浏览器都支持 CORS

进度事件

前共有 6 个进度事件,他们会随数据传输进展被顺序触发(除了 error,abort 事件)

新版本的 XMLHttpRequest 对象,传送数据的时候,有一个 progress 事件,用来返回进度信息。

它分成上传和下载两种情况。下载的 progress 事件属于 XMLHttpRequest 对象,上传的 progress 事件属于 XMLHttpRequest.upload 对象。

xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;
1
2

然后,在回调函数里面,使用这个事件的一些属性。

function updateProgress(event) {
  if (event.lengthComputable) {
    var percentComplete = event.loaded / event.total;
  }
}
1
2
3
4
5

上面的代码中,event.total 是需要传输的总字节,event.loaded 是已经传输的字节。如果 event.lengthComputable 不为真,则 event.total 等于 0。

不过还要记得注意,需要在.open()方法前调用 onprogress 事件处理程序。

与 progress 事件相关的,还有其他五个事件,可以分别指定回调函数:

  • load 事件:传输成功完成。
    • 该事件帮助我们节省了 readstatechange 事件,我们不必在 XHR 对象实例上绑定该事件监听函数以追踪实例上 readState 属性的变化,而是可以直接使用以下代码:
const xhr = new XMLHttpRequest();
xhr.onload = () => {
  if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
    alert(xhr.responseText);
  } else {
    alert('Something wrong!');
  }
};
xhr.open('get', 'example.php', true);
xhr.send(null);
1
2
3
4
5
6
7
8
9
10
  • abort 事件:传输被用户取消。
  • error 事件:传输中出现错误。
  • loadstart 事件:传输开始。
  • loadEnd 事件:传输结束,但是不知道成功还是失败。

接收二进制数据

方法 A:改写 MIMEType

改写数据的 MIMEType,将服务器返回的二进制数据伪装成文本数据,并且告诉浏览器这是用户自定义的字符集。

xhr.overrideMimeType('text/plain; charset=x-user-defined');
1

然后,用 responseText 属性接收服务器返回的二进制数据。

var binStr = xhr.responseText;
1

还必须再一个个字节地还原成二进制数据。

for (var i = 0, len = binStr.length; i < len; ++i) {
  var c = binStr.charCodeAt(i);
  var byte = c & 0xff;
}
1
2
3
4

最后一行的位运算"c & 0xff",表示在每个字符的两个字节之中,只保留后一个字节,将前一个字节扔掉。原因是浏览器解读字符的时候,会把字符自动解读成 Unicode 的 0xF700-0xF7ff 区段。

方法 B:responseType 属性

使用新增的 responseType 属性。如果服务器返回文本数据,这个属性的值是"TEXT",这是默认值。较新的浏览器还支持其他值,也就是说,可以接收其他格式的数据。

你可以把 responseType 设为 blob,表示服务器传回的是二进制对象。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png');
xhr.responseType = 'blob';
1
2
3

接收数据的时候,用浏览器自带的 Blob 对象即可。

var blob = new Blob([xhr.response], { type: 'image/png' });
1

你还可以将 responseType 设为 arraybuffer,把二进制数据装在一个数组里。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/path/to/image.png');
xhr.responseType = 'arraybuffer';
1
2
3

接收数据的时候,需要遍历这个数组。

var arrayBuffer = xhr.response;
if (arrayBuffer) {
  var byteArray = new Uint8Array(arrayBuffer);
  for (var i = 0; i < byteArray.byteLength; i++) {
    // do something
  }
}
1
2
3
4
5
6
7

TOC