title: JavaScript高级程序设计第四版学习–第二十四章
date: 2021-5-31 10:46:01
author: Xilong88
tags: JavaScript
本章内容:
使用XMLHttpRequest 对象
处理XMLHttpRequest 事件
源域Ajax限制
Fetch API
Streams API
可能出现的面试题:
1.xhr对象的基本使用方法
2.聊聊HTTP头部
3.xhr的事件有哪些?
4.跨域问题了解过吗?有哪些解决方案
5.Fetch API用过吗?
6.Header对象和Request、Response对象了解过吗?
7.WebSocket了解过吗?
Streams API略
知识点:
1.XMLHttpRequest 对象
let xhr = new XMLHttpRequest();
xhr.open("get", "example.php", false);
使用XHR对象首先要调用open() 方法,这个方法接收3个参数:请求类型(“get” 、“post” 等)、请求URL,以及表示请求是否异步的布尔值。
xhr.open("get", "example.php", false);
只能访问同源URL,也就是域名相同、端口相同、协议相同。如果请求的URL与发送请求的页面在任何方面有所不同,则会抛出安全错误。
要发送定义好的请求,必须调用send() 方法:
xhr.open("get", "example.txt", false);
xhr.send(null);
send() 方法接收一个参数,是作为请求体发送的数据。如果不需要发送请求体,则必须传null ,因为这个参数在某些浏览器中是必需的。
收到响应后,XHR对象的以下属性会被填充上数据。
responseText :作为响应体返回的文本。
responseXML :如果响应的内容类型是"text/xml"
或"application/xml" ,那就是包含响应数据的XML DOM文
档。
status :响应的HTTP状态。
statusText :响应的HTTP状态描述。
XHR对象有一
个readyState 属性,表示当前处在请求/响应过程的哪个阶段。这个属
性有如下可能的值。
0:未初始化(Uninitialized)。尚未调用open() 方法。
1:已打开(Open)。已调用open() 方法,尚未调用send() 方
法。
2:已发送(Sent)。已调用send() 方法,尚未收到响应。
3:接收中(Receiving)。已经收到部分响应。
4:完成(Complete)。已经收到所有响应,可以使用了。
每次readyState 从一个值变成另一个值,都会触发readystatechange 事件。可以借此机会检查readyState 的值。一般来说,我们唯一关心的readyState 值是4,表示数据已就绪。
在收到响应之前如果想取消异步请求,可以调用abort() 方法:
xhr.abort();
2.HTTP头部
默认情况下,XHR请求会发送以下头部字段。
Accept :浏览器可以处理的内容类型。
Accept-Charset :浏览器可以显示的字符集。
Accept-Encoding :浏览器可以处理的压缩编码类型。
Accept-Language :浏览器使用的语言。
Connection :浏览器与服务器的连接类型。
Cookie :页面中设置的Cookie。
Host :发送请求的页面所在的域。
Referer :发送请求的页面的URI。注意,这个字段在HTTP规范
中就拼错了,所以考虑到兼容性也必须将错就错。(正确的拼写应
该是Referrer。)
User-Agent :浏览器的用户代理字符串。
setRequestHeader() 方法。这个方法接收两个参数:头部字段的名称和值。
为保证请求头部被发送,必须在open() 之后、send() 之前调用setRequestHeader()
getResponseHeader() 方法从XHR对象获取响应头部,只要传入要获取头部的名称即可。
如果想取得所有响应头部,可以使用getAllResponseHeaders() 方法,这个方法会返回包含所有响应头部的字符串
let myHeader = xhr.getResponseHeader("MyHeader");
let allHeaders xhr.getAllResponseHeaders();
Date: Sun, 14 Nov 2004 18:04:03 GMT
Server: Apache/1.3.29 (Unix)
Vary: Accept
X-Powered-By: PHP/4.3.8
Connection: close
Content-Type: text/html; charset=iso-8859-1
3.GET请求
xhr.open("get", "example.php?name1=value1&name2=value2", true);
function addURLParam(url, name, value) {
url += (url.indexOf("?") == -1 ? "?" : "&");
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
let url = "example.php";
// 添加参数
url = addURLParam(url, "name", "Nicholas");
url = addURLParam(url, "book", "Professional JavaScript");
// 初始化请求
xhr.open("get", url, false);
4.POST请求
xhr.open("post", "example.php", true);
服务器逻辑需要读取原始POST数据才能取得浏览器发送的数据。不过,可以使用XHR模拟表单提交。为此,第一步需要把ContentType 头部设置为"application/x-www-formurlencoded" ,这是提交表单时使用的内容类型。第二步是创建对应格式的字符串。
5.XMLHttpRequest Level 2
新增了FormData 类型
let data = new FormData();
data.append("name", "Nicholas");
append() 方法接收两个参数:键和值,相当于表单字段名称和该字段的值。
通过直接给FormData 构造函数传入一个表单元素,也可以将表单中的数据作为键/值对填充进去:
let data = new FormData(document.forms[0]);
timeout 属性
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
try {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
} catch (ex) {
// 假设由ontimeout处理
}
}
};
xhr.open("get", "timeout.php", true);
xhr.timeout = 1000; // 设置1秒超时
xhr.ontimeout = function() {
alert("Request did not return in a second.");
};
xhr.send(null);
overrideMimeType() 方法
调用overrideMimeType() 可以保证将响应当成某种格式(这里例子是XML)而不是纯文本来处理:
let xhr = new XMLHttpRequest();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);
为了正确覆盖响应的MIME类型,必须在调用send() 之前调用overrideMimeType() 。
6.进度事件
6个进度相关的事件。
loadstart :在接收到响应的第一个字节时触发。
progress :在接收响应期间反复触发。
error :在请求出错时触发。
abort :在调用abort() 终止连接时触发。
load :在成功接收完响应时触发。
loadend :在通信完成时,且在error 、abort 或load 之后触
发。
load 事件
load 事件在响应接收完成后立即触发,这样就不用检查readyState 属性了。
progress 事件
在浏览器接收数据期间,这个事件会反复触发。每次触发时,onprogress 事件处理程序都会收到event 对象,其target 属性是XHR对象,且包含3个额外属性:lengthComputable 、position 和totalSize
lengthComputable 是一个布尔值,表示进度信息是否可用;position 是接收到的字节数;totalSize 是响应的ContentLength 头部定义的总字节数。
7.跨源资源共享
跨源资源共享(CORS,Cross-Origin Resource Sharing)定义了浏览器与服务器如何实现跨源通信。CORS背后的基本思路就是使用自定义的HTTP头部允许浏览器和服务器相互了解,以确实请求或响应应该成功还是失败。
对于简单的请求,比如GET或POST请求,没有自定义头部,而且请求体是text/plain 类型,这样的请求在发送时会有一个额外的头部叫Origin 。Origin 头部包含发送请求的页面的源(协议、域名和端口),以便服务器确定是否为其提供响应。
如果服务器决定响应请求,那么应该发送Access-Control-Allow-Origin 头部,包含相同的源;或者如果资源是公开的,那么就包含"*"。比如:
Access-Control-Allow-Origin: http://www.nczonline.net
跨域XHR对象允许访问status 和statusText 属性,也允许同步请求。出于安全考虑,跨域XHR对象也施加了一些额外限制。
不能使用 setRequestHeader() 设置自定义头部。
不能发送和接收cookie。
getAllResponseHeaders() 方法始终返回空字符串。
预检请求
CORS通过一种叫预检请求 (preflighted request)的服务器验证机制,允许使用自定义头部、除GET和POST之外的方法,以及不同请求体内容类型。在要发送涉及上述某种高级选项的请求时,会先向服务器发送一个“预检”请求。这个请求使用OPTIONS方法发送并包含以下头部。
Origin :与简单请求相同。
Access-Control-Request-Method :请求希望使用的方法。
Access-Control-Request-Headers :(可选)要使用的逗号分
隔的自定义头部列表。
服务器会通过在响应中发送如下头部与浏览器沟通这些信息。
Access-Control-Allow-Origin :与简单请求相同。
Access-Control-Allow-Methods :允许的方法(逗号分隔的列
表)。
Access-Control-Allow-Headers :服务器允许的头部(逗号分
隔的列表)。
Access-Control-Max-Age :缓存预检请求的秒数。
凭据请求
默认情况下,跨源请求不提供凭据(cookie、HTTP认证和客户端SSL证书)。可以通过将withCredentials 属性设置为true 来表明请求会发送凭据。如果服务器允许带凭据的请求,那么可以在响应中包含如下HTTP头部:
Access-Control-Allow-Credentials: true
7.替代性跨源技术
图片探测
图片探测是与服务器之间简单、跨域、单向的通信。
任何页面都可以跨域加载图片而不必担心限制,因此这也是在线广告跟踪的主要方式。
let img = new Image();
img.onload = img.onerror = function() {
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
JSONP
JSONP看起来跟JSON一样,只是会被包在一个函数调用里,比如:
callback({ "name": "Nicholas" });
JSONP格式包含两个部分:回调和数据。
下面是一个典型的JSONP请求:
http://freegeoip.net/json/?callback=handleResponse
首先,JSONP是从不同的域拉取可执行代码。如果这个域并不可信,则可能在响应中加入恶意内容。此时除了完全删除JSONP没有其他办法。在使用不受控的Web服务时,一定要保证是可以信任的。
第二个缺点是不好确定JSONP请求是否失败。虽然HTML5规定了<script>
元素的onerror 事件处理程序,但还没有被任何浏览器实现。为此,开发者经常使用计时器来决定是否放弃等待响应。这种方式并不准确,毕竟不同用户的网络连接速度和带宽是不一样的。
8.Fetch API
Fetch API则必须是异步
fetch() 只有一个必需的参数input 。多数情况下,这个参数是要获取资源的URL。这个方法返回一个期约:
let r = fetch('/bar');
console.log(r); // Promise <pending>
fetch('bar.txt')
.then((response) => {
console.log(response);
});
// Response { type: "basic", url: ...** **}
读取响应内容的最简单方式是取得纯文本格式的内容,这要用到text() 方法。这个方法返回一个期约,会解决为取得资源的完整内容:
fetch('bar.txt')
.then((response) => {
response.text().then((data) => {
console.log(data);
});
});
// bar.txt的内容
内容的结构通常是打平的:
fetch('bar.txt')
.then((response) => response.text())
.then((data) => console.log(data));
// bar.txt的内容
处理状态码和请求失败
fetch('/bar')
.then((response) => {
console.log(response.status); // 200
console.log(response.statusText); // OK
});
fetch('/does-not-exist')
.then((response) => {
console.log(response.status); // 404
console.log(response.statusText); // Not Found
});
默认行为是跟随重定向并返回状态码不是300~399的响应。跟随重定向时,响应对象的redirected 属性会被设置为true,而状态码仍然是200:
fetch('/permanent-redirect')
.then((response) => {
// 默认行为是跟随重定向直到最终URL
// 这个例子会出现至少两轮网络请求
// <origin url>/permanent-redirect -> <redirect url>
console.log(response.status); // 200
console.log(response.statusText); // OK
console.log(response.redirected); // true
});
自定义选项
只使用URL时,fetch() 会发送GET请求,只包含最低限度的请求头。要进一步配置如何发送请求,需要传入可选的第二个参数init对象。init 对象要按照下表中的键/值进行填充。
headers
常见Fetch请求模式
发送JSON数据可以像下面这样发送简单JSON字符串:
let payload = JSON.stringify({
foo: 'bar'
});
let jsonHeaders = new Headers({
'Content-Type': 'application/json'
});
fetch('/send-me-json', {
method: 'POST', // 发送请求体时必须使用一种HTTP方法
body: payload,
headers: jsonHeaders
});
在请求体中发送参数因为请求体支持任意字符串值,所以可以通过它发送请求参数:
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', // 发送请求体时必须使用一种HTTP方法
body: payload,
headers: paramHeaders
});
在请求体中发送参数
因为请求体支持任意字符串值,所以可以通过它发送请求参数:
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', // 发送请求体时必须使用一种HTTP方法
body: payload,
headers: paramHeaders
});
发送文件
因为请求体支持FormData 实现,所以fetch() 也可以序列化并发送文件字段中的文件:
let imageFormData = new FormData();
let imageInput = document.querySelector("input[type='file']");
imageFormData.append('image', imageInput.files[0]);
fetch('/img-upload', {
method: 'POST',
body: imageFormData
});
这个fetch() 实现可以支持多个文件:
let imageFormData = new FormData();
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
});
加载Blob文件 略
发送跨源请求
从不同的源请求资源,响应要包含CORS头部才能保证浏览器收到响应。没有这些头部,跨源请求会失败并抛出错误。
fetch('//cross-origin.com');
// TypeError: Failed to fetch
// No 'Access-Control-Allow-Origin' header is present on the requested resource.
如果代码不需要访问响应,也可以发送no-cors 请求。此时响应的type 属性值为opaque ,因此无法读取响应内容。这种方式适合发送探测请求或者将响应缓存起来供以后使用。
fetch('//cross-origin.com', { method: 'no-cors' })
.then((response) => console.log(response.type));
// opaque
中断请求
let abortController = new AbortController();
fetch('wikipedia.zip', { signal: abortController.signal })
.catch(() => console.log('aborted!');
// 10毫秒后中断请求
setTimeout(() => abortController.abort(), 10);
// 已经中断
9.Headers 对象
Headers 与Map 的相似之处
let h = new Headers();
let m = new Map();
// 设置键
h.set('foo', 'bar');
m.set('foo', 'bar');
// 检查键
console.log(h.has('foo')); // true
console.log(m.has('foo')); // true
console.log(h.has('qux')); // false
console.log(m.has('qux')); // false
// 获取值
console.log(h.get('foo')); // bar
console.log(m.get('foo')); // bar
// 更新值
h.set('foo', 'baz');
m.set('foo', 'baz');
// 取得更新的值
console.log(h.get('foo')); // baz
console.log(m.get('foo')); // baz
// 删除值
h.delete('foo');
m.delete('foo');
// 确定值已经删除
console.log(h.get('foo')); // undefined
console.log(m.get('foo')); // undefined
Headers 和Map 都可以使用一个可迭代对象来初始化,比如:
let seed = [['foo', 'bar']];
let h = new Headers(seed);
let m = new Map(seed);
console.log(h.get('foo')); // bar
console.log(m.get('foo')); // bar
而且,它们也都有相同的keys() 、values()和entries() 迭代器接口:
let seed = [['foo', 'bar'], ['baz', 'qux']];
let h = new Headers(seed);
let m = new Map(seed);
console.log(...h.keys()); // foo, baz
console.log(...m.keys()); // foo, baz
console.log(...h.values()); // bar, qux
console.log(...m.values()); // bar, qux
console.log(...h.entries()); // ['foo', 'bar'], ['baz', 'qux']
console.log(...m.entries()); // ['foo', 'bar'], ['baz', 'qux']
Headers 独有的特性
Headers 并不是与Map 处处都一样。在初始化Headers 对象时,也可以使用键/值对形式的对象,而Map 则不可以:
let seed = {foo: 'bar'};
let h = new Headers(seed);
console.log(h.get('foo')); // bar
let m = new Map(seed);
// TypeError: object is not iterable
一个HTTP头部字段可以有多个值,而Headers 对象通过append()方法支持添加多个值。在Headers 实例中还不存在的头部上调用append() 方法相当于调用set() 。后续调用会以逗号为分隔符拼接多个值:
let h = new Headers();
h.append('foo', 'bar');
console.log(h.get('foo')); // "bar"
h.append('foo', 'baz');
console.log(h.get('foo')); // "bar, baz"
头部护卫,对headers限制
10.Request 对象
通过构造函数初始化Request 对象。为此需要传入一个input参数,一般是URL:
let r = new Request('https://foo.com');
console.log(r);
// Request {...}
Request 构造函数也接收第二个参数——一个init 对象。这个init 对象与前面介绍的fetch() 的init 对象一样。没有在init对象中涉及的值则会使用默认值:
// 用所有默认值创建Request对象
console.log(new Request(''));
// Request {
// bodyUsed: false
// cache: "default"
// credentials: "same-origin"
// destination: ""
// headers: Headers {}
// integrity: ""
// keepalive: false
// method: "GET"
// mode: "cors"
// redirect: "follow"
// referrer: "about:client"
// referrerPolicy: ""
// signal: AbortSignal {aborted: false, onabort: null}
// url: "<current URL>"
// }
// 用指定的初始值创建Request对象
console.log(new Request('https://foo.com',
{ method: 'POST' }));
// Request {
// bodyUsed: false
// cache: "default"
// credentials: "same-origin"
// destination: ""
// headers: Headers {}
// integrity: ""
// keepalive: false
// method: "POST"
// mode: "cors"
// redirect: "follow"
// referrer: "about:client"
// referrerPolicy: ""
// signal: AbortSignal {aborted: false, onabort: null}
// url: "https://foo.com/"
// }
克隆Request 对象
let r1 = new Request('https://foo.com');
let r2 = new Request(r1);
console.log(r2.url); // https://foo.com/
let r1 = new Request('https://foo.com');
let r2 = new Request(r1, {method: 'POST'});
console.log(r1.method); // GET
console.log(r2.method); // POST
这种克隆方式并不总能得到一模一样的副本。最明显的是,第一个请求的请求体会被标记为“已使用”:
let r1 = new Request('https://foo.com',
{ method: 'POST', body: 'foobar' });
let r2 = new Request(r1);
console.log(r1.bodyUsed); // true
console.log(r2.bodyUsed); // false
在fetch() 中使用Request 对象
let r = new Request('https://foo.com');
// 向foo.com发送GET请求
fetch(r);
// 向foo.com发送POST请求
fetch(r, { method: 'POST' });
fetch() 会在内部克隆传入的Request 对象。与克隆Request 一样,fetch() 也不能拿请求体已经用过的Request 对象来发送请求:
let r = new Request('https://foo.com',
{ method: 'POST', body: 'foobar' });
r.text();
fetch(r);
// TypeError: Cannot construct a Request with a Request object that has already
been used.
关键在于,通过fetch 使用Request 会将请求体标记为已使用。也就是说,有请求体的Request 只能在一次fetch 中使用。
要想基于包含请求体的相同Request 对象多次调用fetch() ,必须在第一次发送fetch() 请求前调用clone() :
let r = new Request('https://foo.com',
{ method: 'POST', body: 'foobar' });
// 3个都会成功
fetch(r.clone());
fetch(r.clone());
fetch(r);
11.Response 对象
可以通过构造函数初始化Response 对象且不需要参数。此时响应实例的属性均为默认值,因为它并不代表实际的HTTP响应:
let r = new Response();
console.log(r);
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: true
// redirected: false
// status: 200
// statusText: "OK"
// type: "default"
// url: ""
// }
Response 构造函数接收一个可选的body 参数。这个body 可以是null ,等同于fetch() 参数init 中的body 。还可以接收一个可选的init 对象,这个对象可以包含下表所列的键和值。
let r = new Response('foobar', {
status: 418,
statusText: 'I\'m a teapot'
});
console.log(r);
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: false
// status: 418
// statusText: "I'm a teapot"
// type: "default"
// url: ""
// }
大多数情况下,产生Response 对象的主要方式是调用fetch() ,它返回一个最后会解决为Response 对象的期约,这个Response对象代表实际的HTTP响应。下面的代码展示了这样得到的Response 对象:
fetch('https://foo.com')
.then((response) => {
console.log(response);
});
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: true
// redirected: false
// status: 200
// statusText: "OK"
// type: "basic"
// url: "https://foo.com/"
// }
Response.redirect() 和Response.error() 。前者接收一个URL和一个重定向状态码(301、302、303、307或308),返回重定向的Response 对象:
console.log(Response.redirect('https://foo.com', 301));
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: false
// status: 301
// statusText: ""
// type: "default"
// url: ""
// }
提供的状态码必须对应重定向,否则会抛出错误:
Response.redirect('https://foo.com', 200);
// RangeError: Failed to execute 'redirect' on 'Response': Invalid status code
另一个静态方法Response.error() 用于产生表示网络错误的Response 对象(网络错误会导致fetch() 期约被拒绝)。
console.log(Response.error());
// Response {
// body: (...)
// bodyUsed: false
// headers: Headers {}
// ok: false
// redirected: false
// status: 0
// statusText: ""
// type: "error"
// url: ""
// }
Response 对象包含一组只读属性,描述了请求完成后的状态,如下表所示。
克隆Response 对象
let r1 = new Response('foobar');
let r2 = r1.clone();
console.log(r1.bodyUsed); // false
console.log(r2.bodyUsed); // false
如果响应对象的bodyUsed 属性为true (即响应体已被读取),则不能再创建这个对象的副本。
12.Request 、Response 及Body 混入
Request 和Response 都使用了Fetch API的Body 混入,以实现两者承担有效载荷的能力
Body 混入提供了5个方法
Body.text() 方法返回期约,解决为将缓冲区转存得到的UTF-8格式字符串。
fetch('https://foo.com')
.then((response) => response.text())
.then(console.log);
// <!doctype html><html lang="en">
// <head>
// <meta charset="utf-8">
// ...
let request = new Request('https://foo.com',
{ method: 'POST', body: 'barbazqux' });
request.text()
.then(console.log);
// barbazqux
Body.json()
Body.json() 方法返回期约,解决为将缓冲区转存得到的JSON。
fetch('https://foo.com/foo.json')
.then((response) => response.json())
.then(console.log);
// {"foo": "bar"}
let request = new Request('https://foo.com',
{ method:'POST', body: JSON.stringify({ bar: 'baz' }) });
request.json()
.then(console.log);
// {bar: 'baz'}
Body.formData()
将FormData 对象序列化/反序列化为主体
let myFormData = new FormData();
myFormData.append('foo', 'bar');
fetch('https://foo.com/form-data')
.then((response) => response.formData())
.then((formData) => console.log(formData.get('foo'));
// bar
let myFormData = new FormData();
myFormData.append('foo', 'bar');
let request = new Request('https://foo.com',
{ method:'POST', body: myFormData });
request.formData()
.then((formData) => console.log(formData.get('foo'));
// bar
Body.arrayBuffer()
将主体内容转换为ArrayBuffer 实例
fetch('https://foo.com')
.then((response) => response.arrayBuffer())
.then(console.log);
// ArrayBuffer(...) {}
let request = new Request('https://foo.com',
{ method:'POST', body: 'abcdefg' });
// 以整数形式打印二进制编码的字符串
request.arrayBuffer()
.then((buf) => console.log(new Int8Array(buf)));
// Int8Array(7) [97, 98, 99, 100, 101, 102, 103]
Body.blob()
以原始二进制格式使用主体
fetch('https://foo.com')
.then((response) => response.blob())
.then(console.log);
// Blob(...) {size:..., type: "..."}
let request = new Request('https://foo.com',
{ method:'POST', body: 'abcdefg' });
request.blob()
.then(console.log);
// Blob(7) {size: 7, type: "text/plain;charset=utf-8"}
一次性流 略
Beacon API 略
13.Web Socket
let socket = new WebSocket("ws://www.example.com/server.php");
WebSocket 也有一个readyState 属性表示当前状态。不过,这个值与XHR中相应的值不一样。
WebSocket.OPENING (0):连接正在建立。
WebSocket.OPEN (1):连接已经建立。
WebSocket.CLOSING (2):连接正在关闭。
WebSocket.CLOSE (3):连接已经关闭。
关闭Web Socket连接:
socket.close();
发送和接收数据
let socket = new WebSocket("ws://www.example.com/server.php");
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);
WebSocket 对象在连接生命周期中有可能触发3个其他事件。
open :在连接成功建立时触发。
error :在发生错误时触发。连接无法存续。
close :在连接关闭时触发。