前言
本文是笔者于 2008 年写的《当年那些风骚的跨域操作》的重制威力加强版。
古云温故而知新,重看当年的文章,还是感觉有颇多有余和疏漏,思考深度也还欠缺,故进行重制。
自己集体能力无限,欢送批评指正。
正名与致歉
结尾先来个一鞠躬。
吐槽一下翻译,浏览各类 Wiki 和 RFC 里英文名都是“cross-origin”,origin 应该翻译成“源”、“起源”,合起来精确的翻译是“跨源”。但不知怎么搞的,在中文区以讹传讹地变成了“跨域”(cross-domain),这也造成了很大的误会,上面有讲到 domian 和 origin 的关系,这糟心的翻译让多少萌新混同了这两个概念(包含我)。
因而,本文只会用”跨源“这个精确翻译,也为本人以前文章的谬误致歉。
演示案例
本次重制最重磅的一点是笔者实现了一套残缺的演示案例,前后端代码都有,前端无第三方依赖,服务端基于 Express,源码细节和盘托出,实践和实际完满联合。可在本地演示下述的所有跨源计划,有 NodeJS 环境就能玩,无需简单配置、编译和容器。传送门
首页截图:
同源策略(Same-Origin Policy)
1995 年,同源策略由 Netscape 公司引入浏览器。目前,所有浏览器都履行这个安全策略。
本文要讲的“跨源”,正是要在确保安全的前提下绕过这个策略的限度。
外围概念
同源策略的目标是确保不同源提供的文件(资源)之间是互相独立的,相似于沙盒的概念。换句话说,只有当不同的文件脚本是由雷同的源提供时才没有限度。限度能够细分为两个方面:
-
对象拜访限度
次要体现在 iframe,如果父子页面属于不同的源,那将有上面的限度:- 不能够互相拜访 DOM(Document Object Model),也就是无奈获得 document 节点,document 上面挂载的形式和属性,包含其所有子节点都无法访问。这也是 Cookie 遵循同源策略的起因,因为
document.cookie
不可能拜访。 -
对于 BOM 只能有大量权限,也就是说能够相互获得 window 对象,但全副办法和大部分属性都无奈用(比方
window.localStorage
、window.name
等),只有大量属性能够无限拜访,比方上面两种:- 可读,
window.length
。 - 可写,
window.location.href
。
- 可读,
- 不能够互相拜访 DOM(Document Object Model),也就是无奈获得 document 节点,document 上面挂载的形式和属性,包含其所有子节点都无法访问。这也是 Cookie 遵循同源策略的起因,因为
-
网络拜访限度
次要体现在 Ajax 申请,如果发动的申请指标源与以后页面的源不同,浏览器就会有上面的限度:- 拦挡响应:对于简略申请,浏览器会发动申请,服务器失常响应,但只有服务器返回的响应报文头不符合要求,就会疏忽所有返回的数据,间接报错。
- 限度申请:对于非简略申请,古代浏览器都会先发动预检申请,但只有服务器返回的响应报文头不符合要求,就间接报错,不会再发动正式申请了,换句话说这种状况下服务器是拿不到任何无关这次申请的数据的。
何为同源(Same-Origin)
origin 在 Web 畛域是有严格定义的,蕴含三个局部:协定、域和端口。
origin = scheme + domain + port
也就是说这三者都完全相同,能力叫同源。
举个例子,假如当初有一个源为 http://example.com
的页面,向如下源发动申请,后果如下:
origin(URL) | result | reason |
---|---|---|
http://example.com |
success | 协定、域和端口号均雷同(浏览器默认 80 端口) |
http://example.com:8080 |
fail | 端口不同 |
https://example.com |
fail | 协定不同 |
http://sub.example.com |
fail | 域名不同 |
跨源计划(Cross-Origin)
同源策略提出的时代还是传统 MVC 架构(jsp,asp)流行的年代,那时候的页面靠服务器渲染实现了大部分填充,开发者也不会保护独立的 API 服务,所以其实跨源的需要是比拟少的。
新时代前后端的拆散和第三方 JSSDK 的衰亡,咱们才开始发现这个策略尽管大大提高了浏览器的安全性,但有时很不不便,正当的用处也受到影响。比方:
- 独立的 API 服务为了方便管理应用了独立的域名;
- 前端开发者本地调试须要应用近程的 API;
- 第三方开发的 JSSDK 须要嵌入到他人的页面中应用;
- 公共平台的凋谢 API。
于是乎,如何解决这些问题的跨源计划就被纷纷提出,堪称百家争鸣,其中不乏令人惊叹的骚操作,尽管当初已有规范的 CORS 计划,但对于深刻了解浏览器与服务器的交互还是值得学习的。
JSON-P(自填充 JSON)
JSON-P 是各类跨源计划中风行度较高的一个,当初在某些要兼容旧浏览器的环境下还会被应用,驰名的 jQuery 也封装其办法。
原理
请勿见名知义,名字中的 P 是 padding,“填充”的意思,这个办法在通信过程中应用的并不是一般的 json 格局文本,而是“自带填充性能的 JavaScript 脚本”。
如何了解“自带填充性能的 JavaScript 脚本”?看看上面的例子。
假如全局(Window)上有这个 getAnimal 函数,而后通过 script 标签的形式,引入一个调用该函数并传入数据的脚本,就能够实现跨源通信。
// 全局上有这个函数
function getAnimal(data){
// 获得数据
var animal = data.name
// do someting
}
另一个脚本:
// 调用函数
getAnimal({name: 'cat'})
也就是说利用浏览器引入 JavaScript 脚本时会主动运行的特点,就能够用来给全局函数传递数据。如果把这段调用函数的脚本作为服务端 API 的输入,就能够以此实现跨源通信。这就是 JSON-P 办法的外围原理,它填充的是全局函数的数据。
流程
- 在全局定义好回调函数,也就是服务端 API 输入的 js 脚本中要调用的函数;
- 新建 script 标签,src 即是 API 地址,将标签插入页面,浏览器便会发动 GET 申请;
- 服务器依据申请生成 js 脚本并返回;
- 页面期待 script 标签就绪,就会主动调用全局定义的回调函数,获得数据。
【PS】不只是 script 标签,所有能够应用 src 属性的标签都能够不受同源策略限度发动 GET 申请(CSP 未配置的状况),比方 img、object 等,但能主动运行 js 代码的只有 script 标签。
错误处理
- 前端通过 script 标签的 error 事件能够捕捉到网络谬误,但并不可知具体的谬误起因,也就是说无奈获取到服务器的响应状态码;
- 服务端返回的脚本如果运行谬误,前端只能通过全局 error 事件捕捉。
实际提醒
-
前端
- 为了防止净化全局,不倡议前端生成大量随机名字的全局函数,能够用一个对象保留所有回调函数,并给每一个回调函数惟一的 id,全局仅裸露对立的执行器,依附 id 去调用回调函数;
- 如果听从上一条的倡议,全局对象内回调函数须要及时清理;
- 每次申请都要生成新的 script 标签,应该在实现后及时清理;
- 为了灵活性,还可与服务端约定将回调函数名作为参数传递,保留多个全局对象状况的扩大空间。
-
服务端
- 只需接管 GET 办法的申请,其余办法可断定为非法;
- 只能在申请的 URL 里获取参数,比方 query 或 path;
- 响应报文头 content-type 设为 text/javascript;
- 强烈建议敞开 HTTP 协定缓存,免得数据不统一,办法参考笔者无关 HTTP 的文章;
- 返回的脚本以纯文本格式写入响应报文体,因为脚本是间接运行的,应特地留神 XSS 攻打。
前端“一个对象保留所有回调函数”的设计思路:
function initJSONPCallback() {
// 保留回调对象的对象
const cbStore = {}
// 这里造成了一个闭包,只能用特定办法操作 cbStore。return {
// 对立执行器(函数)。run: function (statusCode, data) {const { callbackId, msg} = data
try {
// 运行失败分支。if (...) {cbStore[callbackId].reject(new Error(...))
return
}
// 运行胜利分支。cbStore[callbackId].resolve(...)
} finally {
// 执行清理。delete cbStore[callbackId]
}
},
// 设置回调对象,发动申请时调用。set: function (callbackId, resolve, reject) {
// 回调对象蕴含胜利和失败两个分支函数。cbStore[callbackId] = {
resolve,
reject
}
},
// 删除回调对象,清理时调用。del: function (callbackId) {delete cbStore[callbackId]
}
}
}
// 初始化
const JSONPCallback = initJSONPCallback()
// 全局裸露执行器,这也是 API 返回脚本调用的函数。window.JSONPCb = JSONPCallback.run
具体代码请参考演示案例 JSONP 局部源码。
总结
-
长处
- 简略疾速,相比须要 iframe 的计划的确快(演示案例里体验一下就晓得);
- 反对上古级别的浏览器(IE8-);
- 对域无要求,可用于第三方 API。
-
毛病
- 只能是 GET 办法,无奈自定义申请报文头,无奈写入申请报文体;
- 申请数据量受 URL 最大长度限度(不同浏览器不一);
- 调试艰难,服务器谬误无奈检测到具体起因;
- 须要非凡接口反对,不能应用规范的 API 标准。
SubHostProxy(子域名代理)
子域名代理在特定的环境条件下是很实用跨源计划,它能提供与失常 Ajax 申请无差别的体验。
原理
先搞清楚何为子域(domain)?域名的解析是从右往左的,咱们去申请域名,就是申请最靠右的两段(以点为分段),而之后的局部是能够给所有者自定义的,你想多加几段都能够,这些衍生的域就是子域。举个例子,api.demo.com
就是 demo.com
的子域。
实践上例子里的两个算是不同的域,按照下面提到的 domain 是 origin 的一部分,因而也算是不同的源,但浏览器容许将页面 document 的域改为以后域的父级,也就是在 api.demo.com
的页面运行如下代码就能够改为 demo.com
,但这种批改只对 document 的权限有影响,对 Ajax 是无影响的。
// 在 api.demo.com 页面写如下代码
document.domain = 'demo.com'
【PS】
document.domain
的特点:只能设置一次;只能更改域局部,不能批改页面的端口号和协定;会重置以后页面的端口为协定默认端口(即 80 或 433);仅对 document 起作用,不影响其余对象的同源策略。
因而,该计划的原理就是通过这种办法使父级页面领有子域页面 document 的拜访权限,子域恰好又是 API 的域,进而通过子域页面代理发动申请,实现跨源通信。
流程
假如服务端 API 的域为 api.demo.com
,页面域为 demo.com
,独特运行在 http 协定,端口为 80。
- 子域下部署一个代理页,设置其域为
demo.com
,并能够蕴含发动 Ajax 的工具(jQuery、Axios 等); - 主页面也设置域为
demo.com
; - 主页面新建 iframe 标签链接到代理页;
- 当 iframe 内的代理页就绪时,父页面就能够应用
iframe.contentWindow
获得代理页的控制权,应用其发动 Ajax 申请。
错误处理
- iframe 的 error 事件在大部分浏览器是有效的(默认),因而 iframe 内谬误对主页面来说是不可知的;
- 通过 iframe 的 load 事件能够查看代理页是否被加载,以此间接判断是否有网络谬误,但并不可知具体的谬误起因,也就是说无奈获取到服务器的响应状态码;
- 当主页面获取代理页的控制权后,错误处理与失常 Ajax 无异。
实际提醒
-
前端
- 加载代理页是须要耗时的(其实挺慢的),因而要留神发动申请的机会,免在代理页还未加载完的时候申请;
- 并不需要每次申请都加载新的代理页,强烈建议只保留一个,多个申请共享;
- 如果听从上一条的倡议,还需思考代理页加载失败的状况,防止一次失败后后续均不能够;
- 能够应用预加载的形式提前加载代理页,免得减少申请的工夫;
- 主页面必须要应用
document.domain
设置,即是以后域曾经满足要求,也就是说以后页面尽管曾经域是xxx
,但还是得调用一遍document.domain='xxx'
。
-
服务端
- 只能应用规范的 80(http)或 443(https)端口部署(或应用反向代理);
- 代理页的域必须与 API 的域是统一的,并且与主页的域面有独特的父级(或主页面的域就是父级);
- 实践上代理页只有是执行了
document.domain=xxx
的 HTML 格式文件即可,因而能够尽量精简。
共享 iframe 的设计思路:
// 将创立 iframe 用 promise 封装,并保存起来。let initSubHostProxyPromise = null
// 每次申请之前都应先调用这个函数。function initSubHostProxy() {if (initSubHostProxyPromise != null) {
// 如果 promise 曾经存在,则间接返回,因为这个 promise 曾经 resolve,其实就相当于返回了已有的 iframe。return initSubHostProxyPromise
}
// 没有则从新创立。initSubHostProxyPromise = new Promise((resolve, reject) => {const iframe = document.createElement('iframe')
// 填入代理页地址。iframe.src = '...'
iframe.onload = function (event) {
// 这是一种 hack 的检测谬误的办法,见演示案例 README。if (event.target.contentWindow.length === 0) {
// 失败分支
reject(new Error(...))
setTimeout(() => {
// 清理掉失败的 promise,这样下次就会从新创立。initSubHostProxyPromise = null
// 这里还需移除 iframe。document.body.removeChild(iframe)
})
return
}
// 胜利分支,返回 iframe DOM 对象。resolve(iframe)
}
document.body.appendChild(iframe)
})
return initSubHostProxyPromise
}
具体代码请参考演示案例 SubHostProxy 局部源码。
总结
-
长处
- 能够发送任意类型的申请;
- 能够应用规范的 API 标准;
- 能提供与失常 Ajax 申请无差别的体验;
- 谬误捕捉不便精确(除了 iframe 的网络谬误);
- 反对上古级别的浏览器(IE8-)。
-
毛病
- 对域有严格要求,不能用于第三方 API;
- iframe 对浏览器性能影响较大;
- 无奈应用非协定默认端口。
HTML-P/MockForm(自填充 HTML/ 模仿表单)
网上个别称这种计划是“模仿表单”,但我感觉并不精确,应用表单发动申请并不是它的外围特色(前面也还有几种计划用到),它的外围应该是“自填充 HTML”。
原理
我将它称为 HTML-P 是借鉴了 JSON-P 的叫法,它的思路也与 JSON-P 计划很像,服务端 API 返回一个 js 脚本能够主动运行进行数据填充,那间接返回整个 HTML 页面不也能够。
但实际上 HTML 要实现数据填充还是有限度的,首先就是同源限度,父子页面如果不同源,就无奈相互拜访,解决办法天然是“子域代理”里提到的 document.domain
批改大法,但它的目标恰好与“子域代理”相同,通过批改 document 的域,使子页面获取主页面的拜访权限,以此对主页面的数据填充,实现跨源通信。
// API 返回蕴含如下脚本的 HTML,就可拜访父级页面的全局函数进行数据填充。document.domain = 'xxx'
window.parent.callbackFunction(...)
至于表单的作用,其实是利用了表单的 target 的属性,当表单 submit 的时候它会使指定 name 的 iframe 进行跳转,跳转其实就是发动申请,因而浏览器表单组件原生反对的申请办法都能够应用,也正因为应用了表单发动申请,服务端 API 必须返回一个 HTML 格局的文本。
流程
假如服务端 API 的域为 api.demo.com
,页面域为 demo.com
,独特运行在 http 协定,端口为 80。
- 在全局定义好回调函数,也就是服务端 API 输入的 HTML 中要调用的函数;
- 主页面设置域为
demo.com
; - 主页面新建 iframe 标签并指定 name;
- 新建 form 标签,指定 target 为方才的 iframe 的 name,并增加数据、配置申请;
- 提交表单,iframe 内跳转;
- 服务端接管到申请,根据申请参数生成 HTML 页面并返回,其域设为
demo.com
; - iframe 实现 HTML 的加载,子页面调用主页面全局定义的回调函数,主页面获得数据。
错误处理
- iframe 的 error 事件在大部分浏览器是有效的(默认),因而 iframe 内谬误对主页面来说是不可知的;
- 通过 iframe 的 load 事件能够查看代理页是否被加载,以此间接判断是否有网络谬误,但并不可知具体的谬误起因,也就是说无奈获取到服务器的响应状态码;
- 子页面调用主页面产生的谬误属于 iframe 内谬误,因而也是不可知的。
实际提醒
-
前端
- 为了防止净化全局,不倡议前端生成大量随机名字的全局函数,能够用一个对象保留所有回调函数,这点能够参考下面 JSON-P;
- 主页面必须要应用
document.domain
设置,即是以后域曾经满足要求。 - 因为 iframe 内的页面每次申请都不同,因而能够复用 iframe 标签,但不可复用页面;
- 并发时会同时生成多个 iframe 页面,这将导致性能极度降落,并发场景并不实用该计划;
- form 和 iframe 标签应该在实现后及时清理;
-
服务端
- 只能应用规范的 80(http)或 443(https)端口部署(或应用反向代理);
- API 的域与主页的域面有独特的父级(或主页面的域就是父级);
- 响应报文头 content-type 设为 text/html;
- 强烈建议敞开 HTTP 协定缓存,免得数据不统一,办法参考笔者无关 HTTP 的文章;
- 返回的 HTML 以纯文本格式写入响应报文体,因为其中的脚本是间接运行的,应特地留神 XSS 攻打;
- 生成的 HTML 应尽量精简。
具体代码请参考演示案例 MockForm 局部源码。
总结
该计划能够说是“JSON-P”与“子域代理”的缝合版,优缺点均有继承。
-
长处
- 能够发送任意类型的申请(以浏览器 form 标签反对为准);
- 相比“子域代理”来说,无需代理页算是个长处,
- 反对上古级别的浏览器(IE8-)。
-
毛病
- 对域有严格要求,不能用于第三方 API;
- iframe 对浏览器性能影响较大,并且并发须要多个 iframe,根本不能用于须要并发的场景;
- 无奈应用非协定默认端口。
- 谬误捕捉艰难,服务器谬误无奈检测到具体起因,运行谬误也无奈捕捉;
- 须要非凡接口反对,不能应用规范的 API 标准。
WindowName
这是一个以 window.name 个性为外围的计划。
原理
这计划利用了 window.name 的个性:一旦被赋值后,当窗口(iframe)被重定向到一个新的 url 时不会扭转它的值。尽管 window.name 仍然遵循同源策略,只有同源能力读取到值,但咱们只有在非同源页面写入值,再重定向到同源页面读取值即可实现跨源通信。
发动申请的办法与“HTML-P”雷同,都是用 form 触发 iframe 跳转实现。
// 通过 iframe 的 load 事件获得 window.name 的值。iframe.onload = function (event) {const res = event.target.contentWindow.name}
流程
- 主页面新建 iframe 标签并指定 name;
- 新建 form 标签,指定 target 为方才的 iframe 的 name,并增加数据、配置申请;
- 提交表单,iframe 内跳转;
- 服务端接管到申请,根据申请参数生成 HTML 页面并返回;
- iframe 加载 HTML,运行其中脚本将数据设置到 window.name,并重定向;
- iframe 再次加载 HTML,实现时触发 load 事件;
- 主页面监听到 iframe 的 load 事件,获取其 window.name 的值。
错误处理
- iframe 的 error 事件在大部分浏览器是有效的(默认),因而 iframe 内谬误对主页面来说是不可知的;
- 通过 iframe 的 load 事件能够查看代理页是否被加载(非同源须要 hack 办法),以此间接判断是否有网络谬误,但并不可知具体的谬误起因,也就是说无奈获取到服务器的响应状态码;
- 其余谬误可失常捕获即可。
实际提醒
-
前端
- form 和 iframe 相干留神点与“HTML-P”雷同;
- 重定向到同域的页面实践上无需任何内容,只有有 HTML 格局即可,应尽量精简,而且因为无需扭转,可进行长期缓存;
- 尽管实践上 iframe 的 load 事件会触发两次(一次非同源页、一次同源页),但实际上只有 load 触发前重定向,非同源页面的 load 事件是不会接管到的;
- 重定向应应用
window.location.replace
,这样才不会产生 history,会影响主页面的后退操作; - 为了灵活性,倡议将重定向页面的 url 传递给服务端。
-
服务端
- 响应报文头 content-type 设为 text/html;
- 强烈建议敞开 HTTP 协定缓存,免得数据不统一,办法参考笔者无关 HTTP 的文章;
- 返回的 HTML 以纯文本格式写入响应报文体;
- 生成的 HTML 应尽量精简。
具体代码请参考演示案例 WindowName 局部源码。
总结
-
长处
- 能够发送任意类型的申请(以浏览器 form 标签反对为准);
- 对域无要求,可用于第三方 API;
- 反对上古级别的浏览器(IE8-)。
-
毛病
- iframe 对浏览器性能影响较大,两次跳转雪上加霜,并且并发须要多个 iframe,根本不能用于须要并发的场景;
- 近乎是空白的同源重定向页,能够说是无意义的流量,影响流量统计;
- 谬误捕捉艰难,服务器谬误无奈检测到具体起因,运行谬误也无奈捕捉;
- 须要非凡接口反对,不能应用规范的 API 标准。
WindowHash
这是一个以 url 上 hash 局部为外围的计划。
原理
这个计划利用了 window.location.hash
的个性:不同域的页面,能够写不可读。而只扭转哈希局部(井号前面)不会导致页面跳转。也就是能够让非同源的子页面写主页面 url 的 hash 局部,主页面通过监听 hash 变动,实现跨源通信。
发动申请的办法与“HTML-P”雷同,都是用 form 触发 iframe 跳转实现。
// 古代浏览器有 hashchange 事件能够监听。window.addEventListener('hashchange', function () {
// 读取 hash
const hash = window.location.hash
// 清理 hash
if (hash && hash !== '#') {location.replace(url + '#')
} else {return}
})
// 降级计划,循环读取 hash 进行“监听”。var listener = function(){
// 读取 hash
var hash = window.location.hash
// 清理 hash
if (hash && hash !== '#') {location.replace(url + '#')
}
// 持续监听
setTimeout(listener, 100)
}
listener()
流程
- 主页面新建 iframe 标签并指定 name;
- 新建 form 标签,指定 target 为方才的 iframe 的 name,并增加数据、配置申请;
- 提交表单,iframe 内跳转;
- 服务端接管到申请,根据申请参数生成 HTML 页面并返回;
- iframe 加载 HTML,运行其中脚本批改主页面的 hash;
- 主页面监听 hash 的变动,每次获取 hash 值后清空 hash。
错误处理
- iframe 的 error 事件在大部分浏览器是有效的(默认),因而 iframe 内谬误对主页面来说是不可知的;
- 通过 iframe 的 load 事件能够查看代理页是否被加载(非同源须要 hack 办法),以此间接判断是否有网络谬误,但并不可知具体的谬误起因,也就是说无奈获取到服务器的响应状态码;
- 其余谬误可失常捕获即可。
实际提醒
-
前端
- form 和 iframe 相干留神点与“HTML-P”雷同;
- 设置主页面 hash 应该用
window.location.replace
,这样才不会产生 history,会影响主页面的后退操作; - 每次 hash 设置都须要肯定的冷却,并发可能产生错了;
- 没必要每次申请都去监听 hashchange 事件,能够在初始化时设置一个对立事件处理器,用一个对象将每次申请的回调保存起来,调配惟一的 id,通过对立的事件处理器按 id 调用回调;
- 如果听从上一条的倡议,全局对象内回调函数须要及时清理;
- 因为 iframe 内是非同源页面(服务端生成),不可知主页面 url,因而须要将 url 通过参数传递给服务端。
-
服务端
- 响应报文头 content-type 设为 text/html;
- 强烈建议敞开 HTTP 协定缓存,免得数据不统一,办法参考笔者无关 HTTP 的文章;
- 返回的 HTML 以纯文本格式写入响应报文体;
- 生成的 HTML 应尽量精简。
前端“对立事件处理器”的设计思路:
function initHashListener() {
// 保留回调对象的对象
const cbStore = {}
// 设置监听,只需一个。window.addEventListener('hashchange', function () {
// 解决 hash。...
try {
// 运行失败分支。if (...) {cbStore[callbackId].reject(new Error(...))
return
}
// 运行胜利分支。cbStore[callbackId].resolve(...)
} finally {
// 执行清理。delete cbStore[callbackId]
}
})
// 这里造成了一个闭包,只能用特定办法操作 cbStore。return {
// 设置回调对象的办法。set: function (callbackId, resolve, reject) {
// 回调对象蕴含胜利和失败两个分支函数。cbStore[callbackId] = {
resolve,
reject
}
},
// 删除回调对象的办法。del: function (callbackId) {delete cbStore[callbackId]
}
}
}
// 初始化,每次申请都调用其 set 办法设置回调对象。const hashListener = initHashListener()
具体代码请参考演示案例 WindowHash 局部源码。
总结
-
长处
- 能够发送任意类型的申请(以浏览器 form 标签反对为准);
- 对域无要求,可用于第三方 API;
- 反对上古级别的浏览器(IE8-)。
-
毛病
- iframe 对浏览器性能影响较大,并且并发须要多个 iframe,根本不能用于须要并发的场景;
- 并发场景很容易呈现 hash 操作撞车的问题,这个问题如果采纳循环读取 hash 的办法监听则更加重大,除非有更加紧密的防撞车机制,否则强烈不倡议并发应用;
- 申请数据量受 URL 最大长度限度(不同浏览器不一);
- 谬误捕捉艰难,服务器谬误无奈检测到具体起因,运行谬误也无奈捕捉;
- 须要非凡接口反对,不能应用规范的 API 标准。