前端必刷系列之红宝书——第 25 章
"红宝书" 通常指的是《JavaScript 高级程序设计》,这是一本由 Nicholas C. Zakas(尼古拉斯·扎卡斯)编写的 JavaScript 书籍,是一本广受欢迎的经典之作。这本书是一部翔实的工具书,满满的都是 JavaScript 知识和实用技术。
不管你有没有刷过红宝书,如果现在还没掌握好,那就一起来刷红宝书吧,go!go!go!
系列文章:
第一部分:基本知识(重点、反复阅读)
第二部分:进阶内容(重点、反复阅读)
第三部分:BOM 和 DOM (着重学习)
第 25 章 客户端存储
cookie
Cookies
是存储在用户计算机上的小型文本文件,由网站使用以存储有关用户的信息。
它们通常包含有关用户和网站之间状态的信息,用于跟踪用户、记录登录状态、存储购物车内容等。
Cookies 是 HTTP 的一部分,由浏览器自动处理,每个 Cookie 都与一个特定的域相关联。
// 创建一个 Cookie
document.cookie = "username=John Doe; expires=Thu, 18 Dec 2022 12:00:00 UTC; path=/";
// 读取所有 Cookie
var allCookies = document.cookie;
// 读取特定的 Cookie
var usernameCookie = getCookie("username");
function getCookie(name) {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.startsWith(name + '=')) {
return cookie.substring(name.length + 1);
}
}
return null;
}
一些常见的 Cookies 限制:
- 同源策略: 浏览器遵循同源策略,即一个网站下设置的 Cookie 一般不会被其他网站访问。这是为了保护用户的隐私和防止跨站点请求伪造(CSRF)攻击。
- 域限制: Cookie 受同源策略的限制,但可以通过设置
domain
属性来使 Cookie 在主域和其子域之间共享。然而,该域名必须包含在当前页面的域名中,而不是其他域名。 - 路径限制: Cookie 可以通过设置
path
属性指定其有效路径。只有在该路径及其子路径下的页面才能访问该 Cookie。 - 安全限制: 设置
Secure
属性的 Cookie 只能通过 HTTPS 连接传输。这是为了保护敏感信息,确保它在传输过程中不容易被窃听。 - Cookie 数量和大小限制: 不同浏览器对于每个域名下可以存储的 Cookie 数量和总大小有限制。超过这些限制可能会导致一些 Cookie 被丢弃。大约限制为 4KB。
- 过期时间限制: 如果不设置过期时间,Cookie 默认是会话级别的,将在用户关闭浏览器时失效。可以通过设置
expires
属性或max-age
属性来指定 Cookie 的过期时间。 - 第三方 Cookie: 浏览器通常会限制第三方 Cookie 的存储和访问,以减少跟踪用户的能力。一些浏览器对第三方 Cookie 的处理方式可能有所不同。
Cookie 属性:
- Name-Value Pair(名称-值对): 每个 Cookie 都有一个名称和一个相应的值。
- Expires(过期时间): Cookie 的过期时间,过了这个时间后,浏览器会自动删除该 Cookie。
- Domain(域): Cookie 关联的域。只有在该域及其子域下才能访问该 Cookie。
- Path(路径): Cookie 的有效路径。只有在指定路径下的页面才能访问该 Cookie。
- Secure: 表示此 Cookie 只能通过 HTTPS 连接传输。
- HttpOnly: 如果设置了
HttpOnly
属性,那么该 Cookie 将无法通过 JavaScript 访问。这有助于防止一些跨站脚本攻击。 - SameSite: 用于防止跨站点请求伪造攻击。
SameSite
属性可以设置为"Strict"
、"Lax"
或null
。
class CookieUtil {
static get(name) {
let cookieName = `${encodeURIComponent(name)}=`,
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null;
if (cookieStart > -1){
let cookieEnd = document.cookie.indexOf(";", cookieStart);
if (cookieEnd == -1){
cookieEnd = document.cookie.length;
}
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart
+ cookieName.length, cookieEnd));
}
return cookieValue;
}
static set(name, value, expires, path, domain, secure) {
let cookieText =
`${encodeURIComponent(name)}=${encodeURIComponent(value)}`
if (expires instanceof Date) {
cookieText += `; expires=${expires.toGMTString()}`;
}
if (path) {
cookieText += `; path=${path}`;
}
if (domain) {
cookieText += `; domain=${domain}`;
}
if (secure) {
cookieText += "; secure";
}
document.cookie = cookieText;
}
static unset(name, path, domain, secure) {
CookieUtil.set(name, "", new Date(0), path, domain, secure);
}
};
// 设置 cookie
CookieUtil.set("name", "Nicholas");
CookieUtil.set("book", "Professional JavaScript");
// 读取 cookie
alert(CookieUtil.get("name")); // "Nicholas"
alert(CookieUtil.get("book")); // "Professional JavaScript"
// 删除 cookie
CookieUtil.unset("name");
CookieUtil.unset("book");
// 设置有路径、域和过期时间的 cookie
CookieUtil.set("name", "Nicholas", "/books/projs/", "www.wrox.com", new Date("January 1, 2010"));
// 删除刚刚设置的 cookie
CookieUtil.unset("name", "/books/projs/", "www.wrox.com");
// 设置安全 cookie
CookieUtil.set("name", "Nicholas", null, null, null, true);
子 cookies:
class SubCookieUtil {
// 取得一个子 cookie 的值
static get(name, subName) {
let subCookies = SubCookieUtil.getAll(name);
return subCookies ? subCookies[subName] : null;
}
// 取得所有子 cookie
static getAll(name) {
let cookieName = encodeURIComponent(name) + "=",
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null,
cookieEnd,
subCookies,
parts,
result = {};
if (cookieStart > -1) {
cookieEnd = document.cookie.indexOf(";", cookieStart);
if (cookieEnd == -1) {
cookieEnd = document.cookie.length;
}
cookieValue = document.cookie.substring(cookieStart + cookieName.length, cookieEnd);
if (cookieValue.length > 0) {
subCookies = cookieValue.split("&");
for (let i = 0, len = subCookies.length; i < len; i++) {
parts = subCookies[i].split("=");
result[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]);
}
return result;
}
}
return null;
}
static set(name, subName, value, expires, path, domain, secure) {
let subcookies = SubCookieUtil.getAll(name) || {};
subcookies[subName] = value;
SubCookieUtil.setAll(name, subcookies, expires, path, domain, secure);
}
static setAll(name, subcookies, expires, path, domain, secure) {
let cookieText = encodeURIComponent(name) + "=",
subcookieParts = new Array(),
subName;
for (subName in subcookies){
if (subName.length > 0 && subcookies.hasOwnProperty(subName)){
subcookieParts.push('${encodeURIComponent(subName)}=${encodeURIComponent(subcookies[subName])}');
}
}
if (cookieParts.length > 0) {
cookieText += subcookieParts.join("&");
if (expires instanceof Date) {
cookieText += `; expires=${expires.toGMTString()}`;
}
if (path) {
cookieText += `; path=${path}`;
}
if (domain) {
cookieText += `; domain=${domain}`;
}
if (secure) {
cookieText += "; secure";
}
} else {
cookieText += `; expires=${(new Date(0)).toGMTString()}`;
}
document.cookie = cookieText;
}
static unset(name, subName, path, domain, secure) {
let subcookies = SubCookieUtil.getAll(name);
if (subcookies){
delete subcookies[subName]; // 删除
SubCookieUtil.setAll(name, subcookies, null, path, domain, secure);
}
}
static unsetAll(name, path, domain, secure) {
SubCookieUtil.setAll(name, null, new Date(0), path, domain, secure);
}
};
Web Storage
Web Storage 的第 2 版定义了两个对象:localStorage 和 sessionStorage
。
localStorage 是永久存储机制,sessionStorage 是跨会话的存储机制
。
这两种浏览器存储 API 提供了在浏览器中不受页面刷新影响而存储数据的两种方式。
localStorage:
- 长期存储:
localStorage
中的数据是永久性的,除非用户手动清除浏览器缓存或网站清除自己的数据,否则数据将一直保存在本地。 - 大小限制: 通常允许存储的数据大小为 5MB。
- 作用域: 存储在
localStorage
中的数据对于相同域名的所有页面都是共享的。
// 存储数据
localStorage.setItem('key', 'value');
// 读取数据
var value = localStorage.getItem('key');
// 删除数据
localStorage.removeItem('key');
// 清空所有数据
localStorage.clear();
sessionStorage:
- 临时存储:
sessionStorage
中的数据在用户会话结束时被清除,关闭浏览器窗口或标签页会终止用户会话。 - 大小限制: 通常允许存储的数据大小也为 5MB。
- 作用域: 存储在
sessionStorage
中的数据仅对于打开的窗口或标签页是有效的,不同窗口之间不共享数据。
// 存储数据
sessionStorage.setItem('key', 'value');
// 读取数据
var value = sessionStorage.getItem('key');
// 删除数据
sessionStorage.removeItem('key');
// 清空所有数据
sessionStorage.clear();
IndexedDB
IndexedDB
(Indexed Database)是一个在浏览器中提供的低级别的客户端存储数据库,用于存储大量结构化数据。IndexedDB 是一个 NoSQL 数据库
,它使用对象存储空间来存储和检索数据
。相较于 Web Storage,IndexedDB 更适用于存储大量数据和需要进行复杂查询的场景
。
IndexedDB 的设计几乎完全是异步的
。为此,大多数操作以请求的形式执行,这些请求会异步执行,产生成功的结果或错误。绝大多数 IndexedDB 操作要求添加 onerror 和 onsuccess 事件处理程序来确定输出。
数据库
IndexedDB 是类似于 MySQL 或 Web SQL Database 的数据库。与传统数据库最大的区别在于,IndexedDB 使用对象存储而不是表格保存数据
。IndexedDB 数据库就是在一个公共命名空间下的一组对象存储,类似于 NoSQL 风格的实现。
对象存储
建立了数据库连接之后,下一步就是使用对象存储。
事务
创建了对象存储之后,剩下的所有操作都是通过事务完成的。事务要通过调用数据库对象的transaction()方法创建。任何时候,只要想要读取或修改数据,都要通过事务把所有修改操作组织起来。
插入对象
拿到了对象存储的引用后,就可以使用 add()或 put()写入数据了。
这两个方法都接收一个参数,即要存储的对象,并把对象保存到对象存储。
这两个方法只在对象存储中已存在同名的键时有区别。这种情况下,add()会导致错误,而 put()会简单地重写该对象。更简单地说,可以把 add()想象成插入新值,而把 put()想象为更新值。
通过游标查询
使用事务可以通过一个已知键取得一条记录。如果想取得多条数据,则需要在事务中创建一个游标
。
游标
是一个指向结果集的指针。与传统数据库查询不同,游标不会事先收集所有结果。相反,游标指向第一个结果,并在接到指令前不会主动查找下一条数据。
需要在对象存储上调用 openCursor()方法创建游标。
键范围
使用游标会给人一种不太理想的感觉,因为获取数据的方式受到了限制。使用键范围
(key range)可以让游标更容易管理。键范围对应 IDBKeyRange 的实例。
并发问题
IndexedDB 虽然是网页中的异步 API,但仍存在并发问题。如果两个不同的浏览器标签页同时打开了同一个网页,则有可能出现一个网页尝试升级数据库而另一个尚未就绪的情形。有问题的操作是设置数据库为新版本,而版本变化只能在浏览器只有一个标签页使用数据库时才能完成
。
第一次打开数据库时,添加 onversionchange 事件处理程序非常重要。另一个同源标签页将数据库打开到新版本时,将执行此回调。对这个事件最好的回应是立即关闭数据库,以便完成版本升级。例如:
let request, database;
request = indexedDB.open("admin", 1);
request.onsuccess = (event) => {
database = event.target.result;
database.onversionchange = () => database.close();
};
应该在每次成功打开数据库后都指定 onversionchange 事件处理程序。记住,onversionchange 有可能会被其他标签页触发。
通过始终都指定这些事件处理程序,可以保证 Web 应用程序能够更好地处理与 IndexedDB 相关的并发问题。
// 打开数据库
var request = indexedDB.open('myDatabase', 1);
// 数据库升级事件
request.onupgradeneeded = function(event) {
var db = event.target.result;
// 创建对象存储空间
var objectStore = db.createObjectStore('myObjectStore', { keyPath: 'id' });
// 添加索引
objectStore.createIndex('nameIndex', 'name', { unique: false });
};
// 打开数据库成功事件
request.onsuccess = function(event) {
var db = event.target.result;
console.log('Database opened successfully');
// 添加数据
var transaction = db.transaction(['myObjectStore'], 'readwrite');
var objectStore = transaction.objectStore('myObjectStore');
var data = { id: 1, name: 'John Doe', age: 25 };
var addRequest = objectStore.add(data);
addRequest.onsuccess = function(event) {
console.log('Data added successfully');
// 读取数据
var getTransaction = db.transaction(['myObjectStore'], 'readonly');
var getObjectStore = getTransaction.objectStore('myObjectStore');
var getRequest = getObjectStore.get(1);
getRequest.onsuccess = function(event) {
var retrievedData = event.target.result;
console.log('Retrieved data:', retrievedData);
// 删除数据
var deleteTransaction = db.transaction(['myObjectStore'], 'readwrite');
var deleteObjectStore = deleteTransaction.objectStore('myObjectStore');
var deleteRequest = deleteObjectStore.delete(1);
deleteRequest.onsuccess = function(event) {
console.log('Data deleted successfully');
};
};
};
};
// 打开数据库失败事件
request.onerror = function(event) {
console.log('Database error: ' + event.target.errorCode);
};
未完待续......
参考资料
《JavaScript 高级程序设计》(第 4 版)
转载自:https://juejin.cn/post/7330159032991285284