HTML5【3】

158 阅读16分钟

本篇文章是HTML新特性第三篇笔记 2023.12.29~ PS:希望今年内能更新完H5的第四篇笔记捏

  1. Web SQL 浏览器本地存储方式之一
  2. Web IndexedDB 浏览器本地存储方式之一 墙推!)
  3. Web Worker JS是单线程滴 WW让网页实现多线程!

Web SQL

前面讲过的Web Storage 以及接下来要讲的 Web SQL以及IndexedDB都是帮助开发者操作数据存储在浏览器端的技术。【更推荐indexeddb】

HTML5 的 Web SQL 仅被部分浏览器部分版本所支持.(Firefox 全版本不支持 Web SQL、IE 全版本不支持 Web SQL)

  • 选择替代方案:Web Storage / IndexedDB
  • polyfill 库:SQL.js

Web Storage:是在浏览器上模拟数据库,可以使用 JavaScript 来操作 SQL 语言完成对数据的读写操作。

数据操作

关于具体如何使用 JavaScript 代码操作 Web SQL。

打开数据库 —— openDatabase()方法

这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。它接受四个参数:

  1. 数据库名称:一个字符串,用于指定要打开的数据库的名称。如果不存在,则会创建一个新的数据库。

(因此运行此命令并不需要提前创建数据库,此命令如果没有数据库就会自动创建数据库。)

  1. 版本号:一个字符串,用于指定要打开的数据库的版本号。如果版本号不同,则会触发 onversionchange 事件。
  2. 数据库描述:一个可选的字符串,用于描述该数据库的内容和用途。
  3. 数据库大小:一个可选的整数值,用于指定数据库的大小(以字节为单位)。如果省略该参数,则使用默认值(通常是 5MB)。
// 打开 juejin_courses 数据库
const db = openDatabase('juejin_courses', '1.0', 'Juejin Course Database', 2 * 1024 * 1024);

​ 操作完成后打开浏览器控制台 Application -> Web SQL 面板,即可看到 juejin_courses 已经存在。

创建表 —— executeSql()方法

这个方法用于执行 SQL 查询和更新操作,并接收以下参数:

  1. SQL 查询语句或更新语句。
  2. 可选的参数数组,用于将变量绑定到查询或更新语句中。
  3. 成功回调函数,当查询或更新操作成功完成时执行。
  4. 可选的错误回调函数,当查询或更新操作失败时执行。

使用 executeSql() 方法执行了一条 CREATE TABLE 语句。下面示例代码创建了一个名为 html5_table 的表。这个表包含了四个列:idtitleauthorcreatedAt,分别用于存储整数值 id、文本值标题、文本值作者和整数值创建时间。id 列被指定为主键,(这意味着它的值必须是唯一的,且不能为空)。

// 创建一个名为 html5_table 的表
db.transaction(function(tx) {
  tx.executeSql('CREATE TABLE IF NOT EXISTS html5_table (id INTEGER PRIMARY KEY, title TEXT, author TEXT, createdAt INTEGER)');
});

需要注意的是,如果尝试创建一个已经存在的表,则会抛出一个错误。因此,为了避免这种情况,可以使用 IF NOT EXISTS 语句来检查表是否已经存在,如果不存在则创建它。

插入数据 —— 使用executeSql() 来执行INSERT INTO 语句

使用了prepared statement

db.transaction(function(tx) {
    tx.executeSql('INSERT INTO html5_table (title, author, createdAt) VALUES (?, ?, ?)', ['HTML5 语义化标签', '前端周公子', (new Date('2023-05-02')).valueOf()]);
    tx.executeSql('INSERT INTO html5_table (title, author, createdAt) VALUES (?, ?, ?)', ['HTML5 MathML', '前端周公子', (new Date('2023-05-03')).valueOf()]);
    tx.executeSql('INSERT INTO html5_table (title, author, createdAt) VALUES (?, ?, ?)', ['HTML5 Canvas', '前端周公子', (new Date('2023-05-04')).valueOf()]);
  }, function(tx, error) {
    console.log('插入失败: ' + error.message);
  }, function() {
    console.log('插入成功');
  });

查询数据 —— 使用 executeSql() 方法执行 SELECT 语句

查询数据之前,必须提前创建表,否则会查询失败。

 db.transaction(function(tx) {
  tx.executeSql('SELECT * FROM html5_table', [], function(tx, results) {
    const len = results.rows.length;
    for (let i = 0; i < len; i++) {
      console.log(results.rows.item(i));
    }
  }, function(tx, error) {
    console.log('查询失败: ' + error.message);
  });
});

更新数据 —— 使用 executeSql() 方法执行 UPDATE 语句

db.transaction(function(tx) {
    tx.executeSql('UPDATE html5_table SET author=? WHERE author=?', ['周公子在掘金', '前端周公子'], function(tx, results) {
      console.log('更新成功,更新的行数为 ' + results.rowsAffected);
    }, function(tx, error) {
      console.log('更新失败: ' + error.message);
    });
  });

删除数据 —— 使用 executeSql() 方法执行 DELETE 语句

db.transaction(function(tx) {
    tx.executeSql('DELETE FROM html5_table WHERE title=?', ['HTML5 Canvas'], function(tx, results) {
      console.log('删除成功,删除的行数为 ' + results.rowsAffected);
    }, function(tx, error) {
      console.log('删除失败: ' + error.message);
    });
  });

需要注意的是,如果删除的数据不符合表的约束条件,例如主键不存在或非空列值为空,删除操作将失败并抛出一个错误。

HTML5 IndexedDB

前浏览器数据储存方案,都不适合储存大型数据:

  • Cookie 的大小一般不超过 4KB,且每次请求都会发送回服务器。
  • Web Storage 的存储大小通常在 5MB 左右(每个浏览器的实现不同),而且不提供搜索功能,不能建立自定义的索引。
  • Web SQL 浏览器的支持程度过低,导致无法满足所有场景

​ HTML5 IndexedDB 是一种在浏览器中使用的纯客户端本地数据库。它允许开发者在 Web 应用程序中使用它在用户的浏览器中存储大量结构化数据,并且这些数据可以在离线时使用。IndexedDB 允许开发人员存储和检索复杂的数据类型,例如对象和数组,而不仅仅是简单的字符串键值对。

兼容性检查:

if (!window.indexedDB) {
 alert("当前浏览器不支持 IndexedDB,请升级您的浏览器!");
}

IndexedDB 特性

  • **使用异步 API**

    与 Web Storage 同步 API 不同,IndexedDB 的最大特性是使用异步 API 来管理数据库,这意味着它在执行数据库操作时不会阻塞应用程序的主线程。

  • 键值对存储

    IndexedDB内部采用对象仓库(Object Store)存储。所有类型数据(包括javascript对象)都可以存入。在对象仓库中,数据都是以键值对的形式保存的,每条数据信息都需要有对应的唯一主键。

  • 支持事务

    IndexedDB 支持事务(transaction)。也就是说在执行一系列操作的时候,只要其中一步发生了错误,就会终止事件,并且数据库回滚到事务发生前的状态。避免只修改了部分数据的情况。确保数据库数据的一致性。

  • 同源限制——不能访问跨域数据库

    IndexedDB受同源限制,网页只能访问自身域名下的数据库。

  • 大容量存储

    同样是本地永久化存储,IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。

  • 支持二进制存储

    IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。

IndexedDB 基本对象和概念

IndexedDB 是一种基于对象的数据库系统,它由多个不同的对象组成,这些对象一起构成了 IndexedDB 的 API。

  • 全局对象 IndexedDB

    它提供了打开、关闭数据库和创建新的数据库的方法。它还提供了检查当前浏览器是否支持 IndexedDB 的方法。

  • 数据库 IDBDatabase

    IDBDatabase 代表着一个 IndexedDB 的数据库。它提供了创建、删除、事务和对象存储等方法。IDBDatabase 对象是由 IndexedDB.open() 方法返回的。

  • 对象仓库 IDBObjectStore—— 类似于关系型数据库中的表Table。

    IDBObjectStore 代表着对象存储空间。对象存储空间用于存储和检索数据对象(数据对象可以理解为数据表中的数据项)。IDBObjectStore 提供了添加、删除、更新和检索数据对象等方法。

  • 索引 IDBIndex

    IDBIndex 充当着 IDBObjectStore 的索引,它提供了对 IDBObjectStore 中数据对象的快速访问。IDBIndex 可以使用一个或多个属性来创建索引,使得数据对象可以按照这些属性进行检索。

  • 事务 IDBTransaction

    IDBTransaction 代表了一个事务,它用于保证数据库操作的原子性和一致性。在一个事务中,可以执行多个数据库操作。如果其中任何一个操作失败,整个事务将被回滚。

  • 操作对象 IDBRequest

    IDBRequest 代表了一个异步请求,用于向 IndexedDB 数据库发送一个请求,并返回相应的结果。当执行 IndexedDB 操作时,IDBRequest 对象会被创建并返回,开发者可以使用它来获取操作的结果。

  • 指针 IDBCursor

    IDBCursor 代表了一个游标,它提供了对 IDBObjectStore 中数据对象的逐个访问能力。开发者可以使用 IDBCursor 来遍历一个 IDBObjectStore 中的所有数据对象。

IndexedDB 数据操作流程

包括:打开数据库、插入数据.add、读取数据get(id)、遍历数据、更新数据、删除数据

  • 打开 IndexedDB 数据库

在 IndexedDB 中并没有新建数据库的说法,如果不存在数据库则会自动创建.

  1. 使用 IndexDB.open(dbName, dbVersion) 方法打开数据库连接。它接收两个参数:数据库名称和版本号。

  2. 创建对象存储空间。在 onupgradeneeded 事件处理程序中,可以使用IDBDatabase 对象的 createObjectStore() 方法创建对象存储空间。该方法接收一个参数:**对象存储空间名称(相当于数据库中的表名称)。**还可以在该方法的第二个参数中指定键路径和选项,例如对象存储空间是否允许自动增加键值。

    createObjectStore("html5_courses", { keyPath: "id" });

  3. 处理异常错误。由于某些原因,可能会存在数据库打开失败的场景,可以使用 onerror 事件对异常情况进行处理。

  4. 关闭数据库连接。在完成数据库操作后,需关闭数据库连接。可以在 onsuccess 事件处理程序中访问 IDBDatabase 对象,并在它上面调用 close() 方法来关闭数据库连接。

以下代码我们创建了一个名为 JuejinCourses 的数据库,然后在其中新建了一个数据库对象名为 html5_courses,并指定了 id 自增以及索引是 title 字段:

const dbName = "JuejinCourses";
const dbVersion = 1.0;

const request = indexedDB.open(dbName, dbVersion);

request.onerror = function(event) {
  console.log("无法打开数据库");
};

request.onupgradeneeded = function(event) {
  const db = event.target.result;
  // 在这里创建和修改数据库对象
  const objectStore = db.createObjectStore("html5_courses", { keyPath: "id" });
  // createIndex() 的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)。
  objectStore.createIndex("title", "title", { unique: true });
};

request.onsuccess = function(event) {
  const db = event.target.result;
  console.log(`数据库 ${db.name} 已打开`);
};
  • 插入数据

IndexedDB 的所有数据操作都通过 transaction() 方法来进行,对于插入数据的操作,在数据库已经创建并打开后可以使用如下方式进行数据插入。

// 向数据库插入一条数据

const transaction = db.transaction(["html5_courses"], "readwrite");
const objectStore = transaction.objectStore("html5_courses");
objectStore.add({ id: 1, title: 'HTML5 简介', author: '前端周公子', createdAt: (new Date('2023-05-01')).valueOf() });
// 向数据库批量插入数据
const data = [
    { id: 2, title: "HTML5 语义化标签", author: "前端周公子", createdAt: (new Date('2023-05-02')).valueOf() },
    { id: 3, title: "HTML5 MathML", author: "前端周公子", createdAt: (new Date('2023-05-03')).valueOf() },
    { id: 4, title: "HTML5 Canvas", author: "前端周公子", createdAt: (new Date('2023-05-04')).valueOf() },
  ];
const transaction = db.transaction(["html5_courses"], "readwrite");
const objectStore = transaction.objectStore("html5_courses");

data.forEach(function(item) {
  **const request = objectStore.add(item);    //逐个添加data中的数据item
    request.onsuccess = function(event) {
      console.log("数据已添加");
    };
  });

transaction.oncomplete = function(event) {
    console.log("批量添加数据已完成");
    db.close();
  };
  • 读取数据

同样是通过 transaction 方法来进行。

​ 1. 读取一条数据 get(id)

// 读取一条数据,通过主键
const transaction = db.transaction(["html5_courses"], "readonly");
const objectStore = transaction.objectStore("html5_courses");
// 获取 id = 1 的数据
const req = objectStore.get(1);

//成功or失败判断及提示
req.onerror = function(event) {
  console.log('事务失败');
};

req.onsuccess = function(event) {
    if (req.result) {
      console.log('Title: ' + req.result.title);
      console.log('Author: ' + req.result.author);
      console.log('CreatedAt: ' + new Date(req.result.createdAt));
    } else {
      console.log('未获得数据记录');
    }
};
2.  读取全部数据 `getAll()`  —— 读取对象存储空间中的所有数据
const transaction = db.transaction(["html5_courses"], "readonly");
const objectStore = transaction.objectStore("html5_courses");
// 使用 getAll() 方法读取对象存储空间中的所有数据
objectStore.getAll().onsuccess = function(event) {
  const data = event.target.result;
  console.log(data);
};
  • 遍历数据

通过 transaction 方法 。除了批量读取全部数据之外,还可以通过使用指针对象完成遍历数据的操作

// 遍历数据
const transaction = db.transaction(["html5_courses"], "readonly");
const objectStore = transaction.objectStore("html5_courses");

objectStore.openCursor().onsuccess = function(event) {
  console.log("开始遍历数据");
  const cursor = event.target.result;   //cursor 存的是当前指向的对象
  if (cursor) {
    console.log("Title: " + cursor.value.title);
    console.log("Author: " + cursor.value.author);
    console.log("CreatedAt: " + new Date(cursor.value.createdAt));
    cursor.continue();
  } else {
    console.log("没有更多数据了!");
  }
};
  • 更新数据

通过 transaction 方法来进行,使用 put 方法进行数据更新。

// 更新数据
const transaction = db.transaction(["html5_courses"], "readwrite");
const objectStore = transaction.objectStore("html5_courses");

const req = objectStore.put({ id: 1, title: 'HTML5 简介', author: '周公子在掘金', createdAt: (new Date('2023-05-01')).valueOf() });

req.onsuccess = function (event) {
  console.log('数据更新成功');
};

req.onerror = function (event) {
  console.log('数据更新失败');
}
  • 删除数据

通过 transaction 方法来进行,使用 delete(id) 方法进行数据删除。

// 删除数据
const transaction = db.transaction(["html5_courses"], "readwrite");
const objectStore = transaction.objectStore("html5_courses");

const req = objectStore.delete(1);

req.onsuccess = function (event) {
  console.log('数据删除成功');
};

req.onerror = function (event) {
  console.log('数据删除失败');
}

Web三种存储对比

Web StorageWeb SQLIndexedDB
优点Web Storage API 简单易用,可以快速存储和检索少量数据,不需要复杂的配置和设置。Web SQL 具有完整的 SQL 支持,可以存储和检索复杂的数据类型。IndexedDB 可以存储和检索复杂的数据类型,支持异步操作和事务管理,适合存储大量的结构化数据。
缺点只能存储字符串类型的数据,存储容量有限。Web SQL 已经被弃用,不再是 HTML5 的一部分,只能在一些旧版本的浏览器中使用。IndexedDB API 比较复杂,使用起来可能需要一些学习成本。
适用场景适合存储少量的配置信息和用户偏好设置等数据。适合存储大量的结构化数据,例如日志和用户数据等。适合存储大量结构化数据,或需要离线访问数据的应用程序。

在实际开发场景中,为了提升效率,一般都是使用第三方封装库来操作 IndexedDB: Dexie.js...

Web Workers

前期提要: JavaScript 性能瓶颈问题——浏览器中的 JavaScript 代码是单线程执行的,也就是说 Web 页面的 JavaScript 脚本通常运行在浏览器的主线程中,即 UI 线程中。当 JavaScript 执行时间较长时,它会阻塞 UI 线程,导致页面无法响应用户的交互,甚至会出现页面卡顿的情况。

HTML5 中新出的 Web Worker API 就能很好的帮助开发者解决此类问题。

Web Workers 是一种在浏览器后台线程中运行 JavaScript 的机制。它允许将长时间运行的 JavaScript 脚本放在后台线程中运行,从而不会阻塞 UI 线程(也就是我们常说的主线程)。Web Workers 可以与主线程进行通信,从而将后台线程中运行任务结果返回给主线程,并且此过程完全由开发者自行掌控。

总而言之:HTML5 Web Workers 提供了一种在 Web 应用程序中使用多线程的方法,它的出现使得开发人员可以利用计算机的多核 CPU 来加速计算密集型任务,提高 Web 应用程序的性能和响应速度同时提高用户的交互体验。

Web Worker的基础用法

  1. 创建Worker文件

通常可以命名为 worker.js(可以自己起任何名),然后在此文件内将需要在子线程中运行的逻辑代码键入进去。

// worker.js
//添加监听事件:
self.addEventListener('message', function(e) {
  const n = e.data;
  const result = fibonacci(n);
  self.postMessage(result);
}, false);


//计算斐波那契数列的函数:
function fibonacci(n) {
  if (n <= 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}
  • Worker 线程内部需要有一个监听函数addEventListener(),用来监听 message 事件,也就是监听主线程传过来的数据。
  • self 代表子线程本身,也就是子线程的全局对象,等价于 this | global 等全局对象。。
  • 可以通过 self.postMessage(result) 方法来给主线程传回处理后的结果数据。
  1. 创建Worker对象

创建完Web Worker文件,并且在 Worker 子线程里实现了事件监听以及逻辑处理之后,需要在主线程创建 Worker 对象。

  • 主线程创建 Worker 对象 new Worker('xxx.js') 括号内填写的是Web Worker js文件的路径
const worker = new Worker('js/worker.js');
  • 主线程向子线程传递数据
worker.postMessage(data);
  • 主线程接收子线程传递回的结果
worker.addEventListener('message', function(e) {
  result.textContent = e.data;
 }, false);

至此 Worker 对象被创建成功,并且相应的事件都在主线程添加完成,主子线程之间就可以互相通信。

​ 3.终止Worker对象 —— 使用 terminate() 方法

当创建 Worker 后,主线程会不断监听来自子线程的消息,即便子线程已经完成任务。这个时候需要终止Worker以释放浏览器资源。

worker.terminal();

Web Workers 分类

Web Workers 可以分为两种类型:Dedicated Workers 和 Shared Workers。

  • Dedicated Workers

Dedicated Workers 是与单个页面关联的线程,我们日常开发中经常用到的大体上都是此类 Workers。

  • Shared Workers

与 Dedicated Workers 不同,Shared Workers 则是可以被多个页面所共享的线程,它们可以在多个页面之间传递信息。注意:Shared Workers 只能在同一主机名下的页面之间共享消息,这意味着它不能用于跨域通信。

关于 Shared Workers 的创建方式和使用方式,也与 Dedicated Workers 有不同之处,整个过程简单描述如下:

  1. 创建一个 SharedWorker 对象,并监听 onconnect 事件。
  2. 当一个页面连接到 Shared Worker 时,将添加一个 message 事件监听器,以接收从页面发送的消息。
  3. 开发者应该保存一个数组来存储所有连接到 Shared Worker 的页面的端口,以便必要时向它们发送消息。

关于如何在Web Worker中使用第三方库&错误处理

使用 importScripts() 函数在Worker文件中导入第三方.js文件

详细见:HTML5 入门教程 - 前端周公子 - 掘金小册 (juejin.cn)

使用Web Workers注意事项

  • 无法直接访问 DOM and 主线程数据

Web Workers 运行在一个与主线程分离的上下文中,因此它们不能直接访问 DOM,这是使用 Web Workers 之前需要了解并注意的最重要的一点。如果您需要操作 DOM,开发者需要向 Web Workers 传递必要的数据,然后在 Worker 线程中执行操作,并将结果传递回主线程。

  • 资源限制

Web Workers 受到一些资源限制,例如它们不能打开新的窗口或访问本地文件系统。如果您需要执行这些操作,可能需要使用其他技术,例如 Shared Workers 或 Service Workers 等方案。

兼容性

第三方库:Polyfill 库 worker-loader