![90dac593f0b4a128e2156fabaff47f37.png](https://i-blog.csdnimg.cn/blog_migrate/54f311685b6b38f465a14e64d385942b.jpeg)
LocalStorage的性能问题过去一直被人们所诟病,甚至连浏览器厂商和WEB性能专家们都呼吁web开发者停止使用。
诚然,人们说的也并非没有道理。localStorage是会阻塞主线程的同步API,任何时候使用它都有可能使你的页面处于不可交互的状态。
而相对于localStorage通俗易懂的API设计,唯一可替代localStorage的异步API就是IndexDB了,它的设计可谓复杂不少。
这就为Web开发者留下了一个难题,我应该使用较难理解的IndexDB还是选择简单但体验较差的localStorage呢?针对这个问题,不少既能进行读写又能提供通俗易懂的API第三方库开始涌现,然而它们的底层也是基于异步存储API来开发的。引入这些库不仅带来了额外的文件体积,甚至还带来了影响页面性能的风险。
问题来了,是否存在这样的一个可能性,开发者既能享受有如localStorage简单的API,同时又能拥有像异步存储API的性能呢?
答案是,很快就会有了。Chrome正在实验一个名为内嵌模块
的新特性,而首个基于新特性登陆的功能就是名为KV Storage的异步key/value储存模块。
什么是内嵌模块?
内嵌模块
就像普通的Javascript模块,但它不需要随着页面打包让用户下载,因为浏览器自身已经承载了这个模块。
与传统的web APIs一样,内嵌模块这个特性也需要经历一系列标准化的过程 — 在每个新特性落实前,都需要先提供一份规范,通过由浏览器厂商及web开发者组成的设计评审并获得许可。(在Chrome里面,内嵌模块也同样需要遵循与过往推出新API一样的流程。)
但内嵌模块并不像传统的web APIs一样暴露在全局对象,相反,它只能通过import
引用。
不把内嵌模块暴露在全局有很多好处:在创建Javascript运行上下文的时候不会被无故引入(例如:打开new tab、worker或者service worker),此外也不会消耗多余的内存和CPU资源,直到模块确实被引用了。除此之外,这样做也免去了与开发者代码中全局变量命名冲突的风险。
要引用一个内嵌模块,你需要在需引用模块的名字前加上std:
的前缀。举个例子,在支持的浏览器中,你可以使用下面的代码引入KV Storage
。
import {storage, StorageArea} from 'std:kv-storage';
KV Storage
KV Storage
简单得有点像localStorage的API,但它的API模型其实与Javascript的Map
更为接近。为了替代getItem
,setItem
和removeItem
,它提供了get
,set
和delete
。除此之外,也有一系列localStorage不具备的类Map方法,例如keys
,values
,和entries
。与Map相似,这里的键不要求必须是字符串,只要是可序列化的类型都可以。
和Map不一样的是,所有KV Storage
的方法均返回Promise
或者Async Iterators(模块的设计核心并不是同步操作,这一点和localStorage是不一样的)。感兴趣的同学可以从这个文档中获取更多API的完整细节。
或者你已经从上面的代码实例注意到,KV Storage
暴露了两个变量:storage
和StorageArea
。
事实上,storage
可以被理解成StorageArea
的一个实例,此时的name
为default
,这也是开发者最常用的一个用法。而StorageArea
这个类则是为分区存储而生,它会把数据存储到名为kv-storage:${name}
的IndexedDB数据库中,这里的name
对应StorageArea
实例的name
。
下面是KV Storage
的使用例子:
import {storage} from 'std:kv-storage';
const main = async () => {
const oldPreferences = await storage.get('preferences');
document.querySelector('form').addEventListener('submit', async () => {
const newPreferences = Object.assign({}, oldPreferences, {
// Updated preferences go here...
});
await storage.set('preferences', newPreferences);
});
};
main();
注意:KV Storage
在Chrome 74已经可以通过打开Experimental Web Platform features
来使用了(chrome://flags/#enable-experimental-web-platform-features)
要是浏览器不支持内嵌模块怎么办?
如果你已经熟练使用浏览器原生Javascript模块,你应该知道import URL之外的东西会产生异常,而std:kv-storage
并不是一个合法的URL。
那么我们就非得等到所有浏览器支持内嵌模块才能使用了么?当然不是。
事实上,得益于另一个在实验阶段的新特性import maps,只要浏览器支持我们也可以使用。
Import maps
Import maps
本质上是一个可以让开发者将import
标识符映射到一到多个其他标识符的机制,这个机制非常强大,因为它赋予了开发者在运行时针对指定的import模块动态修改浏览器实际获取模块的能力。
针对内嵌模块的场景,不支持内嵌模块
的浏览器就可以通过引入polyfill来使用KV Storage
了,而支持内嵌模块的浏览器则直接引用。
下面是通过声明import map
使用KV Storage
的一个例子:
<!-- The import map is inlined into your page -->
<script type="importmap">
{
"imports": {
"/path/to/kv-storage-polyfill.mjs": [
"std:kv-storage",
"/path/to/kv-storage-polyfill.mjs"
]
}
}
</script>
<!-- Then any module scripts with import statements use the above map -->
<script type="module">
import {storage} from '/path/to/kv-storage-polyfill.mjs';
// Use `storage` ...
</script>
示例代码的核心在于将/path/to/kv-storage-polyfill.mjs
映射到两个地方:std:kv-storage
和/path/to/kv-storage-polyfill.mjs
。浏览器在处理引用这个URL(/path/to/kv-storage-polyfill.mjs
)的import声明时,首先会尝试加载std:kv-storage
,如果失败会降级到加载/path/to/kv-storage-polyfill.mjs
。
值得一提的是,这里的巧妙之处在于浏览器并不一定要支持import maps
或内嵌模块
,因为import引用的URL已经是polyfill了。在不支持的浏览器环境下,Polyfill是默认引用的模块,在支持的浏览器环境下又能切换回原生的KV Storage
,这让内嵌模块成为了一个渐进式增强的能力。
如果浏览器完全不支持modules呢?
为了使用import map
实现按需加载内嵌模块
,你必须使用import声明,这意味着你需要使用module scripts,即<script type="module">
如今已经有超过80%的浏览器支持modules了,至于剩余不支持的浏览器,可以提供nomodule的bundle,此时你需要将所有polyfill模块打包到bundle内。
KV Storage demo
为了证明内嵌模块在较老的浏览器仍能得到兼容,这里提供一个包含上述所提及所有内容的Demo来在现今各个类型的浏览器运行:
- 支持modules, import maps, 和内嵌模块的浏览器不需要加载任何冗余代码
- 支持modules, import maps但不支持内嵌模块的浏览器会加载KV Storage的polyfill(通过浏览器的module loader)
- 支持modules但不支持import maps的浏览器也加载KV Storage的polyfill(通过浏览器的module loader)
- 连modules也不支持的浏览器则加载包含KV Storage polyfill的bundle(通过<script nomodule>)
Demo已经部署到Glitch,所以任何人都可以看到它的源码。README还附带了一些细节的解释,欢迎感兴趣的同学抽空阅览。
Demo使用了Rollup作为构建工具,生成了多个版本,分别适用于上述几个类型的浏览器。
如果想看到原生的内嵌模块,你需要使用Chrome 74
(目前的Chrome Dev或Canary版本)并打开Experimental Web Platform features
(chrome://flags/#enable-experimental-web-platform-features)。
你会发现内嵌模块确实被加载了,因为在这里并没有加载polyfill(有趣的是你甚至可以看到模块的源码并且进行断点。
![d7e41bcd94be5c0f40a2d18adba11c97.png](https://i-blog.csdnimg.cn/blog_migrate/db251a7312e8f17f9a4b0878de1c8785.jpeg)
关于反馈
上述的介绍应该让不少人体验到内嵌模块的魅力,希望你会为此感到兴奋。希望更多的开发者能够尝试KV Storage(还有上面提及到的其余特性)并给出反馈。
下面是上述特性的Github地址:
- KV Storage
- KV Storage Polyfill
- Build-in modules
- Import Maps