原创不易,但愿能关注下咱们,再顺手点个赞~~ |
本文首发于政采云前端团队博客: 基于本地存储的同源跨页面数据同步方案javascript
提起这个方案,还要从某个风和日丽的早晨提及。那日小编正忙着手上的各类需求,忽然后端的亲火急火燎的找到小编,说是有一个重要的用户,在使用 Word 在线编辑文档功能时,发现保存的文件被篡改了。一听到这,我心想这下摊上事儿了,妥妥的线上故障,但仍是故做镇定的开始排查是什么问题。前端
通过了日以继夜的排查后,小编发现是因为用户同时打开了两个在线编辑页面,而且在 A 页面的在线编辑工具还未关闭的状况下,去 B 页面也打开了在线编辑工具。java
说到这个在线编辑工具,它叫 pageOffice
,当他在线被触发启动时,会在本地打开一个相似软件的窗口,启动一个相对独立的服务。且这个服务前端经过 Web SDK 提供的 API 能进行控制的余地很是小,惟一的通讯方式只有 pageOffice
中操做触发页面上的回调函数。在和 pageOffice
的客服进行了一系列如同太极的沟通后,咱们仍是没能解决如何知道用户已经打开了 pageOffice
而且阻止用户在另外一个页面触发打开工具的方法。后端
上文提到的, pageOffice
打开之后就成为了相对独立的个体,因而乎,小编对它直接的各类软磨硬泡都宣告失败。进而小编放弃了探索对它的控制,转而思考两个页面之间通讯的控制。数组
平时我们对一个方法是否运行过,最经常使用的方式就是 “状态开关”。即存储用一个变量,相似于 ifOpen
之类的,将其设置为 ture
去记录当前方法已运行,再在其运行结束时设置为 false
,便可完成一个闭环。而咱们此次除了以上条件,还须要让别的页面也拥有这个变量,才能阻止别的页面在这个方法运行时再次触发这个方法。这听起来有点绕,不过下面有一个小图解来解释咱们此次问题的初步解决方案。浏览器
显而易见的,此处应有一个跨页面通讯的方案,可是因为这是同一个页面上的功能,因此咱们能够选择最简便的方案。缓存
提到跨页面数据存储,聪明的大家确定会想到本地存储 localStorage
,提到localStorage
小编就会想起它的兄弟 sessionStorage
,那就大体回顾一下它们两的特性吧:性能优化
看到这里想必你们已经看出来,本地存储 localStorage
彻底能够知足上图中描述的功能。可是回想一下题目中提到的 “反作用” 一词,你们是否心中暗想此事必不简单。markdown
小编解释一下:首先,因为 localStorage
不会自动清除的特性,当用户再次进入页面时,以前保存的 localStorage
里的数据会还在;其次,以前提到过,pageOffice 打开后就独立了,因此,这两个条件结合后就存在这样一个场景 —— 在 pageOffice 还在打开的时候,用户先把页面关闭了,以后再关闭 pageOffice
,此时,页面已经不存在了,因此 pageOffice 关闭时触发的回调函数,此时已经通知不到页面去改变存在 localStorage
里的变量。而再下一次打开页面时,因为localStorage
存的数据仍是上次未关闭 pageOffice
时的 ifOpen = true
, 因此,若是用户不自主清除本地缓存,将再也打不开 pageOffice
,小编把这种关闭页面在将来可能会形成负面影响的数据称为 “反作用”。session
为了清除上述方案带来的反作用,小编废寝忘食围绕反作用删除的时机想到了几种方案:
方案一:用 localStorage
储存一条有当前打开页面 Id 的数组,当页面关闭就过滤掉关闭页面的 Id,关闭页面直到最后数组长度为 1,而且 Id 就是当前页面的 Id 时,就清除掉localStorage
中反作用的数据。
这个方案的缺陷就是,咱们没法肯定页面的关闭时机,现有的在页面关闭时能触发的事件是beforeunload,可是很是不理想的是,这个事件在页面刷新的时候也会触发,若是刷新页面则会产生预期外的效果,这并非咱们想要的,即便在这个事件中区分当前触发的是刷新仍是关闭也是不太合理的,全部最后仍是选择更换别的方案。
方案二:因为关闭页面的时机没法肯定,因此小编考虑将其转存为页面上的变量或者换一种储存方式。
查阅了和 localStorage
有关的内容以后,发现现存有这么一个神奇的事件叫作 storage
事件,仔细阅读关于这个事件的相关文献后发现其有几个特色:
首先,它须要在同一浏览器打开两个同源的页面
其次,两个页面都注册了这个事件,而且有 localStorage 的变化,事件在其余页面返回最新变化的 localStorage 的 Key 和 Value
最后,这个事件并非用来监听当前页面本身的 localStorage 变化的
看起来这个事件彻底就是考虑到了咱们转存 localStorage
准备的,小编内心顿时就以为怎么会有这么善解人意的事件。
虽然有了这个事件的存在,可是咱们该如何顺利的帮助 localStorage
转型呢?
回想起上文提到的 sessionStorage
这个会话存储,一想到它可以在窗口关闭时自动清除,小编就想用它搞点事情。顺便一提,页面上的变量也是能够在页面关闭时自动清除的,不过当没有两个页面的时候,这种事件触发的变量一刷新就会丢失,可是 sessionStorage
刷新仍是会保留在当前页面存储中,因而,小编就萌生了这样一个 localStorage
和 sessionStorage
联合使用的想法。
这个方案最终的目的就是要把 localStorage
中的数据都转到 sessionStorage
,简单来讲也就是跨页面的 sessionStorage
的数据同步,而 localStorage
就是咱们跨页面的一座桥梁。因此,方案基本的实现原理就是:当数据变化时,咱们首先要作的就是把数据存在当前页的 sessionStorage
里,并触发一次 localStorage
的变化即存一次数据到localStorage
里,经过 storage
将数据运输到另外一个页面。
值得注意是,localStorage
的转型就是为了删除反作用,因此当把数据存入localStorage
后,下一步就是直接清除存入 localStorage
里的数据。
在这里小编封装了一个函数,数据传的是一个对象,这样就能够一次同步多个数据啦,先进入一下图解环节,让你们有个初步的理解。
原理函数:
// 触发事件,须要同步数据变化时的事件 function setSessionStorage(payload) { const data = JSON.stringify(payload); // 同步当前页面数据变化 sessionStorage.setItem('setSessionStorage', data); // 触发localStorage的change事件将数据同步到其余页面 localStorage.setItem('setSessionStorage', data); // 删除反作用 localStorage.removeItem('setSessionStorage'); } 复制代码
而后就是咱们的桥梁 storage 核心事件的实现:
因为把数据存入 localStorage
后,下一步就是直接清除存入 localStorage
里的数据,清除 localStorage
也是会进入这个函数的,只要校验此时的值为空时不将数据同步便可。
// 监听的storage变化的事件 function storageChange(e) { // 校验null是为了在清除localStorage时不产生效果 const ifNull = e.newValue === null || e.newValue === 'null'; // 获取从别的页面传递过来的数据,并将数据同步到当前页 if (e.key === 'setSessionStorage' && !ifNull) { sessionStorage.setItem('setSessionStorage', e.newValue); } // 页面初始化时触发一次change事件将数据同步到其余页面 if (e.key === 'getSessionStorage' && !ifNull) { // 获取当前页的sessionStorage const currentSessionStorage = sessionStorage.getItem('setSessionStorage'); // 其余页面初始化时,已存在的标签页会触发getSessionStorage事件 // 将sessionStorage储存在localStorage并触发其余页面的change事件,同时传递参数 localStorage.setItem('setSessionStorage', currentSessionStorage); localStorage.removeItem('setSessionStorage'); } } 复制代码
这里还有一点要注意的是,咱们同源跨页面的场景通常两个页面都不是同时开启的,又因为咱们删掉了 localStorage
里的数据,因此,在另外一个页面打开时,咱们须要进行一次数据的同步,这就是上文的 storage
事件中下部分函数的功能。这部分可能会有点绕,因此小编仍是贴心的准备了一份图解供你们参考。
function init() { // 初始化监听localStorage的change事件 window.addEventListener('storage', storageChange); // 页面初始化时触发一次change事件将数据同步到其余页面 localStorage.setItem('getSessionStorage', 'any'); // 删除反作用 localStorage.removeItem('getSessionStorage'); } 复制代码
最后,无论在页面哪一个地方,只要不关闭窗口,只须要一行获取当前 sessionStorage 的代码便可。
// 当前sessionStorage储存的数据 const currentSessionStorage = sessionStorage.getItem('setSessionStorage'); 复制代码
这样,一种简单无反作用的同源跨页面数据同步方法就实现啦~
感谢各位的阅读,有任何建议和意见均可以在下方留言,小编定当积极改正。
招人,前端,隶属政采云前端大团队(ZooTeam),50 余个小伙伴正等你加入一块儿浪~ 若是你想改变一直被事折腾,但愿开始能折腾事;若是你想改变一直被告诫须要多些想法,却无从破局;若是你想改变你有能力去作成那个结果,却不须要你;若是你想改变你想作成的事须要一个团队去支撑,但没你带人的位置;若是你想改变“5年工做时间3年工做经验”;若是你想改变原本悟性不错,但老是有那一层窗户纸的模糊… 若是你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的本身。若是你但愿参与到随着业务腾飞的过程,亲手参与一个有着深刻的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我以为咱们该聊聊。任什么时候间,等着你写点什么,发给ZooTeam@cai-inc.com