欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):https://segmentfault.com/blog/frontenddriver

在web开发越来越复杂的今天,前端拥有的能力也越来越多。其中最重要的一项莫过于web存储。开发者们如果使用得当,这些存储可以帮助我们提升网页的性能与灵活度。本文不讲个中的细节,只讲各种前端存储的利弊,与各类存储的应用场景。毕竟这些技术的细节在网上随处可见,如果读者你决定使用的话,再去细查也不迟。我们前端人手里都有哪些存储武器,都用在什么地方,请读者随我一一聊开去......

1 老朋友cookie

1.1 是什么?

cookie是什么就用不着我多说了吧,可是有同学会问了,这也算存储?当然算,它也可以存东西不是,而且它会在用户访问服务器的时候被带上。但是,笔者在这里建议,不要使用过量,因为cookie在每次请求的时候都会被带上。你总不想每次访问自己网站接口或者文件的时候都带上一堆可能用不到的信息把?这样会增大请求包的大小。

1.2 访问限制性

众所周知,cookie可以设置访问域。即,如果你设置cookie的时候,设定了cookie的访问域名为一个顶级域名,则可以达到几个子域名共享cookie的效果。如:腾讯网与微信网页版共享了pac_uid(如图1.3.1与图1.3.2)。


图1.2.1


图1.2.2

访问的限制在种下cookie的时候指定。所以,我们可以设定cookie的访问域名限制(当然,不能跨域啦)。
有些重要信息,如用户的唯一标识,建议给这些cookie字段加上HttpOnly标识。加上了这个标识的话,我们的客户端js是无法读到与写入加了标识的cookie字段的,这样非常安全。如果有不了解的读者,建议百度一下"cookie httpOnly"。

1.3 存储时长

如果设定了cookie的超时时间的话,那么cookie将在到期的时候失效。如果没有设定,那么cookie就是session级别的啦。这里请读者们注意一下。cookie的session含义与我们接下来要讲的sessionStorage的session含义有区别。cookie的session是,在未关闭浏览器的情况下,所有的tab级别的页面或新开,或刷新,均属于一个session,比如,我们打开www.qq.com,在其中种下。test=doctorhou,如图1.3.1

document.cookie = 'test=doctorhou';


图1.3.1

我们再刷新一下当前页面,发现cookie还在,如图1.4.2


图1.3.2

关闭种cookie的tab,我们再新开一个tab,发现cookie还是存在的,如图1.4.3


图1.3.3

退出浏览器,再打开www.qq.com发现test=doctorhou这一项cookie已经不在了。如图1.3.4


图1.3.4

证明cookie的session含义是在浏览器退出时才结束。

1.4 做什么用比较好?

一般非到不得已,不要在cookie里面存东西。如果要存储的话。建议存储一些同步访问页面的时候必须要被带到服务端的信息。

比如,网站的用户登录信息。这个是在访问时必须要在服务端获取的信息,所以种在cookie里面很必要。有的同学会说了,那一些用户信息呢?比如用户在我网站都买了什么东西,之类的。这里建议存储在服务端(存在数据库里面,或者什么里面)。然后使用用户的cookie唯一ID去数据库中查询。我们的目标是,没有蛀,哦不,越少越好。

2 短暂的sessionStorage

2.1 是什么?

sessionStorage属于webstorage的一种,sessionStorage与我们稍后要说的localStorage类似,可以存储k-v形式的数据,使用方法非常简单set便可以存储,如图2.1.1。

sessionStorage.setItem('test', 'doctorhou');


图2.1.1

使用sessionStorage.getItem便可以直接获取。如图2.1.2:

sessionStorage.getItem('test');


图2.1.2

顾名思义,sessionStorage,是session级别的存储。其存储于客户端。服务端是无法直接拿到的。

2.2 访问限制性

不同于cookie,sessionStorage的访问限制更高一些,只有当前设定sessionStorage的域下才能访问,而且不同的两个tab之间不能互通。例,我在www.qq.com下种下了sessionStorage,在wx.qq.com下是,无法访问的。如图2.2.1、图2.2.2:


图2.2.1


图2.2.2

在新开的tab下,或者关闭本TAB再打开后(也是www.qq.com),也是无法访问到之前种的sessionStorage的。如图2.2.3:


图2.2.3

而本tab刷新的时候,sessionStorage确是可以访问的,如图2.2.4


图2.2.4

以上种种,证明sessionStorage里面的session,并不同于cookie,是以tab为级别的session。

2.4 做什么用比较好?

既然是存储于客户端而且存储级别仅仅是一个session的话,还是建议存储一些当前页面刷新需要存储,且不需要在tab关闭时候留下的信息。刚刚说了,只有页面刷新才不会清除掉sessionStorage。剩下的均会清理掉sessionStorage,当然,也许可以用sessionStorage来检测用户是否是刷新进入的页面。对于音乐播放器恢复播放进度条等功能等还是挺实用的。

3 简易强大的localStorage

3.1 是什么?

localStorage与sessionStorage较为相似,接口也简单,通过localStorage.setItem/localStorage.getItem即可轻松使用,如图3.1.1。

localStorage.setItem('test', 'doctorhou');
localStorage.getItem('test');


图3.1.1

localStorage可以存储k-v形式的数据。存储的值需要是字符串类型,没法直接存储对象,但是可以将对象序列化为字符串再存入。如果强行存入object的话,就会被调用object.toString从而悲剧。悲剧的存法,效果如图3.1.2:

var doctorhou = {
    name: 'doctorhou',
    describe: '高大、威猛、帅气'
};
localStorage.setItem('test', doctorhou);
localStorage.getItem('test');


图3.1.2

正确的存取方法,效果如图3.1.3:

var doctorhou = {
    name: 'doctorhou',
    describe: '高大、威猛、帅气'
};
localStorage.setItem('test', JSON.stringify(doctorhou));
JSON.parse(localStorage.getItem('test'));


图3.1.3

localStorage的存储周期比sessionStorage长,如果用户不清理的话,是可以永久存储的。

3.2 访问的限制性

localStorage与sessionStorage虽然相似,但是访问限制却不尽相同,localStorage的访问域默认设定为设置localStorage的当前域,其他域名不可以取。这点与sessionStorage相同,但是与sessionStorage不同的是,localStorage设定后,新开tab是可以访问到的,如图3.2.1与图3.2.2


图3.2.1


图3.2.2

3.3 存储时间

localStorage理论上讲是永久性质的存储。但是,免不了用户会使用浏览器清除数据,或者浏览器有时候为了节省,去清除数据。

3.4 大小限制及检测

localStorage的大小一般限定为4M左右,这点可以根据实际浏览器来测试。那么,如何检测自己已经消耗了多少空间呢?可以直接将localStorage序列化,看看其字节数,就可以算出其已经占用的空间了(可能会有点误差,这样做会把一些转移符号算进去,可以消除掉后再算)。如图3.4.1,我们看出了自己大约已经使用了61个字节的

JSON.stringify(localStorage)


图3.4.1

3.5 做什么用比较好

由于localStorage的稳定性质,及其长效的存储。笔者建议如果有一些数据,服务器难以承载其压力,但又要与用户的信息绑定的话,可以使用localStorage存储一些状态,这样即能缓解服务端压力,也可以存储用户的数据。当然,也有一些小技巧,可以用localStorage提高网站访问的速度。如笔者写的一篇浅析文章:
聊一聊百度移动端首页前端速度那些事儿
读者们可以尝试使用。

4 websql与indexeddb

4.1 是什么?

这两位可是web存储中的重型武器。为什么放在一起说呢,是因为,websql的标准,官方已经不打算维护了,转而维护了新的indexeddb,读者可能会问了,那直接说indexeddb就好了,为啥还要说websql,因为websql虽然过时了,但是其兼容性却出奇的好,几乎是移动端均可用呀。我们来看一下caniuse上的兼容性数据:

websql的兼容性如图4.1.1


图4.1.1

indexeddb的兼容性却不是很好,android4.4之前以及ios7以前都无法使用。如图4.1.2


图4.1.2

所以笔者建议如下,能用indexeddb的时候,就要使用indexeddb,因为其代表了未来的发展方向。如果用不了indexeddb的尽量使用websql进行代替。其实就是使用一段腻子脚本(polyfill)即可,做一下兼容。接下来我们会尝试做一点腻子脚本。

websql更像是关系型数据库,并且使用sql语句进行操作,效果如图4.1.2

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <script>
            var db = window.openDatabase('testDB', '1.0', 'TestDb', 2 * 1024 * 1024);
            db.transaction(function (context) {
                context.executeSql('CREATE TABLE IF NOT EXISTS cubefe(id, name)');
                context.executeSql('INSERT INTO cubefe (id, name) VALUES (1, "doctorhou")');
            }); 
        </script>
    </body>
</html>


图4.1.2

indexeddb更像是nosql,直接使用js的方法操作数据即可,效果如图4.1.3

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
    </head>
    <body>
        <script>
            var startTime = +new Date();
            console.log('starttime:', startTime);
            function openDB(dbname, version, cb) {
                var request = window.indexedDB.open(dbname);
                request.onsuccess = function (e) {
                    var db = e.target.result;
                    myDB.db = db;
                    var version = db.version;
                    if (!db.objectStoreNames.contains('students')) {
                        request = db.createObjectStore('students', {autoIncrement: true});
                    }
                    cb && cb(e);
                };
            }   

            var myDB = {
                name: 'test',
                version: 4,
                db: null
            };

            openDB(myDB.name, myDB.version, function (e) {
                var db = e.target.result;
                var storeName = 'students';
                var transaction = db.transaction(storeName, 'readwrite');
                var store = transaction.objectStore(storeName);
                store.put({id: 2, name: 'doctorhou'}, 'b');
                var request = store.get(1);
                request.onsuccess = function (e) {
                    console.log(request.result);
                    var endTime = +new Date();
                    console.log('take-time:', endTime - startTime);
                };
            });

        </script>
    </body>
</html>


图4.1.3

4.2 访问

indexeddb和websql在这一点上与localStorage一致,均是在创建数据库的域名下才能访问。而且不能指定访问域名。

4.3 存储时间

这两位的存储时间也是永久,除非用户清除数据,可以用作长效的存储。

4.4 大小限制

理论上讲,这两种存储的方式是没有大小限制的。然而indexeddb的数据库超过50M的时候浏览器会弹出确认。基本上也相当于没有限制了。

4.5 性能如何?

我这边做了个实验,indexeddb查询少量数据也就花费了20MS左右。还是很快的。如图4.5.1


图4.5.1

大量数据的情况下,相对耗时会变长一些,但是也就在30MS左右,也是相当给力了。如图4.5.2所示,10W数据+,毕竟nosql。

for (var i = 0; i < 100000; i++) { 
    store.add({id: 2, name: 'doctorhou'});
}


图4.5.2

而websql这边的效率也不错,10w+数据,简单查询一下,只花费了20MS左右,如图4.5.3所示

<script>
var startTime = +new Date();
var db = window.openDatabase('testDB', '1.0', 'TestDb', 2 * 1024 * 1024);
db.transaction(function (context) {
    context.executeSql('CREATE TABLE IF NOT EXISTS cubefe(id, name)');
    /*for (var i = 0; i < 100000; i++) {
        context.executeSql('INSERT INTO cubefe (id, name) VALUES (i, "doctorhou")');
    }*/
    context.executeSql('SELECT * FROM cubefe WHERE rowid="99999"', [], function (tx, results) {    
        console.log(results);
        console.log('take-time', (+new Date()) - startTime);
    }, null);
}); 
</script>


图4.5.3

4.6 拿来干什么用?

当我们是在做一个离线应用,或者webapp的时候,可以考虑使用本地数据库中存取数据。如果不存大量的数据的话,其实localStorage就够用了。亦或者,你想把一张用户的皮肤图片之类的大量数据存入客户端缓存起来,localStorage已经不够用了的话,也可以尝试一下websql与indexeddb。

接下来的一篇文章,我将会和读者们一起聊聊前端调试那些事儿,不要走开,请关注我.....

https://segmentfault.com/a/1190000005964730

如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。

以上内容仅代表笔者个人观点,如有意见笔者愿意学习参考各读者的建议

聊一聊前端存储那些事儿
标签: