写点什么

一种简单无副作用的同源跨页面数据同步方案

2021 年 1 月 29 日

一种简单无副作用的同源跨页面数据同步方案

背景


提起这个方案,还要从某个风和日丽的早晨说起。那日小编正忙着手上的各种需求,突然后端的亲火急火燎的找到小编,说是有一个重要的用户,在使用 Word 在线编辑文档功能时,发现保存的文件被篡改了。一听到这,我心想这下摊上事儿了,妥妥的线上故障,但还是故作镇定的开始排查是什么问题。


经过了日以继夜的排查后,小编发现是由于用户同时打开了两个在线编辑页面,并且在 A 页面的在线编辑工具还未关闭的情况下,去 B 页面也打开了在线编辑工具。


说到这个在线编辑工具,它叫 pageOffice,当他在线被触发启动时,会在本地打开一个类似软件的窗口,启动一个相对独立的服务。且这个服务前端通过 Web SDK 提供的 API 能进行控制的余地非常小,唯一的通信方式只有 pageOffice 中操作触发页面上的回调函数。在和 pageOffice 的客服进行了一系列如同太极的沟通后,我们还是没能解决如何知道用户已经打开了 pageOffice 并且阻止用户在另一个页面触发打开工具的方法。


初探


上文提到的, pageOffice 打开以后就成为了相对独立的个体,于是乎,小编对它直接的各种软磨硬泡都宣告失败。进而小编放弃了探索对它的控制,转而思考两个页面之间通信的控制。


平时咱们对一个方法是否运行过,最常用的方式就是 “状态开关”。即存储用一个变量,类似于  ifOpen 之类的,将其设置为 ture 去记录当前方法已运行,再在其运行结束时设置为 false,即可完成一个闭环。而我们这次除了以上条件,还需要让别的页面也拥有这个变量,才能阻止别的页面在这个方法运行时再次触发这个方法。这听起来有点绕,不过下面有一个小图解来解释我们这次问题的初步解决方案。



显而易见的,此处应有一个跨页面通信的方案,但是由于这是同一个页面上的功能,所以我们可以选择最简便的方案。


提到跨页面数据存储,聪明的你们肯定会想到本地存储 localStorage,提到localStorage 小编就会想起它的兄弟 sessionStorage,那就大致回顾一下它们两的特性吧:


  • localStorage:持久的,相同的协议、主机名、端口(同源)能增删改查,数据不会自动清除;

  • sessionStorage:临时的,除了同源外还要在同一窗口下才能增删改查,数据会在窗口关闭时自动清除。


看到这里想必大家已经看出来,本地存储 localStorage 完全可以满足上图中描述的功能。但是回想一下题目中提到的 副作用 一词,大家是否心中暗想此事必不简单。


小编解释一下:首先,由于 localStorage 不会自动清除的特性,当用户再次进入页面时,之前保存的 localStorage 里的数据会还在;其次,之前提到过,pageOffice 打开后就独立了,所以,这两个条件结合后就存在这样一个场景 —— 在 pageOffice 还在打开的时候,用户先把页面关闭了,之后再关闭 pageOffice,此时,页面已经不存在了,所以 pageOffice 关闭时触发的回调函数,此时已经通知不到页面去改变存在 localStorage 里的变量。而再下一次打开页面时,由于localStorage 存的数据还是上次未关闭 pageOffice 时的 ifOpen = false, 所以,如果用户不自主清除本地缓存,将再也打不开 pageOffice,小编把这种关闭页面在未来可能会造成负面影响的数据称为 副作用


构思


为了清除上述方案带来的副作用,小编废寝忘食围绕副作用删除的时机想到了几种方案:


方案一:用 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');
复制代码


这样,一种简单无副作用的同源跨页面数据同步方法就实现啦~



头图:Unsplash

作者:凯译

原文:https://mp.weixin.qq.com/s/Q90HS8UWbBkFSSdkN8gkVA

原文:一种简单无副作用的同源跨页面数据同步方案

来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]

转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2021 年 1 月 29 日 22:411932

评论 1 条评论

发布
用户头像
。。。 其实,服务器端保存同一个文件的多个版本(副本)就好了啊,先进的系统都是这样做的
2021 年 02 月 07 日 10:22
回复
没有更多了
发现更多内容

几款Java开发者必备常用的工具,准点下班不在话下

华为云开发者社区

Java 工具 开发

“深入内核,拒绝蒙圈”,阿里巴巴一位P7级架构师总结整理的这份《Java架构成长笔记》彻底火了。

Java成神之路

Java 程序员 架构 面试 编程语言

涨薪神作!华为内部操作系统与网络协议笔记爆火,这也太香了吧

Java成神之路

Java 程序员 架构 面试 编程语言

渴望提升自己技术能力的程序员的必备宝典!这份在阿里内部被封神的《Java技术成长笔记》真的太牛了!

Java成神之路

Java 程序员 架构 面试 编程语言

扩招1W人,字节跳动内部公开12月份Java岗71道面试题

比伯

Java 编程 架构 面试 程序人生

【涂鸦物联网足迹】用煲仔饭来说明IaaS/PaaS/SaaS的区别

IoT云工坊

云计算 IaaS PaaS SaaS 云平台

干货丨如何使用Redash连接DolphinDB数据源

DolphinDB

数据处理 时序数据库 DolphinDB 数据库开发 redash

有了这份“Java神级面试资料”,奉劝各位耗子尾汁赶紧扔掉你在网上找的那些千篇一律的面试题

Java成神之路

Java 程序员 架构 面试 编程语言

一文详解激活函数

书豪

Hive中,同时存在map、array、struct这三种格式,应如何在建表语句中指定分隔符?

Geek_de9857

hive struct map array 分隔符

从阿里离职后人虚了,头也秃了,就剩这份Java性能优化的PDF了

Java架构追梦

Java 架构 面试 性能优化 并发

阿里华为等大厂如何处理数值精度/舍入/溢出问题

Java架构师迁哥

女朋友突然问我DNS是个啥....

乱敲代码

计算机网络 DNS DNS服务器

等保数据备份和恢复关键点,这些你该知道!

华为云开发者社区

数据 容灾 恢复

Mysql中,1=1和 1=1=1 和 -1=-1 和 -1=-1=-1 和 5=5 和 5=5=5 有什么区别

Geek_de9857

MySQL sql 返回值 1=1=1 -1=1=1

15年华为云视频架构师采访实录:揭秘未来音视频行业的科技趋势!

华为云开发者社区

直播 视频 华为云

《O2O实战:他们是如何利用互联网的》.pdf

田维常

互联网

从面试角度分析LinkedList源码

Java旅途

Java List 集合 linkedlist

OpenKruise v0.7.0 版本发布:新增周期任务分发控制器

阿里巴巴云原生

阿里云 容器 开发者 运维 云原生

985研究生熬夜23天吃透845页架构宝典 终收割腾讯Java岗offer!再也不用怀疑人生了

比伯

Java 编程 架构 面试 程序人生

架构师训练营第 13 周学习总结

netspecial

极客大学架构师训练营

LeetCode题解:127. 单词接龙,双向BFS,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

滴滴DoKit阶段性成果汇报之一机多控

工具 滴滴开源 DoKit

forsage以太坊矩阵系统软件开发|forsage以太坊矩阵APP开发

开發I852946OIIO

系统开发

程序员因重复记录日志撑爆ELK被辞退!

Java架构师迁哥

90分钟10个手写案例,从源码底层给你讲解7种线程池创建方式

996小迁

Java 源码 架构 资料 笔记

flink 使用curl,通过RESTful api,上传和删除jar包

Geek_de9857

flink RESTful curl 上传jar 删除jar

Alibaba最新《Java架构核心宝典》限时开放下载,互联网主流技术详解总结,提升技术能力的必备宝典!

Java成神之路

Java 程序员 架构 面试 编程语言

阿里云容器服务入选云原生边缘「领导力企业TOP3」,推动「原生云边」基础设施标准建立

阿里巴巴云原生

阿里云 容器 开发者 云原生 边缘计算

佛萨奇Forsage系统开发,智能合约dapp技术

薇電13242772558

智能合约 dapp

架构师 3 期 3 班 -week4- 作业

zbest

作业 week4

一种简单无副作用的同源跨页面数据同步方案-InfoQ