JavaScript高级程序设计(4)
第24章 网络请求与远程资源
> XHR 对象的 API 被普遍认为比较难用,而 Fetch API 自从诞生以后就迅速成为了 XHR 更现代的替代标准。Fetch API 支持期约(promise)和服务线程(service worker),已经成为极其强大的 Web 开发工具。实际开发中,应该尽可能使用 fetch()。 ### 24.1 XMLHttpRequest对象 711 ##### 24.1.1 使用 XHR - > ```javascript > //创建XHR对象 > let xhr = new XMLHttpRequest(); > > 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); > } > } > }; > > //open()方法接收 3 个参数:请求类型("get"、"post"等)、请求 URL,以及表示请求是否异步的布尔值。 > //调用 open()不会实际发送请求,只是为发送请求做好准备。 > xhr.open("get", "example.txt", true); > > //send()方法接收一个参数,是作为请求体发送的数据。如果不需要发送请求体,则必须传 null。之后请求就会发送到服务器。 > xhr.send(null); > ``` > > - xhr.readyState:表示当前处在请求/响应过程的哪个阶段。 > - 0:未初始化(Uninitialized)。尚未调用 open()方法。 > - 1:已打开(Open)。已调用 open()方法,尚未调用 send()方法。 > - 2:已发送(Sent)。已调用 send()方法,尚未收到响应。 > - 3:接收中(Receiving)。已经收到部分响应。 > - 4:完成(Complete)。已经收到所有响应,可以使用了。 > - xhr.responseText:作为响应体返回的文本。 > - xhr.responseXML:如果响应的内容类型是"text/xml"或"application/xml",那就是包含响应数据的 XML DOM 文档。 > - status:响应的 HTTP 状态。 > - statusText:响应的 HTTP 状态描述。 > - xhr.abort():在收到响应之前如果想取消异步请求,可以调用 abort()方法, 调用这个方法后,XHR 对象会停止触发事件,并阻止访问这个对象上任何与响应相关的属性。中断请求后,应该取消对 XHR 对象的引用。由于内存问题,不推荐重用 XHR 对象。 ##### 24.1.2 HTTP 头部 - >setRequestHeader()方法:设置请求头部。 > >getResponseHeader()方法:从 XHR 对象获取响应头部。 > >```javascript >xhr.open("get", "example.php", true); >xhr.setRequestHeader("MyHeader", "MyValue"); >xhr.send(null); > >let myHeader = xhr.getResponseHeader("MyHeader"); >let allHeaders xhr.getAllResponseHeaders(); >``` ##### 24.1.3 GET 请求 - > 发送 GET 请求最常见的一个错误是查询字符串格式不对。查询字符串中的每个名和值都必须使用encodeURIComponent()编码,所有名/值对必须以和号(&)分隔。 > > ```javascript > xhr.open("get", "example.php?name1=value1&name2=value2", true); > > //可以使用以下函数将查询字符串参数添加到现有的 URL 末尾: > function addURLParam(url, name, value) { > url += (url.indexOf("?") == -1 ? "?" : "&"); > url += encodeURIComponent(name) + "=" + encodeURIComponent(value); > return url; > } > > //可以使用addURLParam()函数构建URL,保证通过 XHR 发送请求的 URL 格式正确 > let url = "example.php"; > // 添加参数 > url = addURLParam(url, "name", "Nicholas"); > url = addURLParam(url, "book", "Professional JavaScript"); > // 初始化请求 > xhr.open("get", url, false); > ``` ##### 24.1.4 POST 请求 - > ```javascript > //使用 XHR 模拟表单提交 > xhr.open("post", "postexample.php", true); > xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); > let form = document.getElementById("user-info"); > xhr.send(serialize(form)); > ``` - > 注意 POST 请求相比 GET 请求要占用更多资源。从性能方面说,发送相同数量的数据,GET 请求比 POST 请求要快两倍。 ##### 24.1.5 XMLHttpRequest Level 2 1. **FormData** 类型 > ```javascript > //创建了一个 FormData 对象,并填充了一些数据: > let data = new FormData(); > data.append("name", "Nicholas"); > > //可以将表单中的数据作为键/值对填充进去 > let data = new FormData(document.forms[0]); > > //有了 FormData 实例,可以像下面这样直接传给 XHR 对象的 send()方法: > xhr.open("post", "postexample.php", true); > let form = document.getElementById("user-info"); > xhr.send(new FormData(form)); > ``` 2. 超时 > ```javascript > xhr.open("get", "timeout.php", true); > xhr.timeout = 1000; // 设置 1 秒超时 > xhr.ontimeout = function() { > alert("Request did not return in a second."); > }; > xhr.send(null); > ``` 3. **overrideMimeType()**方法:重写 XHR 响应的 MIME 类型 > ```javascript > let xhr = new XMLHttpRequest(); > xhr.open("get", "text.php", true); > xhr.overrideMimeType("text/xml"); //制让 XHR 把响应当成 XML 而不是纯文本来处理 > xhr.send(null); > ``` ### 24.2 进度事件 718 > 有以下 6 个进度相关的事件: > > - loadstart:在接收到响应的第一个字节时触发。 > - progress:在接收响应期间反复触发。 > - error:在请求出错时触发。 > - abort:在调用 abort()终止连接时触发。 > - load:在成功接收完响应时触发。用于替代readystatechange 事件。这样就不用检查 readyState 属性了。 > - loadend:在通信完成时,且在 error、abort 或 load 之后触发。 > > 每次请求都会首先触发 loadstart 事件,之后是一个或多个 progress 事件,接着是 error、abort或 load 中的一个,最后以 loadend 事件结束。 ```javascript let xhr = new XMLHttpRequest(); xhr.onload = function(event) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } }; //Mozilla 在 XHR 对象上另一个创新是 progress 事件,在浏览器接收数据期间,这个事件会反复触发。每次触发时,onprogress 事件处理程序都会收到 event 对象,其 target 属性是 XHR 对象,且包含 3 个额外属性:lengthComputable、position 和 totalSize。其中,lengthComputable 是一个布尔值,表示进度信息是否可用;position 是接收到的字节数;totalSize 是响应的 ContentLength 头部定义的总字节数。有了这些信息,就可以给用户提供进度条了。 xhr.onprogress = function(event) { let divStatus = document.getElementById("status"); if (event.lengthComputable) { divStatus.innerHTML = "Received " + event.position + " of " + event.totalSize + " bytes"; } }; xhr.open("get", "altevents.php", true); xhr.send(null); ``` ### 24.3 跨源资源共享 719 > 跨源资源共享(CORS,Cross-Origin Resource Sharing)定义了浏览器与服务器如何实现跨源通信。CORS 背后的基本思路就是使用自定义的 HTTP 头部允许浏览器和服务器相互了解,以确实请求或响应应该成功还是失败。 ##### 24.3.1 预检请求 - > Origin:与简单请求相同。 > > Access-Control-Request-Method:请求希望使用的方法。 > > Access-Control-Request-Headers:(可选)要使用的逗号分隔的自定义头部列表。 ##### 24.3.2 凭据请求 - > Access-Control-Allow-Credentials: true ### 24.4 替代性跨源技术 721 ##### 24.4.1 图片探测 - > 图片探测是利用<img>标签实现跨域通信的最早的一种技术。任何页面都可以跨域加载图片而不必担心限制,因此这也是在线广告跟踪的主要方式。可以动态创建图片,然后通过它们的 onload 和onerror 事件处理程序得知何时收到响应。这种动态创建图片的技术经常用于图片探测(image pings)。图片探测的缺点是只能发送 GET 请求和无法获取服务器响应的内容。 > > ```javascript > let img = new Image(); > img.onload = img.onerror = function() { > alert("Done!"); > }; > img.src = "http://www.example.com/test?name=Nicholas"; > ``` ##### 24.4.2 JSONP - > JSONP 格式包含两个部分:回调和数据。回调是在页面接收到响应之后应该调用的函数,通常回调函数的名称是通过请求来动态指定的。而数据就是作为参数传给回调函数的 JSON 数据。下面是一个典型的 JSONP 请求:http://freegeoip.net/json/?callback=handleResponse > > JSONP 调用是通过动态创建<script>元素并为 src 属性指定跨域 URL 实现的。此时的<script>与<img>元素类似,能够不受限制地从其他域加载资源。因为 JSONP 是有效的 JavaScript,所以 JSONP响应在被加载完成之后会立即执行。比如下面这个例子: > > ```java > function handleResponse(response) { > console.log(` > You're at IP address ${response.ip}, which is in > ${response.city}, ${response.region_name}`); > } > let script = document.createElement("script"); > script.src = "http://freegeoip.net/json/?callback=handleResponse"; > document.body.insertBefore(script, document.body.firstChild); > ``` ### 24.5 Fetch API 722 > Fetch API 是 WHATWG 的一个“活标准”(living standard),能够执行 XMLHttpRequest 对象的所有任务,但更容易使用,接口也更现代化,能够在Web 工作线程等现代 Web 工具中使用。XMLHttpRequest 可以选择异步,而 Fetch API 则必须是异步。 ##### 24.5.1 基本用法 - > fetch()方法是暴露在全局作用域中的,包括主页面执行线程、模块和工作线程。调用这个方法,浏览器就会向给定 URL 发送请求。 > > ```javascript > // 分派请求 & 读取响应,以下两种方式等价 > fetch('bar.txt').then((response) => { > response.text().then((data) => { > console.log(data); > }); > }); > > fetch('bar.txt') > .then((response) => response.text()) > .then((data) => console.log(data)); > > //处理状态码和请求失败 > fetch('/bar').then((response) => { > console.log(response.url)); > > console.log(response.status); // 200 > console.log(response.statusText); // OK > > console.log(response.status); // 404 > console.log(response.statusText); // Not Found > }); > > fetch('/hangs-forever').then((response) => { > console.log(response); > }, (err) => { > console.log(err); > }); > ``` ##### 24.5.2 常见 Fetch 请求模式 - > 1. 发送 JSON 数据 > > ```javascript > let payload = JSON.stringify({ > foo: 'bar' > }); > let jsonHeaders = new Headers({ > 'Content-Type': 'application/json' > }); > fetch('/send-me-json', { > method: 'POST', > body: payload, > headers: jsonHeaders > }); > ``` > > 2. 在请求体中发送参数 > > ```javascript > let payload = 'foo=bar&baz=qux'; > let paramHeaders = new Headers({ > 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' > }); > fetch('/send-me-params', { > method: 'POST', > body: payload, > headers: paramHeaders > }); > ``` > > 3. 发送文件 > > ```javascript > //单个文件 > let imageFormData = new FormData(); > let imageInput = document.querySelector("input[type='file']"); > imageFormData.append('image', imageInput.files[0]); > > //支持多个文件: > let imageInput = document.querySelector("input[type='file'][multiple]"); > for (let i = 0; i < imageInput.files.length; ++i) { > imageFormData.append('image', imageInput.files[i]); > } > > // > fetch('/img-upload', { > method: 'POST', > body: imageFormData > }); > ``` > > 4. 加载 **Blob** 文件 > > ```javascript > const imageElement = document.querySelector('img'); > fetch('my-image.png').then((response) => response.blob()).then((blob) => { > imageElement.src = URL.createObjectURL(blob); > }); > ``` > > 5. 发送跨源请求 > > ```javascript > fetch('//cross-origin.com', { method: 'no-cors' }) .then((response) => console.log(response.type)); > ``` > > 6. 中断请求 > > ```javascript > let abortController = new AbortController(); > fetch('wikipedia.zip', { signal: abortController.signal }).catch(() => console.log('aborted!')); > // 10 毫秒后中断请求 > setTimeout(() => abortController.abort(), 10); > // 已经中断 > ``` ##### 24.5.3 **Headers** 对象 - > 1. **Headers** 与 **Map** 的相似之处 > > > Headers 与 Map 类型都有 get()、set()、has()和 delete()等实例方法,也都有相同的 keys()、values()和 entries()迭代器接口。 > > ```javascript > let h = new Headers(); > h.set('foo', 'bar'); > console.log(h.has('foo')); // true > console.log(h.get('foo')); // bar > h.set('foo', 'baz'); > h.delete('foo'); > > console.log(...h.keys()); // foo, baz > console.log(...h.values()); // bar, qux > console.log(...h.entries()); // ['foo', 'bar'], ['baz', 'qux'] > ``` > > 2. **Headers** 独有的特性 > > > 可以使用键/值对形式的对象,通过 append()方法支持添加多个值。 > > ```javascript > let seed = {foo: 'bar'}; > let h = new Headers(seed); > h.append('foo', 'baz'); > ``` > > 3. 头部护卫 ##### 24.5.4 **Request** 对象 - > ```javascript > //创建 > let r1 =new Request(''); > let r1 =new Request('https://foo.com'); > let r1 =new Request('https://foo.com', { method: 'POST' } > new Request('https://foo.com', { method: 'POST', body: 'foobar' }); > > //克隆 > let r2 = new Request(r1); > let r2 = new Request(r1, {method: 'POST'}); > let r2 = r1.clone(); > > //在 fetch()中使用 Request 对象 > fetch(r1); > fetch(r1, { method: 'POST' }); > fetch(r1.clone()); > ``` ##### 24.5.5 **Response** 对象 - > ```javascript > //创建 > let r = new Response(); > let r = new Response('foobar', { status: 418, statusText: 'I\'m a teapot' }); > > //大多数情况下,产生 Response 对象的主要方式是调用 fetch() > fetch('https://foo.com') .then((response) => { > console.log(response); > }); > > //重定向 > Response.redirect('https://foo.com', 200); > //于产生表示网络错误的 Response 对象 > Response.error(). > > //克隆 Response 对象 > let r1 = new Response('foobar'); > let r2 = r1.clone(); > ``` ##### 24.5.6 **Request**、**Response** 及 **Body** 混入 - > 1. **Body.text()**:返回期约,解决为将缓冲区转存得到的 UTF-8 格式字符串 > > ```javascript > fetch('https://foo.com') .then((response) => response.text()) .then(console.log); > > let request = new Request('https://foo.com', { method: 'POST', body: 'barbazqux' }); > request.text().then(console.log); > ``` > > 2. **Body.json()**:返回期约,解决为将缓冲区转存得到的 JSON。 > > ```javascript > fetch('https://foo.com/foo.json') .then((response) => response.json()) .then(console.log); > > let request = new Request('https://foo.com', { method:'POST', body: JSON.stringify({ bar: 'baz' }) }); > request.json().then(console.log); > ``` > > 3. **Body.formData()**:返回期约,解决为将缓冲区转存得到的 FormData 实例 > > ```javascript > fetch('https://foo.com/form-data') > .then((response) => response.formData()) > .then((formData) => console.log(formData.get('foo')); > > let request = new Request('https://foo.com',{ method:'POST', body: myFormData }); > request.formData().then((formData) => console.log(formData.get('foo')); > ``` > > 4. **Body.arrayBuffer()** :返回期约,解决为将缓冲区转存得到的 ArrayBuffer 实例。 > > 5. **Body.blob()**:返回期约,解决为将缓冲区转存得到的 Blob 实例。 > > 6. **一次性流**:因为 Body 混入是构建在 ReadableStream 之上的,所以主体流只能使用一次。这意味着所有主体混入方法都只能调用一次,再次调用就会抛出错误。 > > 7. 使用 **ReadableStream** 主体 ### 24.6 Beacon API 747 > sendBeacon()方法: > > - sendBeacon()并不是只能在页面生命周期末尾使用,而是任何时候都可以使用。 > - 调用 sendBeacon()后,浏览器会把请求添加到一个内部的请求队列。浏览器会主动地发送队列中的请求。 > - 浏览器保证在原始页面已经关闭的情况下也会发送请求。 > - 状态码、超时和其他网络原因造成的失败完全是不透明的,不能通过编程方式处理。 > - 信标(beacon)请求会携带调用 sendBeacon()时所有相关的 cookie。 > > ```javascript > navigator.sendBeacon(url, body); > navigator.sendBeacon('https://example.com/analytics-reporting-url', '{foo: "bar"}'); > ``` ### 24.7 Web Socket 747 > ```javascript > let socket = new WebSocket("ws://www.example.com/server.php"); > socket.onopen = function() { > alert("Connection established."); > }; > socket.onerror = function() { > alert("Connection error."); > }; > socket.onclose = function() { > console.log(`as clean? ${event.wasClean} Code=${event.code} Reason=${event.reason}`); > }; > socket.onmessage = function(event) { > let data = event.data; > // 对数据执行某些操作 > }; > > let stringData = "Hello world!"; > let arrayBufferData = Uint8Array.from(['f', 'o', 'o']); > let blobData = new Blob(['f', 'o', 'o']); > socket.send(stringData); > socket.send(arrayBufferData.buffer); > socket.send(blobData); > ``` ### 24.8 安全 749 > Ajax 应用程序,无论大小,都会受到 CSRF攻击的影响,包括无害的漏洞验证攻击和恶意的数据盗窃或数据破坏攻击。 > > 关于安全防护 Ajax 相关 URL 的一般理论认为,需要验证请求发送者拥有对资源的访问权限。可以通过如下方式实现: > > - 要求通过 SSL 访问能够被 Ajax 访问的资源。 > - 要求每个请求都发送一个按约定算法计算好的令牌(token)。 > > 注意,以下手段对防护 CSRF 攻击是无效的: > > - 要求 POST 而非 GET 请求(很容易修改请求方法)。 > - 使用来源 URL 验证来源(来源 URL 很容易伪造)。 > - 基于 cookie 验证(同样很容易伪造)。
顶部
收展
底部
[TOC]
目录
第1章 JavaScript简介
第2章 在 HTML中使用JavaScript
第3章 语言基础(1)语法变量
第3章 语言基础(2)数据类型
第3章 语言基础(3)操作符
第3章 语言基础(4)语句
第4章 变量、作用域与内存
第5章 基本引用类型
第6章 集合引用类型
第7章 迭代器与生成器
第8 章对象、类与面向对象编程
第9章 代理与反射
第10章 函数
第11章 期约与异步函数
第12章 BOM
第13章 客户端检测
第14章 DOM
第15章 DOM 扩展
第16章 DOM2 和 DOM3
第17章 事件
第18章 动画与 Canvas 图形
第19章 表单脚本
第20章 JavaScript API
第21章 错误处理与调试
第22章 处理 XML
第23章 JSON
第24章 网络请求与远程资源
第25章 客户端存储
第26章 模块
第27章 工作者线程
第28章 最佳实践
相关推荐
WebSocket