先来几道面试题

1、a.meituan.com 和 b.meituan.com 这两个域能够共享同一个 localStorage 吗?

2、在 webview 中打开一个页面:i.meituan.com/home.html,点击一个按钮,调用 js 桥打开一个新的 webview:i.meituan.com/list.html,这两个分属不同 webview 的页面能共享同一个 localStorage 吗?

3、如果 localStorage 存满了,再往里存东西,或者要存的东西超过了剩余容量,会发生什么?

答案

1、同一个域名(document.domain)共享同一个 localStorage,a.meituan.com 和 b.meituan.com 是两个域名,所以不能共享

2、能。相当于同一个浏览器的不同标签页。不同浏览器之间不能共享。

3、存不进去并报错(QuotaExceededError)

技术很简单,业务很麻烦

在大公司,同一个域名下可能存在几十上百条业务线,每条业务线都可能因为各种理由往 localStorage 里塞东西,跨页面传数据啦、缓存啦、离线化啦、性能优化啦...,5M 看起来很多,其实很快就用完了。而开发时基本无感知,是因为大家都只访问自己的业务,但用户会访问各种业务,时间一久,很容易就存满了,凡是严重依赖 localStorage 的业务流程都存在风险,写可能出问题,读自然也会出问题。

一种容易想到的方案是,当 localStorage 存满后降级到 sessionStorage 里。看上去没啥问题,但实际业务中 app 内 h5 页面跳转常常采用新打开 webview 的方式,这么做的好处是关闭一个 webview 可以直接回到上一个页面,而不用重新加载页面,对于订单填写这类带有状态的页面就很需要这么做。新打开 webview 等于新打开一个会话,而 sessionStorage 只能存在于同一个会话中,因此 sessionStorage 无法跨页面共享。

那降级到 cookie 里呢?cookie 一共才 50 个,总大小不超过 4k,作为 backup 过于脆弱,而且还会影响请求的效率。如果后端对请求头大小做了限制,还可能产生 413 错误,导致请求被拦截。

那降级到 url 上呢?很麻烦。比如有一个交互流程是这样的:页面 A => 页面 B => 页面 C,如果页面 A 的数据要传到页面 C,就得通过页面 B 做一层中转。而且 url 长度也是有限制的。

单页应用解决跨页面传数据就很简单,改造成单页应用呢?这个就得估算成本,看老板们认不认可了,而且原有应用积累了大量的业务逻辑,没有注释,没有测试用例,需求文档散落在不知名的角落,你真能保证重新做的和原来的功能一模一样吗。

我们还可以求助客户端同学,通过 js bridge 提供一个仿 localStorage 的东西,不过要考虑版本的问题,新版 app 里使用了客户端提供的 store,怎么兼顾老版 app,而且还要考虑兼容浏览器、微信。所以这种方案也只能解决一部分问题,当然,如果 h5 的流量绝大多数都在 app 里,那么这种方案是可以解决一大部分问题的,不过客户端提供的存储可不见得比原生的存储可靠,还是得加 backup。

我们还可以求助后端同学,多加几个字段甚至多加几个接口,不过这涉及到核心业务流程的改造,风险不小,而且不见得能完全解决问题,也无法永久的解决问题。

我们还可以来一招互相伤害大法,那就是把别人存的东西都删掉。。。

localStorage 是个好东西,不用,这是因噎废食,用,又很难统一和约束各业务线的用法,一旦放开用,就总会面临存满的风险。跟你在同一个域名下做开发的人可能跟你不在同一栋楼,甚至可能不在同一个城市,你有那个影响力去统一所有人的使用规范吗。

还有一个很讨厌的事情:safari 在隐私模式下不支持 localStorage 的存取(ios11 以下),这种情况比较罕见,但如果出了客诉,也是个大坑。

问题的本质

localStorage 归根结底就两个作用:持久化存储与跨页面传数据。持久化存储不会出问题,存不进去就存不进去呗,取不出来就去其它地方取,或者不取。问题就出在跨页面传数据上,上一个页面因为 localStorage 存满导致数据没有写入,下一个页面读取数据为空,从而导致错误。

问题的根源

同一个域名共享同一个 localStorage,而同一个域名下存在过多独立的业务线,业务线之间各自为政,毫无节制的攫取公共资源,这就是 localStorage 溢出问题的根源。

就我观察的情况来看,很多公司都喜欢把 h5 页面挂在 i.xxx.com 或 m.xxx.com 下,然后通过路径划分业务,比如 i.xxx.com/project-a, i.xxx.com/project-b...,随着业务发展,越来越多的业务都加到 i.xxx.com 中,“公地悲剧”就无可奈何的产生了,而且积重难返。我以前在的团队也是如此,用 h5、js、css 这样的类型名称来划分目录,初期东西少,自然没问题,但后来所有应用都把资源塞到 js 文件夹、css 文件夹下,一个文件夹包含了来自五湖四海的上百个文件,维护起来十分难受。

通过应用类型划分,而不是通过业务类型划分,这是最初架构策略的问题。如果 a 业务挂在 a.xxx.com 下,b 业务挂在 b.xxx.com 下,每个业务有独立的团队维护,localStorage 从公共资源变成团队的私有财产,或许这样才能从根源上解决 localStorage 无限膨胀的问题。有网友提出对 i.xxx.com 进一步划分子域,其实也是这个思路。

理想的方案

假设我们回到起点,从零建设前端工程,我们怎么避免 localStorage 存满的问题?

1、划分域名。各域名下的存储空间由各业务组统一规划使用

2、跨页面传数据:考虑单页应用、优先采用 url 传数据

3、最后的兜底方案:清掉别人的存储

互相伤害其实是个好办法

在已然发展很久的业务中,我们怎么解决此问题呢?

const QUOTA_EXCEEDED_ERR_CODE = 22
function write (key, data) {
try {
localStorage.setItem(key, data);
} catch (e) {
if (e.code === QUOTA_EXCEEDED_ERR_CODE) {
localStorage.clear();
localStorage.setItem(key, data);
}
}
}

上面这个方法还是有点问题,因为它把自己业务要用的东西也给删了,所以自己的业务最好统一在 key 上加一个前缀,清空 localStorage 时只删别人的。

有的同学可能会担心,这样会不会对其它业务造成伤害?或者产生一些难以发现的 bug。其实这种担心很大程度上是因为忽略了实际的使用场景。用户用同一个设备打开同一个 app,在同一个时间只能访问一个业务,因此不会存在某个业务正在使用过程中,localStorage 被其它业务清掉的场景,除非!除非有交叉的业务场景。

没有银弹,没有十全十美的方案。

补充

掘金网友@FE 提出用 indexedDB 存文件类型的数据,localStorage 存业务数据,这是一种可以缓解问题的方案。

localStorage 存满了怎么办?的更多相关文章

  1. localStorage存的值如果有true,false,需要注意了。

    把一个全局变量存到localStorage里面 isSupport是 true  false; window.localStorage && window.localStorage.s ...

  2. Android_html5交互 弹框localstorage 存值 整体案例

    经历2周多的时间 终于是完成了还算可以的android 整体案例了,分享下给大家  也希望自己有时间回过头来看看当初研究android的纠结心情.痛苦的经历是开发android 大部分都是在网上找解决 ...

  3. localStorage存值取值以及存取JSON,以及基于html5 localStorage的购物车

    localStorage.setItem("key","value");//存储变量名为key,值为value的变量 localStorage.key = &q ...

  4. localStorage存、取数组

    localStorage存储数组时需要先使用JSON.stringify()转成字符串,取的时候再字符串转数组JSON.parse(). var arr=[1,2,3,4]; localStorage ...

  5. 关于localStorage

    localStorage 是 HTML5 本地存储的 API,使用键值对的方式进行存取数据,存取的数据只能是字符串.不同浏览器对该 API 支持情况有所差异,如使用方法.最大存储空间等. 使用方法 设 ...

  6. QuotaExceededError: The quota has been exceeded. localStorage缓存超出限制

    今天在项目中遇到了一个问题,localStorage存储超出限制.报错信息如标题.这个是因为最近做了一波优化,把导航栏和一些用户信息本地化存储,都放在localStorage里,也不是每个用户会出现这 ...

  7. html5 sessionStorage 与 localStorage存储

    sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁.因此sessionStorage不是一种持久化的本地 ...

  8. localStorage的使用

    HTML5中提供了localStorage对象可以将数据长期保存在客户端,直到人为清除. localStorage提供了几个方法: 1.存储:localStorage.setItem(key,valu ...

  9. js中location.search、split()HTML5中localStorage

    1. location.search在客户端获取Url参数的方法 location.search是从当前URL的?号开始的字符串 如:http://www.baidu.com/s?wd=baidu&a ...

随机推荐

  1. python入门(三):循环

    1.for i in xxx xxx: 序列(列表,元祖,字符串) xxx: 可迭代对象 >>> for i in "abc": ...     print(i) ...

  2. 数据库启动windows

    1.上 MongoDB官网下载数据库,下载之后选择自己想放的文件夹要记住文件夹位置,比如我下载之后就放在D盘,改文件夹为 mongodb 2.启动之前要给mongodb指定一个文件夹,这里取名为&qu ...

  3. Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS

    [Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS] 1.通过 VPOS / WPOS 语义获取. VPOS 是 HLSL 中 对 屏幕 坐标 的 语义, 而 WPOS 是 ...

  4. eclipse配置Servlet连接Mysql要注意的几个地方

    用Servlet即把jdbc那套放到继承于HttpServlet的派生类之内,那段代码很简单 protected void doPost(HttpServletRequest request, Htt ...

  5. Python设计模式 - 基础 - 封装 & 继承 & 多态

    面向对象的核心是对象,世间万物都可以看作对象,任何一个对象都可以通过一系列属性和行为来描述,可以包含任意数量和类型的数据或操作.类是用来描述具有相同属性和方法的所有对象的集合.类通常是抽象化的概念,而 ...

  6. JavaSE基础知识(3)—流程控制结构

    一.顺序结构 1.说明 程序从上往下依次执行,中间没有任何跳转或选择2.特点 变量必须遵循 “前向引用” (局部变量必须先声明.赋值,然后再使用!) 二.分支结构(条件) 1.说明 程序从两条或多条路 ...

  7. flask 未完待续

    Flask - 一个短小精悍.可扩展的一个Web框架很多可用的第三方组件:http://flask.pocoo.org/extensions/blogs:https://www.cnblogs.com ...

  8. mybatis的Sql语句打印

    我们在使用mybatis的时候,有时候,希望可以在eclipse的控制台下打印出来sql语句,但是有时候却不希望相关的语句打印.这个时候,需要我们进行一些配置.  在mybatis中,他通过调用一些接 ...

  9. VB.Net 经典画圆方法

    计算机图形学课程作业-----画圆 Public Class Form1 Private Sub Button1_Click(ByVal sender As System.Object, ByVal ...

  10. cpp 区块链模拟示例(五) 序列化

    有了区块和区块链的基本结构,有了工作量证明,我们已经可以开始挖矿了.剩下就是最核心的功能-交易,但是在开始实现交易这一重大功能之前,我们还要预先做一些铺垫,比如数据的序列化和启动命令解析. 根据< ...