1. Ajax

1.1 发送请求

创建XHR对象后,用的第一个方法是open(),open方法不会真正发送请求,而只是启动一个请求已备发送。

1
2
3
4
var xhr = new XMLHttpRequest();
xhr.open('get', 'example.php', false);
xhr.setRequestHeader('MyHeader', 'MyValue');
xhr.send(null);

1.2 监听请求状态

XHR的readyState属性,表示请求/响应过程的当前活动阶段。只要readyState属性的值由一个值变成另一个值,都会触发一次readystatechange事件。

  • 0: 未初始化,尚未调用open()方法
  • 1: 启动,已经调用open()方法,但尚未调用send()方法
  • 2: 发送,已经调用send()方法, 当尚未接收到响应
  • 3: 接收,已经接收到部分响应数据
  • 4: 完成,已经接收到全部响应数据,而且已经可以在客户端使用了。

1.3 读取返回内容

请求发出后,javascript代码会等到服务器响应之后再继续执行。在收到响应后,xhr.readyState == 4, 响应的数据会自动填充XHR对象的属性。相关属性

  • xhr.responseText: 作为响应主题被返回的文本
  • xhr.responseXML: 如果响应的内容类型是text/xml, application/xml,这个属性中将保存包含着响应数据的XML DOM文档
  • xhr.status: 响应的HTTP状态
  • xhr.statusText: HTTP状态的说明
1
2
3
4
5
6
7
8
9
xhr.onreadystatechange = function() {
if (xhr.readyState == 4){
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
alert(xhr.responseText)
} else {
alert("Request was unsuccessful:" + xhr.status )
}
}
}

1.4 取消请求

在接收到响应之前,还可以调用xhr.abort()方法来取消异步请求。

1.5 设置请求头

默认情况下,在发送XHR请求的同时,会发送下列头部信息。

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

有的浏览器允许开发人员重写默认的头部信息,有的浏览器不允许这样做。建议使用自定义的头部字段,否则有可能会影响服务器的响应。

1
2
3
4
5
6
// 设置请求头
xhr.setRequestHeader('myHeader', 'myValue');

// 获取响应头部信息
var myHeader = xhr.getResponseHeader('myHeader');
var allHeaders = xhr.getAllResponseHeaders(); // 返回多行文本内容

1.5 GET/POST

  • GET请求
    GET请求,将查询字符串参数追加到url的末尾,查询字符串的每个参数key,value都需使用encodeURIComponent()进行编码。所有键值对需由 & 分隔。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 对于';/?:@&=+$,#这些字符,在encodeURIComponent中统统会被编码。
    function addURLParam(url, key, value) {
    url += (url.indexOf("?") == -1 ? "?" : "&");
    url += encodeURIComponent(key) + '=' + encodeURILComponent(value);
    return url
    }

    var url = 'example.php';
    url = addURLParam(url, "name", "Nicholas");
    url = addURLParam(url, "book", "Marketing");

    xhr.open('get', url, false);
  • POST请求
    POST请求把数据作为请求的主体提交,数据通过xhr.send(data)传入。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // formData
    // Content-type: application/x-www-form-urlencoded
    var data = new FormData();
    data.append('name', 'Nicholas');
    var xhr = new XMLHttpRequest();
    xhr.open('post', 'postexample.php', false);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xhr.send(data);

    // json

1.6 进度事件

  • loadstart: 在接收到响应数据的第一字节时触发
  • error: 在请求发生错误时触发
  • abort: 调用abort()后触发
  • load: 在接收到完整的响应数据时触发
  • loadend: 通信完成,或触发error, abort, load事件后触发
  • progress: 在接收响应期间持续不断地触发。事件会收到一个event对象,其target属性是xhr对象,包含着三个额外的属性:

lengthComputable 进度信息是否可用的布尔值,
position 已经接收到的字节数,
totalSize 根据Content-length响应头部确定的预期字节数。

1
2
3
4
5
6
xhr.onprogress = function(event) {
var divStatus = document.getElementById('status');
if (event.lengthComputable) {
divStatus.innerHTML = "Received" + event.position + "of" + event.totalSize + "bytes";
}
}

1.7 跨域请求

1.7.1 CORS

CORS(Cross-Origin Resource Sharing, 跨源资源共享), 在发送请求时,附加一个Origin请求头,值为请求页面的源信息(协议,域名和端口),以便服务器根据这个头部信息来决定是否给予响应。

1
2
3
4
5
"Origin", "http://www.nczonline.net"

// Firefox, safari, chrome无需额外编写代码,就可以触发CORS行, IE要用XDR对象
// xhr.open()里传入绝对url
xhr.open("get", "http://www.somewhere-else.com/page/", true);

如果服务器认为这个请求可以接受,就在Access-Control-Allow-Origin头部中返回相同的源信息。注意:请求和响应都不包含cookie信息。

1
Access-Control-Allow-Origin: http://www.nczonline.net

CORS存在一些安全限制

  • 不能使用setRequestHeader()设置自定义头部
  • 不能发送和接收cookie
  • 调用getAllResponseHeaders()方法总会返回空字符串
1.7.2 Preflighted Request

CORS 通过一种叫做 Preflighted Requests 的透明服务器验证机制支持开发人员使用自定义的头部、 GET 或 POST 之外的方法,以及不同类型的主体内容。在使用下列高级选项来发送请求时,就会向服务 器发送一个 Preflight 请求。这种请求使用 OPTIONS 方法,发送下列头部。

1
2
3
Origin:与简单的请求相同。
Access-Control-Request-Method:请求自身使用的方法。
Access-Control-Request-Headers:(可选)自定义的头部信息,多个头部以逗号分隔。

服务器通过响应中返回如下头部

1
2
3
4
Access-Control-Allow-Origin:与简单的请求相同。
Access-Control-Allow-Methods:允许的方法,多个方法以逗号分隔。
Access-Control-Allow-Headers:允许的头部,多个头部以逗号分隔。
Access-Control-Max-Age:应该将这个 Preflight 请求缓存多长时间(以秒表示)。

1.7.3 withCredentials

默认情况下,跨源资源不提供凭据(cookie, HTTP认证,SSL证明)。通过将withCredential属性设为true, 可以指定某个请求应该发送凭证。如果服务端接受凭据的请求,会用下面的HTTP头部来响应。

1
Access-Control-Allow-Credentials: true

如果服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给 JavaScript(于是,responseText 中将是空字符串,status 的值为 0,而且会调用 onerror()事件处 理程序)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createCORSRequest(method, url){
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr){
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined"){
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
var request = createCORSRequest("get","http://www.somewhere-else.com/page/");
request.send();

1.7.4 其他跨域技术

2. 实现Axios

用Promise实现一个简单的axios

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Axios(obj){
const { url, method, data } = obj;
return new Promise((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) {
return
}

if (xhr.status === 200) {
resolve(xhr.response)
} else {
reject(xhr.statuText)
}
}
xhr.send(data);
})
}

使用的时候就可以用.then()

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
url: 'https://cnodejs.org/api/v1/topics',
method: 'GET',
data: ''
}

axios(obj)
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error)
})

参考资料