关于前端:Pride再谈风骚的跨源域方案上

6次阅读

共计 11078 个字符,预计需要花费 28 分钟才能阅读完成。

前言

本文是笔者于 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.localStoragewindow.name 等),只有大量属性能够无限拜访,比方上面两种:

      • 可读,window.length
      • 可写,window.location.href
  • 网络拜访限度
    次要体现在 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 办法的外围原理,它填充的是全局函数的数据。

流程

  1. 在全局定义好回调函数,也就是服务端 API 输入的 js 脚本中要调用的函数;
  2. 新建 script 标签,src 即是 API 地址,将标签插入页面,浏览器便会发动 GET 申请;
  3. 服务器依据申请生成 js 脚本并返回;
  4. 页面期待 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。

  1. 子域下部署一个代理页,设置其域为 demo.com,并能够蕴含发动 Ajax 的工具(jQuery、Axios 等);
  2. 主页面也设置域为 demo.com
  3. 主页面新建 iframe 标签链接到代理页;
  4. 当 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。

  1. 在全局定义好回调函数,也就是服务端 API 输入的 HTML 中要调用的函数;
  2. 主页面设置域为 demo.com
  3. 主页面新建 iframe 标签并指定 name;
  4. 新建 form 标签,指定 target 为方才的 iframe 的 name,并增加数据、配置申请;
  5. 提交表单,iframe 内跳转;
  6. 服务端接管到申请,根据申请参数生成 HTML 页面并返回,其域设为 demo.com
  7. 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}

流程

  1. 主页面新建 iframe 标签并指定 name;
  2. 新建 form 标签,指定 target 为方才的 iframe 的 name,并增加数据、配置申请;
  3. 提交表单,iframe 内跳转;
  4. 服务端接管到申请,根据申请参数生成 HTML 页面并返回;
  5. iframe 加载 HTML,运行其中脚本将数据设置到 window.name,并重定向;
  6. iframe 再次加载 HTML,实现时触发 load 事件;
  7. 主页面监听到 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()

流程

  1. 主页面新建 iframe 标签并指定 name;
  2. 新建 form 标签,指定 target 为方才的 iframe 的 name,并增加数据、配置申请;
  3. 提交表单,iframe 内跳转;
  4. 服务端接管到申请,根据申请参数生成 HTML 页面并返回;
  5. iframe 加载 HTML,运行其中脚本批改主页面的 hash;
  6. 主页面监听 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 标准。

正文完
 0