js温故(四):Ajax—XHR、fetch、websocket
一、XMLHttpRequest
对象
所有现代浏览器都支持通过XMLHttpRequest
构造函数来原生支持XHR
对象:const xhr = new XMLHttpRequest()
。
1. 使用XHR
使用XHR
第一步便是调用open()
方法,其接收三个参数:请求的类型(如get
),url
,以及一个布尔值表示请求是否为异步。例如,xhr.open('get', 'www.baidu.com', false)
表示将要向www.baidu.com
发送一个同步的get
请求。有两点需要注意:
- 这里得第二个参数
url
一般是相对于当前页面的,不过也可以使用绝对URL
; - 调用
open()
方法只是进行初始化,并不会立即发送请求,而是为发送请求做准备,真正开始发送请求还需要调用send()
方法。
send()
方法接收一个参数,表示作为请求体发送的数据。xhr.send({username: 'cc', pwd: 'lovecake'})
。如果不需要请求体,则必须传入null
:xhr.send(null)
。调用send()
方法后,请求就会发送到服务器。由于在open()
方法里将这个请求设置为同步的,因此在调用send()
方法后,后续的js
代码会暂停执行,等到服务器响应后方才恢复执行。当收到相应后,xhr
对象的一些属性会被填充上响应数据:
responseText
:作为相应体返回的文本;responseXML
:如果响应的内容类型是text/xml
或application/xml
,则此属性为包含响应数据的XML DOM
文档;status
:响应的HTTP
状态;statusText
:响应的HTTP
状态描述。
当收到响应后,首先应该检查状态码status
来判断响应是否成功返回。一般来说,状态码为2xx
则表示成功。此外,状态码为304
,表示服务器资源未修改,直接用缓存中的资源,这也意味着响应有效。其它状态码则表示请求失败。
1 | const xhr = new XMLHttpRequest(); |
尽管statusText
中也包含状态描述信息,但是它在跨浏览器时并不可靠,因此还是应该用status
来检查。对于不同的响应类型,responseText
始终保存响应体的内容,而responseXML
只在相应类型为XML
数据时为有效数据,否则其值为null
。
由于设置为同步请求,会阻塞之后的js
代码,因此,最好使用异步请求。
1 | const xhr = new XMLHttpRequest(); |
异步请求需要用到xhr
的readyState
属性,该属性表示当前请求处于哪个阶段,其可能的值如下:
- 0 :未初始化,即尚未调用
open()
方法; - 1 :已打开。即调用了
open()
方法,但还未调用send()
; - 2 :已发送。已经调用
send()
方法,但还未接收到响应; - 3 :接收中,即已接受到部分响应;
- 4 :完成,即已接收到所有数据,可以使用。
可以看到,我们最需要的就是readyState
的值为 4。readyState
的值每次变化时,都会触发readystatechange
事件,因此,只需要监听xhr
对象的readystatechange
事件,该事件没有event
对象,因此我们判断readyState
值为 4 时,即请求完成,可进行后续操作。 为保证跨浏览器兼容,对readystatechange
事件的监听应在open()
方法调用之前进行。 对于不想继续的请求,可以使用abort()
方法取消,并断开对xhr
对象的引用。由于内存问题,不推荐复用xhr
对象,最好每个请求都重新创建一个。
1 | const xhr = new XMLHttpRequest(); |
2. HTTP
头部
每个HTTP
请求和响应都携带有一些头部信息。XHR
对象通过一些方法暴露请求和响应相关的头部字段。
默认情况下,XHR
请求会发送一些头部字段:
Accept
:浏览器能够处理的内容类型;Accept-Charset
:浏览器能够显示的字符集;Accept-Encoding
:浏览器能够处理的压缩编码类型;Accept-Language
:浏览器使用的语言;Connection
:浏览器与服务器的连接类型;Cookie
:页面中设置的 Cookie;Host
:发送请求的页面所在的域;Referer
:发送请求的页面的URL
(这个字段本应是Referrer
,但是在HTTP
规范中就拼错了,因此将错就错);User-Agent
:浏览器的用户代理字符串。
如果需要发送额外的头部字段,可以使用setRequestHeader()
方法,这个方法应该在open()
方法之后、send()
方法之前调用。
1 | // 创建xhr对象 |
要读取相应头的信息,可以使用getResponseHeader()
方法,另外,也可以使用getAllResponseHeaders()
方法来获取所有的响应头,其值为包含所有响应头部的字符串。
1 | const myHeader = xhr.getResponseHeader("my-header"), |
3. GET
请求
GET
请求的查询参数一般都拼接在URL
后面。对于XHR
而言,拼接的参数应该经过encodeURLComponent()
方法来正确编码并添加到URL
后面,然后传给open()
方法。如下函数可以将查询字符串参数添加到URL
后面。
1 | function appendURLParams(url, name, value) { |
4. POST
请求
POST
请求在请求体中携带数据。XHR
最初主要设计用来发送XML
数据的,也可以发送字符串。对于服务器而言, POST
请求与表单提交是不一样的。可以将请求头中的Content-Type
设置为与表单提交一致,即application/x-www-formurlencoded
,并且创建对应格式的字符串,来模拟表单提交。
5. XMLHTTPRequest
Level 2
XMLHTTPRequest
Level 2 进一步发展了XHR
。
(1) FormData
类型
XMLHTTPRequest
Level 2 中新增了FormData
类型,以便对表单数据进行序列化,或创建与表单类似格式的数据然后通过XHR
发送。
1 | const fd = new FormData(); |
使用append()
方法来添加数据(键可以重复),delete()
方法来删除所有对应键的数据,set()
来设置不重复的键,get()
方法来获取该键的第一条数据,getAll()
方法得到包含该键对应的所有数据的数组。
(2) 超时
给XHR
对象的timeout
属性设置一个时间(毫秒),如果超过该时间还未收到响应,则XHR
对象会触发timeout
事件,并调用ontimeout
处理程序。触发timeout
事件后,readyState
的值也会变成 4,但是此时访问xhr.status
会出错。因此,检查status
的语句应该放在try/catch
语句中,当捕获到错误则说明请求超时。
1 | // 创建xhr对象 |
(3) overrideMimeType()
方法
overrideMimeType()
方法用于重写XHR
响应的MIME
类型。响应返回的MIME
类型决定了XHR
对象如何处理响应。例如,如果服务器实际上发送了XML
数据,但是响应头设置的MIME
类型是text/plain
,则XHR
对象不会将其当作XML
类型来处理,导致xhr.responseXML
的值为null
。此时调用overrideMimeType()
方法可以强制将响应当成XML
来处理当然,这个方法应该在调用send()
方法之前调用。
1 | const xhr = new XMLHttpRequest(); |
二、进度事件 progress
进度事件一开始只针对于XHR
,后来也推广到了其它类似API
。一般来说,有如下 6 个进度相关的事件:
loadstart
:在接收到第一个响应字节时触发;progress
:在接收响应期间反复出发;error
:在请求出错时触发;abort
:在请求终止连接时触发;load
:在成功接收完响应时触发;loadend
:在通信完成时,且在error
、abort
、load
之后触发。
这些事件都比较容易理解,主要说明一下load
和progress
:
1. load
事件
onload
事件处理程序相当于之前的readyState
的值为 4,简化了操作。它接收一个event
对象,其target
为对应的xhr
对象,但是不是所有浏览器都实现了这个事件的event
对象,因此,考虑到兼容性,还是应该像之前一样使用xhr
,而不是event.target
。当然,只要接收到完响应,就会触发load
事件,这不受状态码status
的影响。因此,我们仍然需要检查status
。
2. progress
事件
在浏览器接收数据期间,progress
事件会反复触发。每次触发,onprogress
事件处理程序都会接收一个event
对象,它的target
属性是xhr
对象,且拥有额外的三个属性:lengthComputable
、position
、totalSize
。其中,lengthComputable
是一个布尔值,表示进度信息是否可用;position
表示当前接收到的字节数;totalSize
表示总字节数。用这些信息就可以展示进度条了。另外,onprogress
事件处理程序应该在open()
事件之前添加。
1 | let xhr = new XMLHttpRequest(); |
三、 跨域资源共享
由于XHR
受跨域安全策略限制,默认情况下,XHR
只能访问与发起请求的页面同在一个域内的资源。跨域资源共享(CORS
)定义了浏览器与服务器如何进行跨源通信。CORS
背后的基本思路是使用自定义的HTTP
头部,允许浏览器与服务器相互了解,来确定请求应该成功或失败。
对于GET
、POST
这些简单的请求,没有请求头,且请求体为text/plain
类型,这样的请求在发送时会额外有一个头部,叫做Origin
,它包含发送请求的页面的源(协议、域名、端口),从而让服务器确定是否为其提供响应。如果服务器决定响应,就会发送 Access-Control-Allow-Origin
头部,包含相同的源(协议、域名、端口),或者为*
,表示资源是公开的。
如果没有这个头部,或者有但是源不匹配,则表示不会响应浏览器请求。无论请求还是响应,都没有Cookie
信息。跨域XHR
对象允许访问status
和responseText
,也允许同步请求,但是处于安全考虑也做了一些限制:
- 不允许使用
setRequestHeader()
来设置自定义头部; - 不能接收与发送
cookie
; getAllResponseHeaders()
始终返回空字符串;
1. 预检请求
CORS
通过预检请求的服务器验证机制,允许使用自定义头部、除GET
、POST
以外的方法、以及不同请求体的内容类型。在发送这些里面的某种高级选项的请求时,会先向服务器发送一个预检请求,该请求使用OPTIONS
方法发送并包含如下头部:
Origin
:与简单请求相同,为发送请求的页面的源;Access-Control-Request-Method
:请求希望使用的方法;Access-Control-Request-Headers
:用逗号分隔的请求头部列表(可选)。
例如,假设一个POST
请求的预检请求的头部如下:
1 | Origin: http://www.baidu.com |
在该请求发送后,服务器判断是否允许这种类型的请求,并在响应中包含如下头部,来与浏览器沟通这些信息:
Access-Control-Allow-Origin
:与简单请求相同,允许访问的源,或者*
表示资源公开;Access-Control-Allow-Methods
:允许访问的方法,以逗号分隔的列表;Access-Control-Allow-Headers
:服务器允许的头部,以逗号分隔的列表;Access-Control-Max-Age
:缓存预检请求的秒数。预检请求响应后,其结果会按照这个秒数被缓存一段时间,期间再次发送这种类型的请求无需再发送预检请求。
2. 凭据请求
默认情况下,跨源请求不提供凭据(cookie
、HTTP
认证、客户端SSL
证书等)。可以将withCredentials
设置为true
来表明请求会发送凭据。如果服务器允许带凭据的请求,则可以在响应头中包含如下HTTP
头部:
1 | Access-Control-Allow-Credentials: true |
如果发送了凭据请求,但是服务器的响应中没有这个头部,则浏览器不会把响应的内容交给JavaScript
。当然,服务器也可以在预检请求的响应中包含这个头部,来表明这个源允许发送凭据请求。
四、 替代性跨源技术
1. 图片探测
图片探测是与服务器之间简单、跨域、单向的通信,只能通过设置src
属性来发送GET
请求,且无法获取服务器返回的数据。通过动态创建图片,监听它们的onload
和onerror
事件处理程序来得知何时收到响应。数据可以通过查询字符串来发送,响应可以随意设置,毕竟浏览器无法得到任何数据,只能通过onload
和onerror
来获悉什么时候接收到响应。
2. JSONP
JSONP
看起来和JSON
一样,但是它被包含在函数里。JSONP
格式包含 回调 和 数据 两部分。回调是在页面接收到响应之后应该调用的函数,回调函数的名称通常是通过请求来动态指定的。而数据就是作为参数传给回调函数的JSON
数据。JSONP
服务通常支持用查询字符串来指定回调函数的名称。
<script>
标签不受跨源安全策略的限制,因此通过动态生成<script>
标签并指定其src
属性来调用JSONP
。
1 | function handleResponse(response) { |
五、 Fetch API
Fetch API
可以执行所有XMLHttpRequest
的任务,且更便于使用,接口也更现代化,能够在工作者线程中使用。Fetch API
必须是异步的。
1. 基本使用
fetch()
方法暴露在全局作用域中,包括主页面线程、模块和工作者线程。
(1) 分派请求
fetch()
方法只有一个必需的参数,即获取资源的URL
,可以是相对路径或绝对路径。返回一个期约(Promise
实例)。
1 | let response = fetch("www.baidu.com"); |
当请求完成且资源可用时,该期约会解决为一个Response
对象,该对象是API
的封装,可以通过它的某些方法来获取相应的资源。
(2) 读取响应
最快的读取响应的方法是读取纯文本内容,这需要调用Response
对象的text()
方法,该方法返回一个期约,解决为取得的资源的内容。
1 | fetch("test.txt") |
(3) 处理 状态码 和 失败请求
可以检查Response
对象的status
和statusText
来确定相应状态。成功获取响应的请求一般会得到值为 200 的状态码;请求不存在的资源一般会得到值为 404 的状态码;服务器错误一般是值为 500 的状态码;可以显示地定义fetch()
遇到重定向时的行为,但是默认行为是跟随重定向,此时响应对象的redirected
属性会被设置为true
,而状态码仍然是 200。
哪怕请求看起来失败了(如status
值为 500),期约也会被解决,我们需要判断状态码来确定请求是否成功。而当请求超时时,期约才会执行拒绝程序。此外,无网络连接、违反CORS
、HTTPS
错配等也会导致期约被拒绝。
通过Response
对象的url
属性可以检查发起请求的完整URL
。
1 | fetch("/test").then((resp) => console.log(resp.url)); |
(4) 自定义选项
只使用URL
参数时,fetch()
默认为GET
请求,且只使用最低限度的请求头。如果徐奥进一步配置,比如使用POST
方法、自定义请求头等操作时,就需要传入可选的第二个参数:init
对象。下面列举一些常用的配置:
body
:请求体的内容,必须是Blob
,BufferSource
、FormData
、URLSearchParams
、ReadableStream
或String
的实例;cache
:用于控制浏览器与HTTP
缓存的交互。如需跟踪缓存的重定向,则请求的redirect
属性应为follow
,且不能违反CORS
。cache
的值应为以下之一:Default
:默认值。命中有效的缓存,则fetch()
返回该缓存,不发送请求;命中无效的缓存,则发送条件式请求,若响应已经改变,则更新缓存的值,且fetch()
返回新的缓存值;若没有命中缓存,则发送请求并缓存响应,然后fetch()
返回响应。no-store
:浏览器不检查缓存,直接发送请求;不缓存响应,直接fetch()
返回;reload
:浏览器不检查缓存,直接发起请求;之后缓存响应并fetch()
返回响应;no-cache
:未命中缓存则发送请求,并缓存响应,然后fetch()
返回响应;否则发送条件式请求,若响应已经改变,则更新缓存值然后fetch()
返回缓存的值;force-cache
:未命中缓存则发起请求,缓存响应并fetch()
返回响应;否则,无论命中有效缓存还是无效缓存,都直接返回缓存的值,不发起请求;only-if-cached
:只有请求模式为same-origin
时使用缓存;无论命中有效缓存还是无效缓存时,都返回缓存的值,不发送请求;没有命中缓存则返回状态码为 504 (网关超时) 的响应。
credentials
:用于指定在外派请求时如何包含cookie
,和XMLHttpRequest
的withCredentials
标签类似。值为以下字符串之一:omit
:不发送cookie
;same-origin
:默认值。只在同源请求时发送cookie
;include
:无论同源还是跨源请求,都发送cookie
。
headers
:用于指定请求头部,必须是Headers
实例或包含字符串格式的键值对对象。默认为空的Headers
实例,但是这不意味着不发送请求头,浏览器仍然会跟随请求发送一些头部信息,但是这些请求头对JavaScript
不可见,但浏览器的网络检查器可以检查到。
keepalive
:指示浏览器是否允许在页面卸载后请求仍然存在,适合向服务器发送报告事件与分析。值为布尔值,默认为false
。method
:指定请求的方法,值为以下字符串之一:GET
,默认值;POST
;PUT
;PATCH
;DELETE
;HEAD
;OPTIONS
;CONNECT
;TARCE
。
mode
:指定请求模式,决定来自跨源请求的响应是否有效。必须是以下字符串之一:cors
:通过构造函数手动创建Request
实例时默认值为此,允许遵守CORS
跨源请求;no-cors
:其它情况默认值为此,允许不需要发送预检请求的跨源请求,如HEAD
、GET
和只带有满足CORS
请求头部的POST
请求;响应类型是opaque
,意思是能读取响应内容;same-origin
:只允许同源请求;navigate
:用于支持HTML
导航,一般用不到。
redirect
:用于指定如何处理重定向响应。follow
:默认值,跟踪重定向请求,以最终非重定向的URL
的响应作为最终响应;error
:遇到重定向时抛出错误;manual
:不跟踪重定向,但是返回opaqueredirect
类型的响应,并暴露期望重定向的URL
。允许以手动方式跟踪重定向。
referrer
:用于指定HTTP
的Referer
头部的内容,值为以下字符串之一:no-referrer
:以no-referrer
作为值;client/about:client
:以当前URL
或no-referrer
作为值;<URL>
:以伪造的URL
作为值。伪造URL
的源必须与执行脚本的源匹配。
2. 常见的Fetch
请求模式
与XMLHttpRequest
类似,Fetch
既能够接收数据,也能够发送数据。
(1) 发送JSON
数据
1 | let data = JSON.stringify({ |
(2) 在请求体中发送参数
请求体可以是String
实例,这意味着它可以是任意字符串,可以用来发送请求参数。
1 | let params = "uname=cc&age=18"; |
(3) 发送文件
请求体可以是FormData
实例,因此fetch()
可以序列化并发送文件字段中的文件。
1 | // 初始化一个FormData实例 |
当然,也可以给FormData
实例添加多个文件,然后通过fetch()
发送多个文件。
1 | // 初始化一个FormData实例 |
(4) 加载Blob
文件
Fetch API
可以提供Blob
类型的响应,而Blob
类型又兼容多种浏览器API
。以图片为例,可以将图片文件加载到内存,然后将其添加到HTML
元素上。我们可以使用Response
对象上的blob()
方法,该方法返回一个期约,解决为一个Blob
实例。将这个Blob
实例传给URL.createObjectUrl()
方法,可以生成添加给图片元素src
属性的值:
1 | const img = document.createElement("img"); |
(5) 发送跨源请求
发送跨源请求,响应头需要包含CORS
头部才能保证浏览器收到响应。若没有这些头部,则跨源请求会失败并抛出错误。如果不需要访问响应,也可以发送no-cors
请求,此时响应的类型为opaque
,意思是无法读取响应,这种请求适合作为探测请求。
(6) 中断请求
Fetch API
可以通过 AbortController/AbortSignal
对来中断请求。调用AbortController.abort()
方法会中断所有网络传输,尤其适合传输大型负载的情况 (如长视频) 。中断的fetch()
请求会导致包含错误的拒绝。
1 | const abortController = new AbortController(); |
3. Headers
对象
Headers
是所有请求和响应的头部的容器。每个外派的Request
请求和入站的Response
对象都有一个Headers
实例,可以通过Request.prototype.header
和Response.prototype.header
来访问。这两个属性都可以修改,此外,也可以通过new Headers()
来创建一个Headers
实例。
(1) Headers
与 Map
这两者极其相似,都有get()
,set()
,delete()
,has()
等方法。都可以使用一个可迭代对象来初始化。
1 | let seed = [["name", "cc"]]; |
都有相同的keys()
,values()
,entries()
迭代器接口。
(2) Headers
独有的特性
Headers
在初始化时,也可以使用键值对的形式,而Map
不可以。Headers
实例的一个字段,可以通过append()
方法添加多个值,后续的值会以逗号分隔,拼接在原来的值后面。
1 | // 以键值对形式初始化 |
(3) 头部护卫
头部护卫根据Request
对象的来源不同而不同,它能保护相关头部字段不被修改,违反护卫限制则会抛出TypeError
。护卫只能是以下几种之一:
none
:在通过构造函数创建Headers
实例时激活,不会限制字段的修改;request
:在通过构造函数初始化Headers
实例,且mode
值不为no-cors
时激活,不允许修改禁止修改的请求头部;request-no-cors
:在通过构造函数初始化Headers
实例,且mode
值为no-cors
时激活,不允许修改非简单头部;response
:在通过构造函数初始化Response
对象时激活,不允许修改禁止修改的响应头部;immutable
:在通过error()
或redirect()
静态方法初始化Response
对象时激活,不允许修改任何头部。
4. Request
对象
(1) 创建Request
对象
可以通过构造函数来创建,需要传入一个input
参数,一般是URL
。
1 | const req = new Request("https://www.baidu.com"); |
也接收第二个参数,一个init
对象。与fetch()
接收的参数一样,不在赘述。
(2) 克隆Request
对象
- 使用构造函数来克隆;
- 使用
clone()
方法来克隆。
将一个Request
实例传给构造函数,可以得到该请求对象的一个副本。如果同时传入了init
对象,则init
对象的值会覆盖原来的同名字段。使用这种方法克隆请求对象之后,源请求对象会被标记为 已使用 ,即其bodyUsed
属性会变为true
。如果源对象与新对象不同源,则新对象的referrer
属性会被清除。此外,如果源对象的mode
属性为navigate
,则新对象的mode
属性会被转换为same-origin
。
第二中克隆方式是使用clone()
方法,这个方法会得到一模一样的实例。且不会把任何请求的请求体标记为已使用。
需要注意的是,不论使用哪种方式,只有bodyUsed
为false
的请求对象才可以被克隆,否则会抛出TypeError
。
1 | let req1 = new Request("https://www.baidu.com"); |
(3) 在fetch()
中使用Request
对象
给fetch()
传入Request
实例,相当于在fetch()
内部使用Request
构造函数克隆了一份传入的Request
实例。同样的,给fetch()
对象传入的第二个参数init
的值会覆盖Request
实例上的同名字段;请求体被标记为已使用的Request
实例传给fetch()
也会抛出TypeError
;被fetch()
使用过的请求对象,其请求体会被标记为已使用,无法再次传给fetch()
使用,当然,没有请求体的Request
实例不受此限制,因此,如要复用Request
实例,则每次传给fetch()
的应该是使用clone()
方法克隆出来的实例副本。
5. Response
对象
(1) 创建Response
对象
可以通过构造函数创建Response
对象,且无需任何参数。此时创建的实例各属性均为默认值,因为它并不代表实际的HTTP
响应。
(2) 克隆 Response
对象
主要使用clone()
方法,创建一个一模一样的实例副本,且不会将bodyUsed
属性标记为true
。
6. Request
、Response
、Body
混入
Request
和Response
对象都使用了Fetch API
的Body
混入,这个混入为两种类型提供了只读的body
属性 (实现为ReadableStream
) 、bodyUsed
(标记 body 流是否已读),以及一组方法,用于读取body
流的内容并转换为某种类型的JavaScript
对象类型。
Body
混入提供了 5 种方法,用于读取流内容并转换为对应的JavaScript
对象类型。
(1) Body.text()
返回一个期约,解决为utf-8
格式的字符串。如下例演示了在Response
对象上使用text()
。
1 | fetch("/cc-intro.com") |
(2) Body.json()
返回一个期约,解决为JSON
。
1 | fetch("/cc-intro.json") |
(3) Body.formdata()
返回一个期约,解决为FormData
实例。
1 | fetch('https://cc.com/form-data') |
(4) Body.arrayBuffer()
返回期约,解决为ArrayBuffer
实例。
(5) Body.blob()
返回期约,解决为Blob
实例。
(6) 一次性流
Body
混入是建立在ReadableStream
的基础之上的,因此主体流只能使用一次,这意味着以上五种方法只能选择其中的一种调用一次,再次调用以上任何方法则会报错。
(7) 使用ReadableStream
主体
由于对流的理解尚为浅薄,此处暂时就不班门弄斧来介绍相关要点了。
六、 Beacon API
在页面关闭(unload
)事件触发时,分析工具应停止收集信息,并将收集到的信息发送给服务器。异步XMLHttpRequest
或fetch()
都不太适合这个任务,因为浏览器会将unload
事件处理程序中的网络请求取消,毕竟对浏览器而言,没有任何理由需要在页面关闭后还继续发送请求。虽然同步XMLHttpRequest
可以完成这个任务,但是会造成用户关闭浏览器的延迟,影响用户体验。(实际上,设置fetch()
中的init
参数对象的keepalive
为true
,也可以允许在页面关闭后维持请求的生命周期。)
为此,W3C
引入了Beacon API
来解决这个问题。这个API
给navigator
对象增加了一个sendBeacon()
方法,发送一个POST
请求。此处不作详细介绍。
七、 Web Socket
Web Socket
(套接字)的目标是通过一个长时连接实现与服务器全双工、双向的通信。使用自定义协议:ws://
、wss://
,前者是非安全连接,后者是安全连接,而不再使用http://
、https://
。使用自定义协议,允许客户端和服务器之间发送非常少的数据,不会给HTTP
造成任何负担。
1. API
要创建一个新的Web Socket
,就要提供一个绝对URL
链接来实例化一个WebSocket
对象。浏览器同源策略不适用于Web Socket
,因此传入的URL
可以是打开到任意站点的链接。而是否与特定源的页面通信,就完全取决于服务器了。
1 | const ws = new WebSocket("ws://baidu.com"); |
浏览器在初始化Web Socket
后会立即简历连接。与XHR
类似,Web Socket
也有一个readyState
属性表示连接状态,但是其取值与XHR
不一样。
WebSocket.OPENING(0)
:正在连接;WebSocket.OPEN
(1):连接已经建立;WebSocket.CLOSING(2)
:正在关闭连接;WebSocket.CLOSE(3)
:连接已关闭。
WebSocket
对象没有readystatechange
事件,不过有与各个状态的其它事件。readyState
的值从 0 开始。
任何时候都可以调用close()
方法来关闭连接:ws.close()
。调用关闭方法之后,readyState
的值立即变为 2,并在关闭完成之后变为 3。
2. 发送 与 接收数据
要向服务器发送数据,可以使用send()
方法,传入一个字符串、ArrayBuffer
或者 Blob
。
1 | const ws = new WebSocket("wss://www.baidu.com"); |
当服务器向客户端发送消息时,WebSocket
对象会触发message
事件,可以在onmessage
事件处理程序中进行处理。该事件与其它消息协议类似,接收一个event
事件对象,且可以通过event.data
来访问到有效载荷:
1 | ws.onmessage = (event) => { |
与send()
方法相似,接收到的event.data
也可能是ArrayBuffer
或Blob
。这由WebSocket
对象的binaryType
决定,该属性可能是blob
或arraybuffer
。
3. 其它事件
在WebSocket
的连接生命周期中,有可能会触发其它三个事件:
open
:在成功建立连接时触发;error
:在发生错误时触发,触发后连接无法存续;close
:在成功关闭连接时触发。
WebSocket
事件不支持DOM LEVEL 2
事件监听(即addEventListener()
方式),因此需要使用DOM LEVEL 0
风格的事件处理程序来监听:
1 | const ws = new WebSocket("wss://www.baidu.com"); |
这三个事件中,只有close
事件的event
对象上有额外信息。该对象上有 3 个额外属性:
wasClean
:布尔值,表示连接是否干净地关闭;code
:来自服务器的数值状态码;reason
:字符串,包含服务器发来的消息。
可以将这些信息显示给用户,或者记录到日志里。