js学习笔记(25)

Li Guangqiao - 23/05/2023

js

Section 25 客户端存储

js为什么会涉及到客户端存储呢?

目标:将不常更新、且相对公用的数据存储在客户端中,减少与服务端的交互,提高整体的性能。

原始目标: 要求HTTP头部包含会话信息。

cookie具有局限性

浏览器级别的局限性:

  1. 不超过300个cookie
  2. 每个cookie不超过4096字节
  3. 每个域不超过20个cookie
  4. 每个域不超过81920字节。

域级别的局限性:

浏览器每个域的cookie限制
Edge不超过50
Firefox不超过150个
Opera不超过180个
Safari和Chrome没有硬性限制

浏览器对于cookie的过限处理:

如果cookie总数超过了单域上限,浏览器会删除之前设置的cookie。

cookie构成

cookie_arc

js中的cookie接口只有*BOM(Brower Object Model)*的document.cookie

取值过程会获取页面中所有的有效cookie字符串(根据域、路径、过期时间和安全设置),以分号分隔。

子cookie的机制实际上是利用cookie的机制,将一连串的cookie信息编码成字符串,给cookie的其中一个键上。

使用cookie的注意事项

叫做HTTP-only的cookie。Http-only可以在浏览器设置,也可以在服务器设置。但只能在服务器上读取。这种cookie值js无法读取。

注意:因为所有cookie都会作为请求头部由浏览器发送给服务器,所以在cookie种保存大量信息可能影响特定域浏览器请求的性能。保存的cookie越大,请求完成的时间就越长。即使浏览器对cookie大小有限制,最好还是尽可能只能保存必要信息,避免性能问题。

对cookie的限制及其特性决定了cookie并不是存储大量数据的理想方式。


Web Storage

目标:

Web Storage的规范目标:

Storage类型

目标:可用于保存键值对数据,直到达到存储控件上限(取决于浏览器)。

Storage实例的特征方法:

注意:

//方法一
for (let i = 0, len = sessionStorage.length; i < len; i++){ 
 let key = sessionStorage.key(i); 
 let value = sessionStorage.getItem(key); 
    console.log(key,value);
 } 
//方法二
for (let key in sessionStorage){ 
 let value = sessionStorage.getItem(key); 
 console.log(key,value);
}

sessionStorage对象

sessionStorage 对象只存储会话数据,这意味着数据只会存储到浏览器关闭。这跟浏览器关闭时会消失的会话 cookie 类似。存储在 sessionStorage 中的数据不受页面刷新影响,可以在浏览器崩溃并重启后恢复。(取决于浏览器,Firefox 和 WebKit 支持,IE 不支持。)

localStorage对象

要访问同一个 localStorage 对象,页面必须来自同一个域(子域不可以)、在相同的端口上使用相同的协议。

注意:

localStorage和sessionStorage的区别,loaclStorage中的数据会保留到通过js删除或者用户清楚浏览器缓存。localStorage 数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览器而丢失

存储事件

每当 Storage 对象发生变化时,都会在文档上触发 storage 事件。使用属性或 setItem()设置值、使用 delete 或 removeItem()删除值,以及每次调用 clear()时都会触发这个事件。这个事件的事件对象有如下 4 个属性。

注意:

对于 sessionStorage 和 localStorage 上的任何更改都会触发 storage 事件,但 storage 事件不会区分这两者。


IndexedDB

Indexed Database API简称IndexedDB,浏览器中存储结构化数据的解决方案。

IndexedDB的设计完全异步,故通常是以请求的形式执行,这些请求会异步执行,产生成功的结果或错误。

常利用onerror和onsuccess事件处理程序来确定输出。

数据库

声明一个indexedDB数据库

  1. 调用**indexedDB.open()**方法,第一个参数是数据库名称,第二个参数是版本号

    **注意:**如果给定名称的数据库已存在,则会发送一个打开它的请求;如果不存在,则会发送创建并打

    开这个数据库的请求

  2. 通过添加onerroronsuccess事件去监听打开失败或者打开成功

    let db, 
     request, 
     version = 1; 
    request = indexedDB.open("admin", version); 
    request.onerror = (event) => 
     alert(`Failed to open: ${event.target.errorCode}`); 
    request.onsuccess = (event) => { 
     db = event.target.result; 
    };
    

对象存储

IndexedDB使用对象存储而不是表,并且单个数据库可以包含任意数量的对象存储。当一个值存储在对象存储库中时,它就与一个键相关联。根据对象存储是使用密钥路径(Key Path)还是密钥生成器(Key Generator),有几种不同的方式可以提供密钥。

下述表格多种以提供键的方式:

Key Path(keyPath)Key Generator(autoIncrement)Description
NN这个对象存储可以保存任何类型的值,甚至是像数字和字符串这样的基本值。每当要添加新值时,必须提供单独的键参数。
YN这个对象存储只能保存JavaScript对象。对象必须具有与Key Path同名的属性。
NY这个对象存储可以保存任何类型的值。键是自动生成的,如果您想使用特定的键,也可以提供单独的键参数。
YY这个对象存储只能保存JavaScript对象。通常生成一个键,并且生成的键的值存储在与Key Path同名的属性中的对象中。但是,如果这样的属性已经存在,则使用该属性的值作为键,而不是生成新键。
  1. 准备数据

    // 创建用户对象数组
    const customerData = [
      { ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" },
      { ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" },
    ];
    
  2. 创建IndexedDB存储我们的数据

    const dbName = "the_name";
    
    const request = indexedDB.open(dbName, 2);
    
    request.onerror = (event) => {
      // 处理数据库打开的异常处理
    };
    request.onupgradeneeded = (event) => {
      const db = event.target.result;
    
      //创建一个对象存储保存用户信息,并通过ssn作为键路径(默认为唯一索引)
      const objectStore = db.createObjectStore("customers", { keyPath: "ssn" });
    
      // 将name作为对象存储的一个查询索引,目标是为了通过name去筛选用户
      // 因为用户名是可重复的,所以我们唯一索引属性不能为true
      objectStore.createIndex("name", "name", { unique: false });
    
      // Create an index to search customers by email. We want to ensure that
      // no two customers have the same email, so use a unique index.
      // 将email作为对象存储的一个查询索引,目标是为了通过email去筛选用户
      // 因为邮箱是不可重复的,所以我们唯一索引属性设置为true
      objectStore.createIndex("email", "email", { unique: true });
    
      // 使用交易的oncomplete 来保证对象存储的创建在数据添加之前已经完成
      // 事务处理
      objectStore.transaction.oncomplete = (event) => {
        // Store values in the newly created objectStore.
        // 将用户信息存储到一个创建好的对象存储当中。
        const customerObjectStore = db
          .transaction("customers", "readwrite")
          .objectStore("customers");
        customerData.forEach((customer) => {
          customerObjectStore.add(customer);
        });
      };
    };
    
    

    注意:

    onupgradenneeded是唯一可以更改数据库结构的地方。在其中,您可以创建和删除对象存储,构建和删除索引。

事务:

const transaction = db.transaction(["customers"], "readwrite");
// Note: Older experimental implementations use the deprecated constant IDBTransaction.READ_WRITE instead of "readwrite".
// In case you want to support such an implementation, you can write:
// const transaction = db.transaction(["customers"], IDBTransaction.READ_WRITE);

在对新数据库执行任何操作之前,需要启动一个事务transaction。事务来自数据库对象,您必须指定希望事务跨越哪个对象存储。一旦进入事务内部,就可以访问保存数据和发出请求的对象存储。接下来,您需要决定是否要对数据库进行更改,还是只需要从数据库中读取数据。事务有三种可用的模式:只读(readonly)、读写(readwrite)和版本变更(versionchange)。

***注意:***通过在事务中使用正确的作用域和模式可以加速数据访问

事务方法:

transaction()**拥有两个参数,其中一个可选的,最后会返回一个事务对象。

事务生命周期:

事务与事件循环紧密相连。如果您创建一个事务并返回到事件循环而不使用它,那么该事务将变为非活动状态。保持事务活动的唯一方法是对其发出请求。当请求完成时,您将获得一个DOM事件,并且假设请求成功,您将有另一个机会在回调期间扩展事务。如果没有扩展事务就返回事件循环,则事务将变为非活动状态,依此类推。只要有挂起的请求,事务就保持活动状态。事务生命周期非常简单,但可能需要一点时间来适应。再举几个例子也会有所帮助。如果你开始看到TRANSACTION_INACTIVE_ERR错误代码,那么你就把事情搞砸了。

事务可以接收三种不同类型的DOM事件:错误(error)、中止(abort)和完成(complete)。我们已经讨论了错误事件冒泡的方式,因此事务从它生成的任何请求中接收错误事件。这里更微妙的一点是,错误的默认行为是中止发生错误的事务。除非先在错误事件上调用stopPropagation(),然后再执行其他操作来处理错误,否则整个事务将回滚。这种设计迫使您考虑和处理错误,但您总是可以添加一个捕获错误处理程序。

1)数据库添加数据

// Do something when all the data is added to the database.
transaction.oncomplete = (event) => {
  console.log("All done!");
};

transaction.onerror = (event) => {
  // Don't forget to handle errors!
};

const objectStore = transaction.objectStore("customers");
customerData.forEach((customer) => {
  const request = objectStore.add(customer);
  request.onsuccess = (event) => {
    // event.target.result === customer.ssn;
  };
});

调用add()生成的请求的结果是所添加值的键。所以在这种情况下,它应该等于所添加对象的ssn属性,因为对象存储使用ssn属性作为键路径。注意,add()函数要求数据库中不存在具有相同键的对象。如果您试图修改一个现有的条目,或者您不关心是否已经存在一个条目,那么您可以使用put()函数,如下面的更新数据库中的条目部分所示。

2)数据库删除数据

const request = db
  .transaction(["customers"], "readwrite")
  .objectStore("customers")
  .delete("444-44-4444");
request.onsuccess = (event) => {
  // It's gone!
};

3)数据库获取数据

4)更新数据库中的条目

5)使用游标

6)使用索引

7)指定游标的范围和方向

Li Guangqiao
Li Guangqiao

一个正在转rust的ExtJs前端工程师。迷信rust的整体发展,十分相信rust在各个领域都能发光发热,至少目前rust在很多领域上验证了其安全性、易维护性。但说实话对于我这种菜鸡也是真的难上手哈哈哈~~。 思路总结:

  • 万物诞生都会有一个需求来源,每一个改变都是为了解决某个问题,最后应该考虑如何去做
  • 学会掌握一些宏观的知识和理论:系统论、还原论
  • 工程化思想,如何描述整体,从整体架构到模块关联等 故学习东西应该像看地图一样,先看整体了解整体的结构,然后再聚焦每一个模块,对于模块的学习,思考三个问题,“是什么?”、“为什么?”、“怎么做?”;那么设计一个东西时也应该去考虑整体性和关联性。

有关于未来的发展,以下是鄙人的粗浅的观点:

  • 编程语言未来应该是每个人必备的工具
  • 未来的交互方式应该会以语言交互为主流
  • 下一个去中心化的技术方案出来之前,区块链依然是web3建立价值体系的基础技术方案,如何将现实价值和虚拟价值联通是进入数字世界的一个大难题。
  • 未来注定是AI的世界。AI的进化会伴随绝大部分人的退化,届时除了尖端人才,人们学习的重心会放在何处?