likes
comments
collection
share

前端必刷系列之红宝书——第 25 章

作者站长头像
站长
· 阅读数 61

"红宝书" 通常指的是《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 限制:

  1. 同源策略: 浏览器遵循同源策略,即一个网站下设置的 Cookie 一般不会被其他网站访问。这是为了保护用户的隐私和防止跨站点请求伪造(CSRF)攻击。
  2. 域限制: Cookie 受同源策略的限制,但可以通过设置 domain 属性来使 Cookie 在主域和其子域之间共享。然而,该域名必须包含在当前页面的域名中,而不是其他域名。
  3. 路径限制: Cookie 可以通过设置 path 属性指定其有效路径。只有在该路径及其子路径下的页面才能访问该 Cookie。
  4. 安全限制: 设置 Secure 属性的 Cookie 只能通过 HTTPS 连接传输。这是为了保护敏感信息,确保它在传输过程中不容易被窃听。
  5. Cookie 数量和大小限制: 不同浏览器对于每个域名下可以存储的 Cookie 数量和总大小有限制。超过这些限制可能会导致一些 Cookie 被丢弃。大约限制为 4KB。
  6. 过期时间限制: 如果不设置过期时间,Cookie 默认是会话级别的,将在用户关闭浏览器时失效。可以通过设置 expires 属性或 max-age 属性来指定 Cookie 的过期时间。
  7. 第三方 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
评论
请登录