往期回顾
- 我所知道的HTML——现成轮子都堆成山了,新特性还算新吗?(上篇)
- 我所知道的HTML——现成轮子都堆成山了,新特性还算新吗?(中篇)
- 我所知道的HTML——现成轮子都堆成山了,新特性还算新吗?(下篇)
- 我所知道的HTML——跨源资源共享(CORS)
(如果您正巧因为首页推荐的功能点进此文章,由衷地建议您先回顾往期内容,这将有助您接下来的阅读体验。)
前言
在上一篇文章中,我们讨论了跨源资源共享CORS
,学习了它的定义、使用场景、限制和一些HTTP头,并且结合真实的面试题,将理论知识应用于实践。
在本篇文章中,我们会一起学习本地存储:
- sessionStorage
- localStorage
sessionStorage
sessionStorage
属性允许你访问一个,对应当前源的 sessionStorage
对象。它与localStorage
相似,不同之处在于localStorage
里面存储的数据不存在过期时间,而存储在sessionStorage
里面的数据在页面会话结束时会被清除(即当前页面被关闭后)。——MDN
语法
sessionStorage
的使用很简单,只有4种方式,分别是保存数据、获取数据、删除(保存的)数据以及删除全部(保存的)数据
- 保存数据:
sessionStorage.setItem("userName", "Mike");
- 第一个参数是键名【key】
- 第二个参数是键值【value】
- 获取数据:
sessionStorage.getItem("userName");
- 参数为键名
- 删除数据:
sessionStorage.removeItem("userName");
- 参数为键名
- 删除全部数据:
sessionStorage.clear()
关于sessionStorage.setItem
方法,还有一个需要注意的地方,那就是键名和键值的格式:
被存储的键值对总是以 UTF-16 DOMString 的格式所存储,其使用两个字节来表示一个字符。对于对象、整数 key 值会自动转换成字符串形式。
我们来根据这个格式,写一个代码示例看看效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Storage Example</title>
</head>
<body>
<button id="btn">Click Me</button>
<script>
// 对象作为键,数组作为值
var key_obj = { a: 1, b: 234, c: "hello" }
var value_obj = [1, 2, 3, 4, 5, 6]
sessionStorage.setItem(key_obj, value_obj);
// 整数作为键,整数作为值
var key_num = 1
var value_num = 12
sessionStorage.setItem(key_num, value_num);
</script>
</body>
</html>
可以看到,对象的存储结果变成了[object Object]
如果我们期望对象的内容可以被正常存入,那么可以使用JSON.stringify
<script>
// 对象作为键,数组作为值
var key_obj = { a: 1, b: 234, c: "hello" }
var value_obj = [1, 2, 3, 4, 5, 6]
sessionStorage.setItem(JSON.stringify(key_obj), value_obj);
</script>
不过JSON.stringify
是有很多局限性的,比如:
-
不支持的值:
JSON.stringify
无法序列化以下类型的值:undefined
、Function
和Symbol
类型的值在序列化过程中会被忽略或转换成null
。- 不可枚举的属性会被忽略。
- 如果对象中存在循环引用,
JSON.stringify
会抛出错误。
-
格式限制:
JSON.stringify
仅支持 JSON 格式允许的数据类型,这意味着:- 所有键名都必须是字符串。
- 不支持
Date
对象,它会被转换成一个字符串而不是日期对象。 - 不支持
BigInt
类型,因为 JSON 标准不支持大于2^53 - 1
的整数。
-
自定义序列化:默认情况下,
JSON.stringify
不会调用对象的自定义toJSON
方法。如果需要自定义序列化行为,必须显式调用对象的toJSON
方法。 -
格式化限制:虽然
JSON.stringify
允许第二个参数来替换值,或者第三个参数来添加缩进,但它对格式的控制是有限的。例如,不能自定义日期格式或添加注释。 -
性能问题:当处理大量数据或深层嵌套的对象时,
JSON.stringify
可能会变得很慢,因为它需要递归遍历整个对象结构。 -
顺序问题:
JSON.stringify
不保证对象的键值对的顺序。在 ES2015(ES6)及以后的版本中,对象键的顺序是根据其添加到对象中的顺序来确定的,但JSON.stringify
可能不会保留这个顺序。
所以当我们保存对象时,需要注意呢。
对象的存储结果变成了[object Object]
,本质上可以认为是默认调用了toString()
方法,因此如果我们给key_obj
添加一个toString
去遮蔽掉原型链上的toString()
方法的话,我们可以实现这种效果
<script>
// 对象作为键,数组作为值
var key_obj = { a: 1, b: 234, c: "hello", toString: () => "hello" }
var value_obj = [1, 2, 3, 4, 5, 6]
sessionStorage.setItem(key_obj, value_obj);
</script>
可以看到此时存的键就是"hello"
了
性质
-
页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
- 这个性质从下方动图可见一斑:
- 这个性质从下方动图可见一斑:
-
在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文,这点和 session cookie 的运行方式不同。
-
新标签或窗口打开页面: 当你在浏览器中通过新标签或窗口打开一个页面时,这个新打开的页面会创建一个新的浏览上下文。这个上下文是独立的,意味着它有自己的历史记录、DOM 状态、JavaScript 变量等。
-
复制顶级浏览会话的上下文: 虽然新标签或窗口创建了一个新的浏览上下文,但是它会复制顶级浏览会话(即浏览器主窗口的会话)的某些上下文信息。例如,如果是通过浏览器的主窗口打开新标签或窗口,新标签或窗口可能会继承顶级窗口的一些设置,如用户登录状态、权限设置等。但这并不意味着两个上下文是完全相同的,它们仍然是独立的。
-
与 session cookie 的运行方式不同: session cookie 是一种特殊的 cookie,它在用户会话期间存在,通常用于存储用户登录状态等信息。当用户关闭浏览器时,session cookie 就会被删除。重要的是,session cookie 是绑定到浏览器会话的,而不是绑定到特定的标签或窗口。
这里的关键区别在于:
- 当你在一个新标签或窗口中打开页面时,新会话可能会复制顶级浏览会话的某些上下文,但这通常不包括 session cookie 的状态。每个标签或窗口通常都有自己的 session cookie,除非它们是从同一个标签或窗口直接打开的(例如,通过 JavaScript 的
window.open
方法)。 - 因此,如果你在新标签或窗口中打开一个页面,并且希望保持用户登录状态,你不能依赖于 session cookie 的自动复制。相反,你需要确保在新标签或窗口中也能正确设置 session cookie。
- 当你在一个新标签或窗口中打开页面时,新会话可能会复制顶级浏览会话的某些上下文,但这通常不包括 session cookie 的状态。每个标签或窗口通常都有自己的 session cookie,除非它们是从同一个标签或窗口直接打开的(例如,通过 JavaScript 的
简而言之,这句话强调了在新标签或窗口打开页面时,尽管会复制一些顶级浏览会话的上下文,但这个过程与 session cookie 的行为不同,因为 session cookie 通常不会在新标签或窗口中自动复制。
-
-
打开多个相同的 URL 的 Tabs 页面,会创建各自的
sessionStorage
。-
这张页面没有点击按钮,因此不会往
sessionStorage
中存入btn-13579
这个键值对 -
这张页面点击了按钮,往
sessionStorage
中存入btn-13579
这个键值对可以看到,两张页面URL相同,但是
sessionStorage
是独立的,互不影响。
-
-
关闭对应浏览器标签或窗口,会清除对应的
sessionStorage
。
localStorage
只读的
localStorage
属性允许你访问一个Document
源(origin)的对象Storage
;存储的数据将保存在浏览器会话中。localStorage
类似sessionStorage
,但其区别在于:存储在localStorage
的数据可以长期保留;而当页面会话结束——也就是说,当页面被关闭时,存储在sessionStorage
的数据会被清除。
语法
其实和sessionStorage
一模一样。localStorage
的使用同样很简单,只有4种方式,分别是保存数据、获取数据、删除(保存的)数据以及删除全部(保存的)数据
-
保存数据:
localStorage.setItem("userId", "0823");
- 第一个参数是键名【key】
- 第二个参数是键值【value】
-
获取数据:
localStorage.getItem("userId");
- 参数为键名
-
删除数据:
localStorage.removeItem("userId");
- 参数为键名
-
删除全部数据:
localStorage.clear()
同理,需要注意键名和键值的格式:
被存储的键值对总是以 UTF-16 DOMString 的格式所存储,其使用两个字节来表示一个字符。对于对象、整数 key 值会自动转换成字符串形式。
在此不做赘述了。
性质
-
如果不进行手动清空,比如通过F12打开控制台,手动清空。哪怕浏览器被关闭了,或者电脑重启了,这个源下的
localStorage
照样存在。 -
之前我们提到不同的页面会话会对应各自的
sessionStorage
,但是localStorage
是被共享的,我们看一个例子:可以看到,两个不同的标签页,当触发新增和删除操作时,
localStorage
同步改变
本地存储的安全性
论数据存储在
localStorage
还是sessionStorage
,它们都特定于页面的协议。
意思就是受到同源策略的保护,即不同源的网页不能彼此访问各自的 localStorage
和 sessionStorage
。
结语
最后,我们会再聊一聊manifest
,将其作为HTML系列文章的结尾,manifest
是实现渐进式 Web 应用(PWA:Progressive Web App)的关键组成部分之一。在聊PWA的时候,我们也会提一下Service Worker
,它能够帮助PWA在没有网络连接的情况下(即离线状态下)工作,比如离线阅读某些新闻,通过Service Worker
的Cache
、Clients
、FetchEvent
等API实现。