JS知识点梳理(10)-Ajax

Ajax(Asynchronous JavaScript and XML)技术核心是 XMLHttpRequest 对象,能够以异步的方式从服务器获取信息,从而可以不必刷新页面也能获取新数据。即使用 XHR 对象取得新数据,再通过 DOM 操作将新数据插入到页面中。

XHR 的用法

  • 创建 XHR 对象
    1
    var xhr = new XMLHttpRequest();
  • open(),初始化请求
    open(method,url,async)方法,接受 3 个参数:请求类型,请求的 URL,是否异步(默认为异步请求)
  • send(),发生请求
    send(data)发送请求,接受一个可选参数,作为请求主体发送的数据,如果不需要则必须传入null
  • abort(),取消异步请求
  • 处理响应
    在受到响应后,响应的数据会自动填充 XHR 对象的属性
    • responseText: 作为响应主体被返回的文本
    • responseXML: 如果响应的内容类型是”text/xml”,”application/xml”,则改属性保存着包含响应数据的 XML DOM 文档
    • status: 响应的 HTTP 状态
    • statusText: HTTP 状态说明

在发送异步请求,为了让 JavaScript 继续执行而不必等待响应,可为 XHR 对象添加readystatechange事件,来在请求/响应过程执行相应动作。XHR 对象的readyState属性,可表示请求的当前状态,可取值如下:

  • 0:UNSENT,为初始化,尚未调用open()方法
  • 1:OPENED, 启动,已经 调用open()方法,但未调用send()方法
  • 2: HEADERS_RECEIVED,发送,已经调用send()方法,但尚未接收到响应
  • 3: LOADING,接收,已经接收到部分响应数据
  • 4: DONE,完成,已经接收到全部响应数据

HTTP 头部信息

  • setRequestHeader(header, value): 设置 HTTP 请求头部,此方法必须在open()send()方法之间调用
  • getResponseHeader(name):返回指定的响应头信息
  • getAllResponseHeaders(): 返回所有响应头

XMLHttpRequest 2

FormData

FormData 类型为序列化表单以及创建与表单格式相同的数据提供了便利。

  • 创建 FormData 的 实例后,可直接将它传入 XHR 的send()方法
  • 另外,XHR 对象能够识别传入的数据类型是 FormData 的实例,并配置适当的头部信息,不需在手动配置头部信息(shr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"))
1
2
3
4
5
6
7
// 序列化表单
var data = new FormData(document.forms[0]);


// 创建表单格式数据
var data1 = new FormData();
data.append("name", "jiang jiang");

超时定时

XHR 对象的timeout属性,表示请求在等待响应多少毫秒之后终止。
在给 XHRtimeout设置一个数值后,如果在规定时间内浏览器还没有接收到响应,就会触发timeout事件.

1
2
3
4
5
6
// ...
xhr.timeout = 1000;
xhr.ontimeout = function() {
// ...
}
xhr.send(null);

overrideMimeType()

XHR 的overrideMimeType(mimeType),指定一个 MIME 类型用于替换服务器指定的类型,使服务器响应信息中传输的数据按照该指定 MIME 类型处理。
注意:如果服务器没有指定一个Content-Type头,XHR 默认 MIME 类型为"text/xml"。但如果接收的数据不是有效的 XML,就会出现格式不正确的错误。可用通过overrideMimeType()指定各种类型来 避免这种情况

进度事件

XHR 对象提供了各种在请求被处理期间发生的事件以供监听,包括定期进度通知,错误通知,完成通知等。具体进度事件如下:

  • loadstart: 在接收到响应数据的第一个字节时触发
  • progress: 在接收响应数据期间不断触发
  • error: 在请求发生错误时触发
  • abort: 因调用abort()方法而终止连接时触发
  • timeout: 请求超时时触发
  • load: 在接收到完成数据时触发
  • loadend: 通信终止(成功或失败)时触发,error,abort,timeout,load 任意事件后触发

progress 事件

progress 事件在浏览器接收数据期间周期性的触发,其事件处理程序会接收到的 event 对象,具有以下特殊属性

  • target: XHR 对象
  • lengthComputable: 进度信息是否可用的布尔值,如果为 false,意味着总字节数为止并且 total 的值为 0
  • loaded: 已经接收到的字节数
  • total: 预计的总字节数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<body>
<div class="controls">
<input class="xhr success" type="button" value="XHR (success)" />
<input class="xhr abort" type="button" value="XHR (abort)" />
<input class="xhr error" type="button" value="XHR (error)" />
</div>
<textarea readonly class="event-log" cols="80" rows="20"></textarea>
<img class="img" src="" alt="图片" width="400" height="500" />

<script>
const xhrButtonSuccess = document.querySelector(".xhr.success");
const xhrButtonAbort = document.querySelector(".xhr.abort");
const xhrButtonError = document.querySelector(".xhr.error");
const log = document.querySelector(".event-log");
const img = document.querySelector(".img");
function handleEvent(event) {
console.log("event>>>", event);
log.textContent =
log.textContent +
`${event.type}: lengthComputable: ${event.lengthComputable}, ${event.loaded} / ${event.total} bytes transferred\n`;
if (event.type === "load") {
let xhr = event.target;
console.log(xhr.getAllResponseHeaders());
img.src = xhr.responseURL;
}
}
function addListeners(xhr) {
xhr.addEventListener("loadstart", handleEvent);
xhr.addEventListener("progress", handleEvent);
xhr.addEventListener("load", handleEvent);
xhr.addEventListener("loaded", handleEvent);
xhr.addEventListener("error", handleEvent);
xhr.addEventListener("abort", handleEvent);
}
function runXHR(url) {
log.textContent = "";
const xhr = new XMLHttpRequest();
addListeners(xhr);
xhr.open("get", url);
xhr.send();
return xhr;
}
xhrButtonSuccess.addEventListener("click", () => {
runXHR(
"http://5b0988e595225.cdn.sohucs.com/images/20181012/f34af81a69b145ee83f35a462d8650bb.jpeg"
);
});
xhrButtonAbort.addEventListener("click", () => {
runXHR(
"http://5b0988e595225.cdn.sohucs.com/images/20181012/f34af81a69b145ee83f35a462d8650bb.jpeg"
).abort();
});
xhrButtonError.addEventListener("click", () => {
runXHR("https://somewhere.com/dont-exist");
});
</script>
</body>

跨域资源共享

默认情况下,XHR 对象 只能访问与它包含的页面位于同一域中资源。跨域是指一个域下的文档或脚本试图去请求另一个域下的资源。

广义的跨域

  • 资源跳转: a 链接,重定向,表单提交
  • 资源嵌入: <link>,<script>,<img>标签,css 样式中url(),@font-face()等文件外链
  • 脚本请求: ajax 请求

狭义的跨域

通常所说的跨域是狭义的,是由浏览器同源策略所限制的一类请求场景。同源策略是浏览器最核心的安全功能,所谓同源是指协议+域名+端口三者相同。即使两个不同的域名指向同一 IP 地址,也非同源。

CORS

CORS(Cross-Origin Resource Sharing,跨域资源共享),定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。其基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器沟通,从而决定请求或响应是成功,还是失败。具体做法如下:

  • 浏览器在发生请求时,会给 HTTP 头部附加一个额外的Origin头部,其中包含请求页面的源信息(协议+域名+端口),以便服务器根据该头部信息来决定是否响应
    1
    Origin: https://developer.mozilla.org
  • 服务器如果认为该请求可以接受,就会在Access-Control-Allow-Origin头部中回发相同的源信息,如果是公共资源,可以回发*
    1
    Access-Control-Allow-Origin: *
  • 如果是带凭证的请求,例如 带 cookie 请求时,需要将 XHR 对象withCredentials属性设置为 true,如果服务器接收带凭证的请求,会使用 HTTP 头部来响应Access-Control-Allow-Credentials: true.如果服务器的响应不含该头部,则浏览器不会把响应交给 JavaScript,于是 status 的值为 0,调用 onerror()事件处理程序

跨域解决方案

jsonp

原理:通过动态创建script,在请求时传参(一个回调函数)给后端,后端在返回时执行这个在前端定义的回调函数,回调函数传入的参数就要返回的数据
前端 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<input class="jsonp" type="button" value="JSONP" />
<textarea class="responseData" cols="30" rows="10"></textarea>
<script>
const responseData = document.querySelector(".responseData");
const jsonpButton = document.querySelector(".jsonp");
function handleCallback(res) {
responseData.textContent = JSON.stringify(res);
}
function runJSONP(url, callback) {
var script = document.createElement("script");
script.type = "text/javascript";
script.src = `${url}?callback=${callback}`;
document.head.appendChild(script);
}
jsonpButton.addEventListener("click", () => {
runJSONP("http://127.0.0.1:8080/", "handleCallback");
});
</script>
</body>

后端 node.代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var querystring = require("querystring");
var http = require("http");
var server = http.createServer();

server.on("request", function (req, res) {
var params = querystring.parse(req.url.split("?")[1]);
var fn = params.callback;
console.log(params);

res.writeHead(200, { "Content-Type": "text/javascript" });
var response = { status: true, user: "admin" };
res.write(`${fn}(${JSON.stringify(response)})`);
res.end();
});
server.listen("8080");
console.log("server is running at port 8080....");

postMessage 跨域

postMessage是 window 属性之一,可安全实现跨域通信,能解决以下问题:

  • 页面和其打开的新窗口的数据传递
  • 多窗口之间的息传递
  • 页面与嵌套的iframe之间消息传递

基本思路:一个窗口可以获得对另一个窗口的引用,然后在发送窗口上调用otherWindow.postMessage()方法分发一个MessageEvent消息,接收消息的窗口可监听该事件(message),并可自由处理此事件。

  • 分发消息
    otherWindow.postMessage(message, targetOrigin)方法,分发一个消息给targetOrigin窗口,是通过消息事件对象暴露给接收框口的。
    • targetWindow: 其他窗口的一个引用,例如,iframe 的contentWindow属性,window.open()返回的窗口对象,命名过或数值索引的winsdow.frames
    • message: 要发送给接收窗口的数据,它会被序列化,无需自己序列化
    • targetOrigin: 指定那些窗口能接收到消息事件,其值可以是字符串"*",或者一个 URL
  • 接收消息
    window.addEventListener("message",receiveMessage),通过监听事件来接收消息,事件对象相关属性有:
    • data: 从其他 window 中传递过来的消息内容
    • origin: 调用postMessage时发送方窗口的 origin.
    • source: 对发送消息的窗口对象的引用