Summary

  • W3C 于 2009 年 4 月 23 日发布的 Web Storage 首份工作草案,用同一网站的两个机票预订窗口说明:共享的 cookie 若只记录一个“当前行程”,选择就可能串到另一个窗口。
  • sessionStorage 按顶层窗口和来源存放一次操作的数据;localStorage 按来源保存可跨窗口、跨会话使用的数据。两者都把脚本需要的状态放在浏览器里,由应用决定何时发送。

先别谈浏览器标准,想象一下航空公司的柜台。你拿着两张不同的行程单,一张上午走,一张夜里走。柜员只有一张便签,写着“顾客当前航班”。你问第一张的行李额,他改一次便签;你转头问第二张的座位,他又改一次。最后你说“就买刚才那张”,柜员满怀自信地拿起了另一张。便签的字没有写错,柜台却少了两个抽屉。

W3C 在 2009 年 4 月 23 日发布的 Web Storage 工作草案就用两个订票窗口讲这个问题。草案写道,如果网站靠 cookie 记住用户正在买哪趟航班,两个窗口会互相泄漏选择,甚至可能让人不知不觉买下同一航班的两张票。它没有拿抽象的“状态 A”和“状态 B”敷衍读者:这里的差错会出现在信用卡账单上。

cookie 会随着符合条件的 HTTP 请求送往网站。服务器识别浏览器时,这很方便;同一网站的两个窗口同时推进不同交易时,一个共用的“当前航班”标记却分不清谁是谁。浏览器其实知道每个页面属于哪个顶层窗口。Web Storage 所做的一步,是让网页脚本也能按这条边界存放值。

第一个抽屉叫 sessionStorage。2013 年的 W3C 推荐标准为每个顶层浏览上下文、每个来源安排各自的会话存储区。同一窗口里,订票页面跳到乘客信息页,再跳到付款页,程序仍能读到这趟行程的选择;另一个窗口则有另一份。由原窗口打开的新窗口有时会先拿到一份拷贝,之后两边各改各的。复制的是起点,不是永远绑在一起的命运。你终于可以放心比较早班机和红眼航班,而不用猜哪个窗口刚把共享便签擦掉。

“来源”是另一把锁。存储区归属于网页的来源,别的网站不能随手翻看。于是一次订票的地址不只是“这家网站”,还包括“这个窗口”。把数据放对位置,程序才知道当前选座、附加保险和付款步骤究竟属于哪张票。

第二个抽屉 localStorage 的寿命更长。同一来源的不同窗口可以访问它,浏览会话结束后也可以继续保留。草案想到的用途包括用户自己写的文档、邮件数据;小一点的示范则是网页访问次数计数器。cookie 像每封信都要随身携带的一张纸条,localStorage 的值留在浏览器里,脚本需要时才读取,应用要传给服务器时再传。网站不必为了记住计数器,把它反复塞进每次请求。

2016 年的第二版推荐标准把抽屉的操作写得更细:setItem、getItem、removeItem、clear 管理键和值;写入失败要保持原样;空间不足或存储受限时,setItem 可以抛出 QuotaExceededError。其他能访问同一存储区的页面,在内容改变后可以收到 storage 事件。规范要求脚本看到一致的值,却没有规定每次调用都必须等磁盘真的写完。抽屉有门把手,也有失败时不能吞掉旧文件的规矩。

规矩并非没有成本。2013 年标准的“问题”一节记下了一场争执:为避免窗口之间的脚本发生竞争而设置的存储互斥锁,在一些实现者看来负担太重。一个共享抽屉要让不同窗口读写得有秩序,浏览器就得付出协调成本。这是订票例子背后的工程账单。

W3C 的发布沿革从 2009 年草案延伸到 2013 年与 2016 年的推荐标准。两张机票的例子一直有力,因为它问的是网页记忆最朴素的一件事:一条信息到底属于哪个范围?进行中的订票属于窗口;要长期保存的草稿属于网站来源;cookie 则随着请求去服务器。行李可以共用传送带,行程最好别共用一张便签。