Ajax是异步的JavaScript和XML,就是使用XMLHTTPRequest对象与服务器通信。最大的优点是可以在不重载页面的情况下与服务器通信并更新部分页面内容。
XMLHttpRequest
所有现代浏览器(IE7+、Firefox、Chrome、Safari 以及 Opera)均支持XMLHttpRequest对象(IE5 和 IE6 使用 ActiveXObject对象)。
基本用法:
const xhr = new XMLHttpRequest();
原型属性:
XMLHttpRequest 继承了XMLHttpRequestEventTarget 和 EventTarget 的属性。
属性 | 说明 | 类型 |
readyState | (只读) 用于表示请求的五种状态 0(UNSET):XMLHttpRequest对象已创建且未调用open(); 1(OPENED): open()方法被调用服务器连接已建立且未发送即未调用send(); 2(HEADERS_RECEIVED):send()被调用且头部已被服务器接收,状态已可访问; 3(LOADING):下载中,responseText或responseXML已经包含部分数据; 4(DONE):请求已完成且响应完成。 | unsigned short(无符号短整数) |
response | (只读) 用于获取整个响应实体,响应体的类型由 responseType 来指定 | Blob | ArrayBuffer | Document | JSON | String | null(请求未完成或失败) |
responseText | (只读) 用于获取请求的响应文本 | DOMString | null(请求未完成或失败) |
responseType | 用于设置该值能够改变响应类型 | XMLHttpRequestResponseType |
status | (只读)用于获取请求的 响应状态码,200(OK)和 404(Not Found)以及 0 (请求未完成或出错) | unsigned short |
statusText | (只读)用于获取请求的 响应状态信息,包含一个状态码和消息文本 | DOMString |
timeout | 用于表示最大请求时间(毫秒),若超出该时间,请求会自动终止 | unsigned long |
upload | (只读) 用于在 upload 上添加一个事件监听来跟踪上传过程。继承自XMLHttpRequsetEventTarget,只有事件属性,也只能对其绑定事件来追踪它的进度。 | XMLHttpRequestUpload |
withCredentials | 用于指定跨域 Access-Control 请求是否应当带有授权信息,如Cookie 或授权首部字段 | Boolean |
原型方法
请求发送前
open(method,url[, async][, user][, password]):初始化一个请求,规定method请求类型、url请求地址、async异步或同步(默认是true - 异步,此时send方法会立即返回,而如果值为 false,则 send 方法直到接收到了服务器的返回数据才会返回)。url尾部的查询字符串中每个参数的名称和值都必须使用encodeURIComponent()进行编码,而且所有名称-值对都必须由和号(&)分隔。如果 method 不是有效的 HTTP 方法或 URL 地址不能被成功解析,将会抛出DOMException “SyntaxError” 异常,如果请求方法(不区分大小写)为 CONNECT、TRACE 或 TRACK 将会抛出 DOMException“SecurityError” 异常。
setRequestHeader(name,value):向请求设置HTTP头。name头名称,value对应值;可设置响应的MIME类型(表示文档、文件或字节流的性质和格式的媒体类型);可以调用多次。xhr不允许更改Referer 和 Host字段;调用设置后不能撤销,其他同 name 调用会向 header 中添加value,但不会覆盖它;
必须在open 与 send 之间调用,否则会抛出DOMException “InvalidStateError” 异常。参数name和value必须对应于HTTP请求头,否则会抛出 DOMException “SynataxError” 异常。所有浏览器基本默认会发送的头部字段:
Accept :浏览器能够处理的内容类型。
Accept-Charset :浏览器能够显示的字符集。
Accept-Encoding :浏览器能够处理的压缩编码。
Accept-Language :浏览器当前设置的语言。
Connection :浏览器与服务器之间连接的类型。
Cookie :当前页面设置的任何Cookie。
Host :发出请求的页面所在的域 。
Referer:发出请求的页面的URI。
User-Agent :浏览器的用户代理字符串。
overrideMimeType():用于重写XHR响应的MIME类型,强迫XHR对象将响应当作指定的形式来处理,不会改变Header。必须在send 之前调用,否则会抛出DOMException “InvalidStateError” 异常。
发送请求
send(body):发送网络请求。body 可以是Document | ArrayBuffer | DOMString | FormData | URLSearchParams | USVString | null,所有的事件绑定必须在send之前进行。若没有 调用open 或 send被调用会抛出DOMException “InvalidStateError” 异常。
注意:对于get请求,如果不想获得的是缓存结果,可给url加上唯一的id(比如实时可用时间戳)
请求发送后
abort():立刻终止已被发送的请求(readyState变为XMLHttpRequest.UNSENT(0),并且 status 属性置为 0),此时,应对该 xhr 实例解除引用(xhrInstance = null)。
接受响应后
getResponseHeader():获取响应http头中特定字段的值,如果响应头还没有被接收,或该响应头不存在,则返回 null。使用该方法获取某些响应头时,浏览器会抛出异常,因为W3C 的 XHR 标准中做了限制,规定无论是同域还是跨域,客户端均无法获取 response 中的 Set-Cookie、Set-Cookie2 这 2 个字段,而且 W3C 的 CORS 标准对于跨域请求也做了限制,规定对于跨域请求,客户端允许获取的响应头首部字段只限于简单的响应首部字段,比比如:
Expires Cache-Control Last-Modified Pragma Access-Control-Expose-Headers Content-Language Content-Type。 |
getAllResponseHeaders():获取响应http头中所有字段的值(Set-Cookie 和 Set-Cookie2 除外)。注意:使用该方法获取的响应头首部字段与在开发者工具 Network 面板中看到的响应头不一致。
应用示例——获取最后需修改日期并处理:
原型事件
状态变更
onreadystatechange,事件字符串readstatechange,当readyState属性发生改变时即触发 readystatechange 事件的时候被调用。只有它是XMLHttpRequest上定义的事件,其余事件均继承自XMLHttpRequestEventTarget 。
请求响应后
onloadstart,对应事件字符串为 loadstart,接受到数据响应时触发。
onload,对应事件字符串为load,用于当请求完成时触发,此时 readyState 值为 XMLHttpRequest.DONE(4)。
onloadend,对应事件字符串为loadend,当请求结束时触发,无论请求成功 ( load) 还是失败 (abort 或 error),该事件内无法知道是何种原因触发的结束。
onprogress,对应事件字符串为pregress用于当请求接收到数据的时候被周期性触发。使用该事件可以获取传输进度信息。progress 事件在使用 file: 协议的情况下是无效的。
异常处理
onabort,事件字符串为 abort,当请求停止时触发。
ontimeout,事件字符串为timeout,当请求超时时触发。
onerror,事件字符串为 error,当请求出现异常时触发。
除了readystatechange中事件的回调函数中的事件对象是标准Event外,其余均为ProgressEvent(继承自Event),它是测量如 HTTP 请求(一个XMLHttpRequest,或者一个 <img>,<audio>,<video>,<style> 或 <link> 等底层资源的加载)等底层进程进度的事件。四个只读属性:
lengthComputable,boolean,表示底层流程将需要完成的总工作量和已经完成的工作量是否可以计算,即进度是否可以被测量。
loaded,是一个 unsigned long 类型数据,表示底层进程已经执行的工作总量。可以用这个属性和 ProgressEvent.total 计算工作完成比例。当使用 HTTP 下载资源,它只表示内容本身的部分,不包括首部和其它开销。
total,是一个 unsigned long类型数据,表示正在执行的底层进程的工作总量。当使用 HTTP 下载资源,它只表示内容本身的部分,不包括首部和其它开销。
如果需要支持 Internet Explorer 6 和更老的浏览器:
FormData
FormData对象以键值对 key/value 的构造用于表单数据的序列化,可以将数据通过 XMLHttpRequest.send 方法发送出去,如果xhr中编码类型字段为multipart/form-data,则会使用和表单一样的格式。
const data = new FormData(form?: HTMLFormElement),会自动将form中的表单值也包含进去,包括文件内容也会被编码之后包含进去。
has(name),返回一个布尔值表明 FormData 对象是否包含某些键。
get(name),返回在 FormData 对象中与给定键关联的第一个值。
getAll(name),返回一个包含 FormData 对象中与给定键关联的所有值的数组。
delete(name),从 FormData 对象里面删除一个键值对。
entries(),返回一个包含所有键值对的iterator对象。不过,FormData也可以直接使用for...of... 进行迭代。
values(),返回一个包含所有值的iterator对象。
keys(),返回一个包含所有键的iterator对象。
append(name, value),追加设置表单名称-值;set(name, value),直接设置表单名称-值;区别在于若指定的键已经存在, set 会使用新值覆盖已有的值,而 append会把新值添加到该key的已有值集合的后面。
CORS(跨源资源共享)
默认情况下XHR对象具有跨域安全策略限制即只能访问同域资源(协议-域名-端口相同)(可能是浏览器直接限制发起请求,也可能是请求响应被浏览器拦截),跨域会抛出DOMExpection “INVALID_ACCESS_ERR” 异常。
CORS(Cross-Origin Resource Sharing,跨源资源共享)规定必需跨域访问资源时的通信机制。通过服务器设置响应头字段Access-Control-Allow-Origin来放开跨域限制。
CORS默认存在的限制:1. 不能使用 setRequestHeader() 设置自定义头部2. 不能发送和接收cookie 3. 调用 getAllResponseHeaders() 方法总会返回空字符串。
默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等)。通过将 withCredentials属性设置为 true,可以指定该请求应该发送凭据。如果服务器接受带凭据的请求,服务端 必须明确 Access-Control-Allow-Origin 的值,而不能使用通配符“*”。而且只有服务器端的响应中携带了 Access-Control-Allow-Credentials: true,浏览器才会把响应内容返回给请求的发送者。
检测XHR是否支持CORS的最简单方式:检查是否存在 withCredentials属性。再结合检测 XDomainRequest 对象是否存在。
浏览器将CORS请求分为两种:简单请求和非简单请求(即CORS预检请求)。
满足以下全部条件的请求即简单请求:
- http方法是get、post、head之一;
- 使用CORS规定的安全首部集合中的字段:Accept、Accept-Language、Content-Language、Content-Type (只能是text/plain、multipart/form-data、application/x-www-form-urlencoded之一)、DPR、Downlink、Save-Data、Viewport-Width、Width;
- 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象使用 XMLHttpRequest.upload 属性访问;
- 请求中没有使用 ReadableStream 对象。
Preflighted Requests(CORS预检请求——简单请求以外的请求,用于检查服务器是否支持 CORS 即跨域资源共享)的透明服务器验证机制支持开发人员使用自定义的头部、GET或POST之外的方法,以及不同类型的主体内容,发送和接收以下头部信息:
Access-Control-Allow-Origin (服务器)/Origin(客户端):允许的源/发送的源。
Access-Control-Allow-Methods /Access-Control-Request-Method :允许的方法,多个方法以逗号分隔。
Access-Control-Allow-Headers / Access-Control-Request-Headers:允许的头部,多个头部以逗号分隔。
Access-Control-Max-Age :应该将这个Preflight请求缓存多长时间(以秒表示)。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个 最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。
Access-Control-Allow-Credentials:由于CORS 预检请求本身不能包含凭据。因此预检请求的响应必须指定 Access-Control-Allow-Credentials: true 来表明后续客户端是否可以携带凭据进行实际的请求。
注意:在上述CORS 响应中设置的 cookie 适用一般性第三方 cookie 策略,即在SameSite 属性允许前提下生效。
当发生符合非简单请求的条件时,浏览器会自动先发送一个options请求,如果发现服务器支持该请求,则会再将真正的请求发送到后端,反之,如果浏览器发现服务端并不支持该请求,则会在控制台抛出错误。
浏览器CORS允许的应用场景:
- 由 XMLHttpRequest 或 Fetch 发起的跨域 HTTP 请求
- Web 字体(CSS 通过 @font-face 使用跨域字体资源)
- WebGL 贴图
- 使用 drawImage 将 Images/viedo 画面绘制到 Canvas
- 样式表(使用 CSSOM )
对于 Webview(使用 Cordova 或 Ionic)的移动应用程序,Android 将不会带来任何麻烦,但iOS上的新WKWebview将需要CORS。这意味着几乎必须始终将 Access-Control-Allow-Origin 标头设置为 * ,但实际上这并不理想。
Comet(服务器推送)
Comet即服务器向页面推送数据。比如应用于体育比赛的分数和股票报价等实时推送数据。优点:实时性好,性能好。缺点: 长期占用连接,丧失了无状态高并发的特点。
实现方式:长轮询和流两种。
短轮询(定时轮询):浏览器定时向服务器发送请求,看有没有更新的数据。由于需要不断的建立 HTTP 连接,严重浪费了服务器端和客户端的资源。客户端数越多,对服务器压力越大,因此短轮询不适用于那些同时在线用户数量比较大,并且很注重性能的 Web 应用。
长轮询:浏览器发送请求到服务器,服务器一直处于连接状态,直到服务器有数据发送给浏览器,浏览器接收完数据后随即发送新请求连接。明显减少了很多不必要的 http 请求次数,相比之下节约了资源,缺点在于,连接挂起也会导致资源的浪费。
长轮询与短轮询共同特点是浏览器都要在接收数据之前,先发起对服务器的请求连接(被动型服务器 的体现:服务器不会主动推送信息)。区别在于:
- 长轮询浏览器是接收完数据后才随即发起新请求,短轮询浏览器定时发起新请求。
- 长轮询服务保持一个请求直到有数据响应,短轮询服务器对每个请求立即响应。
- 若消息到达率未知,则长轮询提供更短的消息延迟。若消息到达率较高,则长轮询会发送更多的xhr请求。因此,若对消息延迟要求不高的话,则定时轮询能有效的合并一定时间内的消息而形成“消息聚合”,能有效的减少请求数量并提高移动设备的电池寿命。
流:浏览器仅向服务器发送一个请求(一个HTTP连接),而服务器保持连接打开,然后周期性地向浏览器发送数据。周期性的readyState变为3,客户端从上一次取出数据的末尾开始取出数据。
SSE(服务器发送事件)
通过SSE创建到服务器的连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的MIME类型必须是text/event-stream ,而且是浏览器中的JavaScript API能解析格式输出。SSE支持短轮询、长轮询和HTTP流,而且能在断开连接时自动确定何时重新连接。
const source = new EventSource(url):参数url规定发送更新数据的同源url。默认情况下,即使连接断开(服务器可通过返回的数据附加id,在连接断开时,客户端会向服务器发送一个包含名为Last-Event-ID的请求,保证下一次请求发送的数据段),还会重新连接,强制断开使用instance.close()方法。其instance具有属性readyState(0:正连接到服务器,1:打开了连接,2:关闭了连接),包括三个事件:
open (属性为onopen):在建立连接时触发。
message(属性为onmessage) :在从服务器接收到新事件时触发。
error (属性为onerror):在无法建立连接时触发。
服务器发回的数据以字符串形式保存在 event.data。
服务器可返回多个连续的以data: 开头的数据行,每个值之间以一个换行符分隔。只有在包含 data:的数据行后面有空行时,才会触发 message 事件,因此在服务器上生成事件流时记得添加。
选择SSE还是Web socket?
- WebSocket协议不同于HTTP,需要实现支持Web Socket协议的服务器。SSE是通过常规HTTP通信,因此现有服务器就可以满足需求。
- 双向通信(如聊天室),优先Web Sockets。在不能选择WebSockets的情况下,组合XHR和SSE也是能实现双向通信的。
四种前端即时通讯技术比较:从兼容性考虑:短轮询 > 长轮询 > 长连接 SSE > WebSocket;从性能方面考虑:WebSocket > 长连接 SSE > 长轮询 > 短轮询;
实现Ajax
实现Ajax(GET为例):
实现promise版的Ajax: