文章背景
通信,这个词我相信大家一定不陌生。SPA中有许多的实现如eventEmitter、Props、context以及优秀的Redux、Vuex库。但是本文主要讨论的是跨页面的通信。
为什么写
工作中遇上了这么个需求,需要在A页面点击后打开一个新页面B,并触发EventEmitter打开侧边栏。当然解决需求不难,但是希望能得到一个完整的解决方案,而不仅仅针对单一需求。最后罗列了一下场景:
- Tab/页面AB均已打开,两页面之间进行数据传递
2. 在页面A中操作打开新页面B、让B知道不仅仅是打开就完事了,还得干活。
3. 待补充。。。
所以针对这几种情况做了一些调研。项目中大多采用URL携参数来解决场景2,此外针对场景1没有落地的需求。
那么就得给花了的时间一个交代,也希望能有幸帮上之前不了解的web developer吧。
现有解决方案
一、localStorage
场景:
1. 页面AB均已存在,可以通过onstorage监听共享域获取值。
2. 页面A存在,点击打开新页面B后进行通信。可以在B页面内通过localStorage.getItem(key)获取值。
![d3b3d3c905ed1b681c9ce97283b20f68.png](https://i-blog.csdnimg.cn/blog_migrate/a37892d927f9dacceb3a229859ef7894.jpeg)
注意点:
- 页面A修改localStorage,不会触发onstorage事件
- 如果localStorage值未发生变化,不会触发onstorage事件
- 部分浏览器隐身模式下,无法设置localStorage。如safari
优势:浏览器支持效果好、API直观。onstorage的设定,就像是为了页面间通信做的准备。
二、url
通过url中携带query参数,在B页面初始化时获取参数。
场景:仅可用于A页面下打开新页面B的场景
优势:浏览器支持效果好、持久化、无需配置。
缺陷:语义不明的内部状态暴露在url中不合理 ,同时数据较多时导致url臃肿。
三、postMessage
A页面通过window.open获取B页面的句柄。通过otherWindow.postMessage传递数据,在B页面通过onmessage获取A页面传递的参数,也可以通过e.source.postMessage传递信息给A页面。
场景:
- AB页面均存在时,通过postMessage与onmessage组合即可
- A存在,打开新页面B进行传递时,A页面需要监听B页面完成message注册后才能传递。
![bc2da7497375444c52939cb5e0db2844.png](https://i-blog.csdnimg.cn/blog_migrate/4018ac5daa526bd353b04d080fd6274e.jpeg)
注意点
- Window.open方法的第二个参数为设置子页面的name,再次调用open方法会触发同name属性子页面刷新。
- 该方案允许跨源传递数据,注意数据的安全性。如果知道通信窗口的origin属性,建议配置第二个参数进行限制接收方(推荐配置)
- 异步请求后window.open会被浏览器拦截,当然也可以通过改变otherWindow.location属性来避开
优点:点对点通信,跨域的场景下可以使用
劣势:使用面小,高度依赖window.open。比如:通过a标签打开B页面,无法使用。
四、 BroadCast channel
它的出现,是为了解决postMessage只能点对点通信的问题,广播形式能够在同域的页面下进行一对多的通信。通过相同的口令连接到同一个频道(像是面对面建群?)
场景:
- AB页面均存在,可通过onmessage与postMessage随意通信,处理
- 打开新页面B,需要监听B页面完成message注册后才能传递。
![87dc7cb55975042eff5d3884dc3944f8.png](https://i-blog.csdnimg.cn/blog_migrate/58d80216ef68291f356e845809e55ad0.jpeg)
优点:实现一对多的数据传递(同名频道内),API直观。功能相比下更为全面
缺点:浏览器支持效果如下图,不乐观。
![bea390b31000ecc374fbbc3f33c60c52.png](https://i-blog.csdnimg.cn/blog_migrate/c4d746f279e1e0232e8cccef0dcda54c.png)
五、cookie
类似于localStorage的低配版,使用document.cookies保存。(仅为可能性讨论,不推荐)
场景:
- AB页面都存在,只能通过轮询检查cookie是否变化。。。emmm
- 打开新页面B进行通信,问题不大,同样在初始化后,获取cookie值进行处理
优点:。。。
缺点:cookie自身的特殊性,谁也不希望每次请求都带上一堆不相干的参数。。
六、通过window.***绑定参数
想法来自于:突发奇想。方法过于简单粗暴,通过B页面的window.opener.***获取到参数。但是不太推荐。因为这个方案只是突发奇想,解决的过程不优雅,无法形成统一的解决方案。
![62bfaba884c863ec2614ab18cc34fd20.png](https://i-blog.csdnimg.cn/blog_migrate/3460214b1de35d553699f5fe01476aa1.jpeg)
场景:仅适用于打开新页面B传递参数
优点:谁也顶不住的简单方便
缺点:称不上解决方案,过于hack。
其他方案
sharedworker
该功能的设计是页面间解决共享计算逻辑,但是也可以具有通信的手段,所以列出来:
![6a5e2de24a523a95eb7db00cebfa91f0.png](https://i-blog.csdnimg.cn/blog_migrate/1c0e42b84f83258465ba062292d3555e.jpeg)
场景:符合上述2种场景。
优点:异步处理,不占用主线程。
servieWorker
PWA的实现基础,主要用于处理网络,离线体验。可用于通信,但是不好用。附mozilla链接,感兴趣可以看看
Service Worker APIdeveloper.mozilla.org![4333bc611af6270d8df17eb9a31f3d3c.png](https://i-blog.csdnimg.cn/blog_migrate/7ae5a47ce59de7f84fc94e9d02719c58.jpeg)
浏览器数据库技术
调研中感觉在前端开发时已经不仅仅是js了,跨页面的通信在浏览器端都出现了许多方案。如下面介绍的sql语言
indexDB
数据存储技术,相比与localStorage来说对JS更友好,支持null、undefined、结构化数据(文件、blob)等所有数据类型。同时也支持离线使用。
场景:支持数据共享,若要即时通信,需要搭配轮询
优点:功能更为全面,提供索引实现高性能的查询。支持所有js类型的存储、支持好
缺点:没有监听事件无法用于即时通信。数据库对于前端来说相对陌生。
websql
浏览器端数据库(关系型),不过已经废弃,由indexDB取代了
总结
上面列举了目前已有的解决方案(不包含服务端如SSE、Websocket)。
在选择的技术,主要考量的三个方面:
1、兼容性。包括浏览器支持度、对历史代码的影响程度
2、通用性。能否覆盖需求、是否具有拓展性
3、便捷性。开发便捷程度
主要讨论下PostMessage、broadCastChannel。
在了解boradCastChannel时,抛开浏览器支持性来说,是个不错的解决方案。即时通信、随时关闭频道、不会产生因为LocalStorage不及时清理而引起的问题。
如场景2描述的:如A页面内打开B页面并存储了值,在B初始化时获取值并打开抽屉。若没能及时清理,会造成每次打开B页面都会打开抽屉。即使在B初始化后清理,也可能因为B页面未打开便关闭,造成未清理。
postMessage:点对点的通信,局限性很大。但是针对需求来说是完美cover的,但是无法对其进行扩展、如独立页面间的互相通信。所以无法因为符合需求就引入这种解决方案,而是需要与项目的解决方案保持一致。
以上分析后,符合笔者需要的解决方案为:localStorage。
当然缺陷都是可以补齐的,通过window.onclose监听搭配B页面初始化后清理存储值,当然方法记得多加注释。
感想
当然对于调研方案的人来说,花了很多时间收集、对比但是最后用回了老技术,心里是有些失落的,似乎毫无提升。但是往往都是如此、就像发现了多彩的蘑菇、但是多半是有毒的。也许要等到毒性可控的时候,才会因为它的鲜美被人们接受。
总而言之,了解新方案,总是没坏处的。