主頁 > 前端設計 > JavaScript(九)(IndexedDB API、Web Worker)

JavaScript(九)(IndexedDB API、Web Worker)

2021-08-01 07:52:06 前端設計

JavaScript(九)(IndexedDB API、Web Worker)

文章目錄

  • JavaScript(九)(IndexedDB API、Web Worker)
    • 67. IndexedDB API
      • 67.1 概述
      • 67.2 基本概念
          • **(1)資料庫**
          • **(2)物件倉庫**
          • **(3)資料記錄**
          • **(4)索引**
          • **(5)事務**
      • 67.3 操作流程
        • 67.3.1 打開資料庫
          • **(1)error 事件**
          • **(2)success 事件**
          • **(3)upgradeneeded 事件**
        • 67.3.2 新建資料庫
        • 67.3.3 新增資料
        • 67.3.4 讀取資料
        • 67.3.5 遍歷資料
        • 67.3.6 更新資料
        • 67.3.7 洗掉資料
        • 67.3.8 使用索引
      • 67.4 indexedDB 物件
        • 67.4.1 indexedDB.open()
        • 67.4.2 indexedDB.deleteDatabase()
        • 67.4.3 indexedDB.cmp()
      • 67.5 IDBRequest 物件
      • 67.6 IDBDatabase 物件
        • 67.6.1 屬性
        • 67.6.2 方法
      • 67.7 IDBObjectStore 物件
        • 67.7.1 屬性
        • 67.7.2 方法
          • **(1)IDBObjectStore.add()**
          • **(2)IDBObjectStore.put()**
          • **(3)IDBObjectStore.clear()**
          • **(4)IDBObjectStore.delete()**
          • **(5)IDBObjectStore.count()**
          • **(6)IDBObjectStore.getKey()**
          • **(7)IDBObjectStore.get()**
          • **(8)IDBObjectStore.getAll()**
          • **(9)IDBObjectStore.getAllKeys()**
          • **(10)IDBObjectStore.index()**
          • **(11)IDBObjectStore.createIndex()**
          • **(12)IDBObjectStore.deleteIndex()**
          • **(13)IDBObjectStore.openCursor()**
          • **(14)IDBObjectStore.openKeyCursor()**
      • 67.8 IDBTransaction 物件
      • 67.9 IDBIndex 物件
      • 67.10 IDBCursor 物件
      • 67.11 IDBKeyRange 物件
    • 68. Web Worker
      • 68.1 概述
      • 68.2 基本用法
        • 68.2.1 主執行緒
        • 68.2.2 Worker 執行緒(self子執行緒)
        • 68.2.3 Worker 加載腳本
        • 68.2.4 錯誤處理
        • 68.2.5 關閉 Worker
      • 68.3 資料通信
      • 68.4 同頁面的 Web Worker
      • 68.5 實體:Worker 執行緒完成輪詢
      • 68.6 實體: Worker 新建 Worker
      • 68.7 API
        • 68.7.1 主執行緒
        • 68.7.2 Worker 執行緒

67. IndexedDB API

67.1 概述

隨著瀏覽器的功能不斷增強,越來越多的網站開始考慮,將大量資料儲存在客戶端,這樣可以減少從服務器獲取資料,直接從本地獲取資料,

現有的瀏覽器資料儲存方案,都不適合儲存大量資料:Cookie 的大小不超過 4KB,且每次請求都會發送回服務器;LocalStorage 在 2.5MB 到 10MB 之間(各家瀏覽器不同),而且不提供搜索功能,不能建立自定義的索引,所以,需要一種新的解決方案,這就是 IndexedDB 誕生的背景,

通俗地說,**IndexedDB 就是瀏覽器提供的本地資料庫,它可以被網頁腳本創建和操作,IndexedDB 允許儲存大量資料,提供查找介面,還能建立索引,**這些都是 LocalStorage 所不具備的,就資料庫型別而言,IndexedDB 不屬于關系型資料庫(不支持 SQL 查詢陳述句),更接近 NoSQL 資料庫,

IndexedDB 具有以下特點,

(1)鍵值對儲存, IndexedDB 內部采用物件倉庫(object store)存放資料,所有型別的資料都可以直接存入,包括 JavaScript 物件,物件倉庫中,資料以“鍵值對”的形式保存,每一個資料記錄都有對應的主鍵,主鍵是獨一無二的,不能有重復,否則會拋出一個錯誤,

(2)異步, IndexedDB 操作時不會鎖死瀏覽器,用戶依然可以進行其他操作,這與 LocalStorage 形成對比,后者的操作是同步的,異步設計是為了防止大量資料的讀寫,拖慢網頁的表現,

(3)支持事務, IndexedDB 支持事務(transaction),這意味著一系列操作步驟之中,只要有一步失敗,整個事務就都取消,資料庫回滾到事務發生之前的狀態,不存在只改寫一部分資料的情況,

(4)同源限制, IndexedDB 受到同源限制,每一個資料庫對應創建它的域名,網頁只能訪問自身域名下的資料庫,而不能訪問跨域的資料庫,

(5)儲存空間大, IndexedDB 的儲存空間比 LocalStorage 大得多,一般來說不少于 250MB,甚至沒有上限,

(6)支持二進制儲存, IndexedDB 不僅可以儲存字串,還可以儲存二進制資料(ArrayBuffer 物件和 Blob 物件),

67.2 基本概念

IndexedDB 是一個比較復雜的 API,涉及不少概念,它把不同的物體,抽象成一個個物件介面,學習這個 API,就是學習它的各種物件介面,

  • 資料庫:IDBDatabase 物件
  • 物件倉庫:IDBObjectStore 物件
  • 索引: IDBIndex 物件
  • 事務: IDBTransaction 物件
  • 操作請求:IDBRequest 物件
  • 指標: IDBCursor 物件
  • 主鍵集合:IDBKeyRange 物件

下面是一些主要的概念,

(1)數據庫

資料庫是一系列相關資料的容器,每個域名(嚴格的說,是協議 + 域名 + 埠)都可以新建任意多個資料庫,

IndexedDB 資料庫有版本的概念,同一個時刻,只能有一個版本的資料庫存在,如果要修改資料庫結構(新增或洗掉表、索引或者主鍵),只能通過升級資料庫版本完成,

(2)物件倉庫

每個資料庫包含若干個物件倉庫(object store),它類似于關系型資料庫的表格,

(3)資料記錄

物件倉庫保存的是資料記錄,每條記錄類似于關系型資料庫的行,但是只有主鍵和資料體兩部分主鍵用來建立默認的索引,必須是不同的,否則會報錯,主鍵可以是資料記錄里面的一個屬性,也可以指定為一個遞增的整數編號,

 { id: 1, text: 'foo' }

上面的物件中,id屬性可以當作主鍵,

資料體可以是任意資料型別,不限于物件,

(4)索引

為了加速資料的檢索,可以在物件倉庫里面,為不同的屬性建立索引,

(5)事務

資料記錄的讀寫和刪改,都要通過事務完成,事務物件提供errorabortcomplete三個事件,用來監聽操作結果,

67.3 操作流程

IndexedDB 資料庫的各種操作,一般是按照下面的流程進行的,這個部分只給出簡單的代碼示例,用于快速上手,詳細的各個物件的 API 放在后文介紹,

67.3.1 打開資料庫

使用 IndexedDB 的第一步是打開資料庫,使用indexedDB.open()方法,

 var request = window.indexedDB.open(databaseName, version);

這個方法接受兩個引數,第一個引數是字串,表示資料庫的名字,如果指定的資料庫不存在,就會新建資料庫,第二個引數是整數,表示資料庫的版本,如果省略,打開已有資料庫時,默認為當前版本;新建資料庫時,默認為1

indexedDB.open()方法回傳一個 IDBRequest 物件,這個物件通過三種事件errorsuccessupgradeneeded,處理打開資料庫的操作結果,

(1)error 事件

error事件表示打開資料庫失敗,

 request.onerror = function (event) {
   console.log('資料庫打開報錯');
 };
(2)success 事件

success事件表示成功打開資料庫,

 var db;
 
 request.onsuccess = function (event) {
   db = request.result;
   console.log('資料庫打開成功');
 };

這時,通過request物件的result屬性拿到資料庫物件,

(3)upgradeneeded 事件

如果指定的版本號,大于資料庫的實際版本號,就會發生資料庫升級事件upgradeneeded

 var db;
 
 request.onupgradeneeded = function (event) {
   db = event.target.result;
 }

這時通過事件物件的target.result屬性,拿到資料庫實體,

67.3.2 新建資料庫

**新建資料庫與打開資料庫是同一個操作,**如果指定的資料庫不存在,就會新建,不同之處在于,后續的操作主要在upgradeneeded事件的監聽函式里面完成,因為這時版本從無到有,所以會觸發這個事件,

通常,新建資料庫以后,第一件事是新建物件倉庫(即新建表),

 request.onupgradeneeded = function(event) {
   db = event.target.result;
   var objectStore = db.createObjectStore('person', { keyPath: 'id' });
 }

上面代碼中,資料庫新建成功以后,新增一張叫做person的表格,主鍵是id

更好的寫法是先判斷一下,這張表格是否存在,如果不存在再新建,

 request.onupgradeneeded = function (event) {
   db = event.target.result;
   var objectStore;
   if (!db.objectStoreNames.contains('person')) {
     objectStore = db.createObjectStore('person', { keyPath: 'id' });
   }
 }

主鍵(key)是默認建立索引的屬性,比如,資料記錄是{ id: 1, name: '張三' },那么id屬性可以作為主鍵,主鍵也可以指定為下一層物件的屬性,比如{ foo: { bar: 'baz' } }foo.bar也可以指定為主鍵,

如果資料記錄里面沒有合適作為主鍵的屬性,那么可以讓 IndexedDB 自動生成主鍵,

 var objectStore = db.createObjectStore(
   'person',
   { autoIncrement: true }
 );

上面代碼中,指定主鍵為一個遞增的整數,

新建物件倉庫以后,下一步可以新建索引,

 request.onupgradeneeded = function(event) {
   db = event.target.result;
   var objectStore = db.createObjectStore('person', { keyPath: 'id' });
   objectStore.createIndex('name', 'name', { unique: false });
   objectStore.createIndex('email', 'email', { unique: true });
 }

上面代碼中,IDBObject.createIndex()的三個引數分別為索引名稱、索引所在的屬性、配置物件(說明該屬性是否包含重復的值),

67.3.3 新增資料

新增資料指的是向物件倉庫寫入資料記錄,這需要通過事務完成,

 function add() {
   var request = db.transaction(['person'], 'readwrite') //先新建一個事務
     .objectStore('person')  //通過`IDBTransaction.objectStore(name)`方法,拿到 IDBObjectStore 物件
     .add({ id: 1, name: '張三', age: 24, email: 'zhangsan@example.com' });//再通過表格物件的`add()`方法,向表格寫入一條記錄
 
   request.onsuccess = function (event) {
     console.log('資料寫入成功');
   };
 
   request.onerror = function (event) {
     console.log('資料寫入失敗');
   }
 }
 
 add();

上面代碼中,寫入資料需要新建一個事務,新建時必須指定表格名稱和操作模式(“只讀”或“讀寫”),新建事務以后,通過IDBTransaction.objectStore(name)方法,拿到 IDBObjectStore 物件再通過表格物件的add()方法,向表格寫入一條記錄

寫入操作是一個異步操作,通過監聽連接物件的success事件和error事件,了解是否寫入成功,

67.3.4 讀取資料

讀取資料也是通過事務完成,

 function read() {
    var transaction = db.transaction(['person']);
    var objectStore = transaction.objectStore('person');
    var request = objectStore.get(1);
 
    request.onerror = function(event) {
      console.log('事務失敗');
    };
 
    request.onsuccess = function( event) {
       if (request.result) {
         console.log('Name: ' + request.result.name);
         console.log('Age: ' + request.result.age);
         console.log('Email: ' + request.result.email);
       } else {
         console.log('未獲得資料記錄');
       }
    };
 }
 
 read();

上面代碼中,objectStore.get()方法用于讀取資料,引數是主鍵的值,

67.3.5 遍歷資料

遍歷資料表格的所有記錄,要使用指標物件 IDBCursor,

 function readAll() {
   var objectStore = db.transaction('person').objectStore('person');
 
    objectStore.openCursor().onsuccess = function (event) {
      var cursor = event.target.result;
 
      if (cursor) {
        console.log('Id: ' + cursor.key);
        console.log('Name: ' + cursor.value.name);
        console.log('Age: ' + cursor.value.age);
        console.log('Email: ' + cursor.value.email);
        cursor.continue();
     } else {
       console.log('沒有更多資料了!');
     }
   };
 }
 
 readAll();

上面代碼中,新建指標物件的openCursor()方法是一個異步操作,所以要監聽success事件,

67.3.6 更新資料

更新資料要使用IDBObject.put()方法,

 function update() {
   var request = db.transaction(['person'], 'readwrite')
     .objectStore('person')
     .put({ id: 1, name: '李四', age: 35, email: 'lisi@example.com' });
 
   request.onsuccess = function (event) {
     console.log('資料更新成功');
   };
 
   request.onerror = function (event) {
     console.log('資料更新失敗');
   }
 }
 
 update();

上面代碼中,put()方法自動更新了主鍵為1的記錄,

67.3.7 洗掉資料

IDBObjectStore.delete()方法用于洗掉記錄,

 function remove() {
   var request = db.transaction(['person'], 'readwrite')
     .objectStore('person')
     .delete(1);
 
   request.onsuccess = function (event) {
     console.log('資料洗掉成功');
   };
 }
 
 remove();

67.3.8 使用索引

索引的意義在于,可以讓你搜索任意欄位,也就是說從任意欄位拿到資料記錄,如果不建立索引,默認只能搜索主鍵(即從主鍵取值),

假定新建表格的時候,對name欄位建立了索引,

 objectStore.createIndex('name', 'name', { unique: false });

現在,就可以從name找到對應的資料記錄了,

 var transaction = db.transaction(['person'], 'readonly');
 var store = transaction.objectStore('person');
 var index = store.index('name');
 var request = index.get('李四');
 
 request.onsuccess = function (e) {
   var result = e.target.result;
   if (result) {
     // ...
   } else {
     // ...
   }
 }

67.4 indexedDB 物件

瀏覽器原生提供indexedDB物件,作為開發者的操作介面,

67.4.1 indexedDB.open()

indexedDB.open()方法用于打開資料庫,這是一個異步操作,但是會立刻回傳一個 IDBOpenDBRequest 物件,

 var openRequest = window.indexedDB.open('test', 1);

上面代碼表示,打開一個名為test、版本為1的資料庫,如果該資料庫不存在,則會新建該資料庫,

open()方法的第一個引數是資料庫名稱,格式為字串,不可省略;第二個引數是資料庫版本,是一個大于0的正整數(0將報錯),如果該引數大于當前版本,會觸發資料庫升級,第二個引數可省略,如果資料庫已存在,將打開當前版本的資料庫;如果資料庫不存在,將創建該版本的資料庫,默認版本為1

打開資料庫是異步操作,通過各種事件通知客戶端,下面是有可能觸發的4種事件,

  • success:打開成功,
  • error:打開失敗,
  • upgradeneeded:第一次打開該資料庫,或者資料庫版本發生變化,
  • blocked:上一次的資料庫連接還未關閉,

第一次打開資料庫時,會先觸發upgradeneeded事件,然后觸發success事件,

根據不同的需要,對上面4種事件監聽函式,

 var openRequest = indexedDB.open('test', 1);
 var db;
 
 openRequest.onupgradeneeded = function (e) {
   console.log('Upgrading...');
 }
 
 openRequest.onsuccess = function (e) {
   console.log('Success!');
   db = openRequest.result;
 }
 
 openRequest.onerror = function (e) {
   console.log('Error');
   console.log(e);
 }

上面代碼有兩個地方需要注意,首先,open()方法回傳的是一個物件(IDBOpenDBRequest),監聽函式就定義在這個物件上面,其次,success事件發生后,從openRequest.result屬性可以拿到已經打開的IndexedDB資料庫物件,

67.4.2 indexedDB.deleteDatabase()

indexedDB.deleteDatabase()方法用于洗掉一個資料庫,引數為資料庫的名字,它會立刻回傳一個IDBOpenDBRequest物件,然后對資料庫執行異步洗掉,洗掉操作的結果會通過事件通知,IDBOpenDBRequest物件可以監聽以下事件,

  • success:洗掉成功
  • error:洗掉報錯
 var DBDeleteRequest = window.indexedDB.deleteDatabase('demo');
 
 DBDeleteRequest.onerror = function (event) {
   console.log('Error');
 };
 
 DBDeleteRequest.onsuccess = function (event) {
   console.log('success');
 };

呼叫deleteDatabase()方法以后,當前資料庫的其他已經打開的連接都會接收到versionchange事件,

注意,洗掉不存在的資料庫并不會報錯,

67.4.3 indexedDB.cmp()

indexedDB.cmp()方法比較兩個值是否為 indexedDB 的相同的主鍵,它回傳一個整數,表示比較的結果:0表示相同,1表示第一個主鍵大于第二個主鍵,-1表示第一個主鍵小于第二個主鍵,

 window.indexedDB.cmp(1, 2) // -1

注意,這個方法不能用來比較任意的 JavaScript 值,如果引數是布林值或物件,它會報錯,

 window.indexedDB.cmp(1, true) // 報錯
 window.indexedDB.cmp({}, {}) // 報錯

67.5 IDBRequest 物件

IDBRequest 物件表示打開的資料庫連接,indexedDB.open()方法和indexedDB.deleteDatabase()方法會回傳這個物件,資料庫的操作都是通過這個物件完成的,

這個物件的所有操作都是異步操作,要通過readyState屬性判斷是否完成,如果為pending就表示操作正在進行,如果為done就表示操作完成,可能成功也可能失敗,

操作完成以后,觸發success事件或error事件,這時可以通過result屬性和error屬性拿到操作結果,如果在pending階段,就去讀取這兩個屬性,是會報錯的,

IDBRequest 物件有以下屬性,

  • IDBRequest.readyState:等于pending表示操作正在進行,等于done表示操作正在完成,
  • IDBRequest.result:回傳請求的結果,如果請求失敗、結果不可用,讀取該屬性會報錯,
  • IDBRequest.error:請求失敗時,回傳錯誤物件,
  • IDBRequest.source:回傳請求的來源(比如索引物件或 ObjectStore),
  • IDBRequest.transaction:回傳當前請求正在進行的事務,如果不包含事務,回傳null
  • IDBRequest.onsuccess:指定success事件的監聽函式,
  • IDBRequest.onerror:指定error事件的監聽函式,

IDBOpenDBRequest 物件繼承了 IDBRequest 物件,提供了兩個額外的事件監聽屬性,

  • IDBOpenDBRequest.onblocked:指定blocked事件(upgradeneeded事件觸發時,資料庫仍然在使用)的監聽函式,
  • IDBOpenDBRequest.onupgradeneededupgradeneeded事件的監聽函式,

67.6 IDBDatabase 物件

打開資料成功以后,可以從IDBOpenDBRequest物件的result屬性上面,拿到一個IDBDatabase物件,它表示連接的資料庫,后面對資料庫的操作,都通過這個物件完成

 var db;
 var DBOpenRequest = window.indexedDB.open('demo', 1);
 
 DBOpenRequest.onerror = function (event) {
   console.log('Error');
 };
 
 DBOpenRequest.onsuccess = function(event) {
   db = DBOpenRequest.result;
   // ...
 };

67.6.1 屬性

IDBDatabase 物件有以下屬性,

  • IDBDatabase.name:字串,資料庫名稱,
  • IDBDatabase.version:整數,資料庫版本,資料庫第一次創建時,該屬性為空字串,
  • IDBDatabase.objectStoreNames:DOMStringList 物件(字串的集合),包含當前資料的所有 object store 的名字,
  • IDBDatabase.onabort:指定 abort 事件(事務中止)的監聽函式,
  • IDBDatabase.onclose:指定 close 事件(資料庫意外關閉)的監聽函式,
  • IDBDatabase.onerror:指定 error 事件(訪問資料庫失敗)的監聽函式,
  • IDBDatabase.onversionchange:資料庫版本變化時觸發(發生upgradeneeded事件,或呼叫indexedDB.deleteDatabase()),

下面是objectStoreNames屬性的例子,該屬性回傳一個 DOMStringList 物件,包含了當前資料庫所有物件倉庫的名稱(即表名),可以使用 DOMStringList 物件的contains方法,檢查資料庫是否包含某個物件倉庫,

 if (!db.objectStoreNames.contains('firstOS')) {
   db.createObjectStore('firstOS');
 }

上面代碼先判斷某個物件倉庫是否存在,如果不存在就創建該物件倉庫,

67.6.2 方法

IDBDatabase 物件有以下方法,

  • IDBDatabase.close():關閉資料庫連接,實際會等所有事務完成后再關閉,
  • IDBDatabase.createObjectStore():創建存放資料的物件倉庫,類似于傳統關系型資料庫的表格,回傳一個 IDBObjectStore 物件,該方法只能在versionchange事件監聽函式中呼叫,
  • IDBDatabase.deleteObjectStore():洗掉指定的物件倉庫,該方法只能在versionchange事件監聽函式中呼叫,
  • IDBDatabase.transaction():回傳一個 IDBTransaction 事務物件,

下面是createObjectStore()方法的例子,

 var request = window.indexedDB.open('demo', 2);
 
 request.onupgradeneeded = function (event) {
   var db = event.target.result;
 
   db.onerror = function(event) {
     console.log('error');
   };
 
   var objectStore = db.createObjectStore('items');
 
   // ...
 };

上面代碼創建了一個名為items的物件倉庫,如果該物件倉庫已經存在,就會拋出一個錯誤,為了避免出錯,需要用到下文的objectStoreNames屬性,檢查已有哪些物件倉庫,

createObjectStore()方法還可以接受第二個物件引數,用來設定物件倉庫的屬性,

 db.createObjectStore('test', { keyPath: 'email' });
 db.createObjectStore('test2', { autoIncrement: true });

上面代碼中,keyPath屬性表示主鍵(由于主鍵的值不能重復,所以上例存入之前,必須保證資料的email屬性值都是不一樣的),默認值為nullautoIncrement屬性表示,是否使用自動遞增的整數作為主鍵(第一個資料記錄為1,第二個資料記錄為2,以此類推),默認為false,一般來說,keyPathautoIncrement屬性只要使用一個就夠了,如果兩個同時使用,表示主鍵為遞增的整數,且物件不得缺少keyPath指定的屬性,

下面是deleteObjectStore()方法的例子,

 var dbName = 'sampleDB';
 var dbVersion = 2;
 var request = indexedDB.open(dbName, dbVersion);
 
 request.onupgradeneeded = function(e) {
   var db = request.result;
   if (e.oldVersion < 1) {
     db.createObjectStore('store1');
   }
 
   if (e.oldVersion < 2) {
     db.deleteObjectStore('store1');
     db.createObjectStore('store2');
   }
 
   // ...
 };

下面是transaction()方法的例子,該方法用于創建一個資料庫事務,回傳一個 IDBTransaction 物件,向資料庫添加資料之前,必須先創建資料庫事務,

 var t = db.transaction(['items'], 'readwrite');

transaction()方法接受兩個引數:第一個引數是一個陣列,里面是所涉及的物件倉庫,通常是只有一個;第二個引數是一個表示操作型別的字串,目前,操作型別只有兩種:readonly(只讀)和readwrite(讀寫),添加資料使用readwrite,讀取資料使用readonly,第二個引數是可選的,省略時默認為readonly模式,

67.7 IDBObjectStore 物件

IDBObjectStore 物件對應一個物件倉庫(object store),IDBDatabase.createObjectStore()方法回傳的就是一個 IDBObjectStore 物件,

IDBDatabase 物件的transaction()回傳一個事務物件,該物件的objectStore()方法回傳 IDBObjectStore 物件,因此可以采用下面的鏈式寫法,

 db.transaction(['test'], 'readonly')
   .objectStore('test')
   .get(X)
   .onsuccess = function (e) {}

67.7.1 屬性

IDBObjectStore 物件有以下屬性,

  • IDBObjectStore.indexNames:回傳一個類似陣列的物件(DOMStringList),包含了當前物件倉庫的所有索引,
  • IDBObjectStore.keyPath:回傳當前物件倉庫的主鍵,
  • IDBObjectStore.name:回傳當前物件倉庫的名稱,
  • IDBObjectStore.transaction:回傳當前物件倉庫所屬的事務物件,
  • IDBObjectStore.autoIncrement:布林值,表示主鍵是否會自動遞增,

67.7.2 方法

IDBObjectStore 物件有以下方法,

(1)IDBObjectStore.add()

IDBObjectStore.add()用于向物件倉庫添加資料,回傳一個 IDBRequest 物件,該方法只用于添加資料,如果主鍵相同會報錯,因此更新資料必須使用put()方法,

 objectStore.add(value, key)

該方法接受兩個引數,第一個引數是鍵值,第二個引數是主鍵,該引數可選,如果省略默認為null

創建事務以后,就可以獲取物件倉庫,然后使用add()方法往里面添加資料了,

 var db;
 var DBOpenRequest = window.indexedDB.open('demo', 1);
 
 DBOpenRequest.onsuccess = function (event) {
   db = DBOpenRequest.result;
   var transaction = db.transaction(['items'], 'readwrite');
 
   transaction.oncomplete = function (event) {
     console.log('transaction success');
   };
 
   transaction.onerror = function (event) {
     console.log('transaction error: ' + transaction.error);
   };
 
   var objectStore = transaction.objectStore('items');
   var objectStoreRequest = objectStore.add({ foo: 1 });
 
   objectStoreRequest.onsuccess = function (event) {
     console.log('add data success');
   };
 
 };
(2)IDBObjectStore.put()

IDBObjectStore.put()方法用于更新某個主鍵對應的資料記錄,如果對應的鍵值不存在,則插入一條新的記錄,該方法回傳一個 IDBRequest 物件,

 objectStore.put(item, key)

該方法接受兩個引數,第一個引數為新資料,第二個引數為主鍵,該引數可選,且只在自動遞增時才有必要提供,因為那時主鍵不包含在資料值里面,

(3)IDBObjectStore.clear()

IDBObjectStore.clear()洗掉當前物件倉庫的所有記錄,該方法回傳一個 IDBRequest 物件,

 objectStore.clear()

該方法不需要引數,

(4)IDBObjectStore.delete()

IDBObjectStore.delete()方法用于洗掉指定主鍵的記錄,該方法回傳一個 IDBRequest 物件,

 objectStore.delete(Key)

該方法的引數為主鍵的值,

(5)IDBObjectStore.count()

IDBObjectStore.count()方法用于計算記錄的數量,該方法回傳一個 IDBRequest 物件,

 IDBObjectStore.count(key)

不帶引數時,該方法回傳當前物件倉庫的所有記錄數量,如果主鍵或 IDBKeyRange 物件作為引數,則回傳對應的記錄數量,

(6)IDBObjectStore.getKey()

IDBObjectStore.getKey()用于獲取主鍵,該方法回傳一個 IDBRequest 物件,

 objectStore.getKey(key)

該方法的引數可以是主鍵值或 IDBKeyRange 物件,

(7)IDBObjectStore.get()

IDBObjectStore.get()用于獲取主鍵對應的資料記錄,該方法回傳一個 IDBRequest 物件,

 objectStore.get(key)
(8)IDBObjectStore.getAll()

DBObjectStore.getAll()用于獲取物件倉庫的記錄,該方法回傳一個 IDBRequest 物件,

 // 獲取所有記錄
 objectStore.getAll()
 
 // 獲取所有符合指定主鍵或 IDBKeyRange 的記錄
 objectStore.getAll(query)
 
 // 指定獲取記錄的數量
 objectStore.getAll(query, count)
(9)IDBObjectStore.getAllKeys()

IDBObjectStore.getAllKeys()用于獲取所有符合條件的主鍵,該方法回傳一個 IDBRequest 物件,

 // 獲取所有記錄的主鍵
 objectStore.getAllKeys()
 
 // 獲取所有符合條件的主鍵
 objectStore.getAllKeys(query)
 
 // 指定獲取主鍵的數量
 objectStore.getAllKeys(query, count)
(10)IDBObjectStore.index()

IDBObjectStore.index()方法回傳指定名稱的索引物件 IDBIndex,

 objectStore.index(name)

有了索引以后,就可以針對索引所在的屬性讀取資料,

 var t = db.transaction(['people'], 'readonly');
 var store = t.objectStore('people');
 var index = store.index('name');
 
 var request = index.get('foo');

上面代碼打開物件倉庫以后,先用index()方法指定獲取name屬性的索引,然后用get()方法讀取某個name屬性(foo)對應的資料,如果name屬性不是對應唯一值,這時get()方法有可能取回多個資料物件,另外,get()是異步方法,讀取成功以后,只能在success事件的監聽函式中處理資料,

(11)IDBObjectStore.createIndex()

IDBObjectStore.createIndex()方法用于新建當前資料庫的一個索引,該方法只能在VersionChange監聽函式里面呼叫,

 objectStore.createIndex(indexName, keyPath, objectParameters)

該方法可以接受三個引數,

  • indexName:索引名
  • keyPath:主鍵
  • objectParameters:配置物件(可選)

第三個引數可以配置以下屬性,

  • unique:如果設為true,將不允許重復的值
  • multiEntry:如果設為true,對于有多個值的主鍵陣列,每個值將在索引里面新建一個條目,否則主鍵陣列對應一個條目,

假定物件倉庫中的資料記錄都是如下的person型別,

 var person = {
   name: name,
   email: email,
   created: new Date()
 };

可以指定這個物件的某個屬性來建立索引,

 var store = db.createObjectStore('people', { autoIncrement: true });
 
 store.createIndex('name', 'name', { unique: false });
 store.createIndex('email', 'email', { unique: true });

上面代碼告訴索引物件,name屬性不是唯一值,email屬性是唯一值,

(12)IDBObjectStore.deleteIndex()

IDBObjectStore.deleteIndex()方法用于洗掉指定的索引,該方法只能在VersionChange監聽函式里面呼叫,

 objectStore.deleteIndex(indexName)
(13)IDBObjectStore.openCursor()

IDBObjectStore.openCursor()用于獲取一個指標物件,

 IDBObjectStore.openCursor()

**指標物件可以用來遍歷資料,**該物件也是異步的,有自己的successerror事件,可以對它們指定監聽函式,

 var t = db.transaction(['test'], 'readonly');
 var store = t.objectStore('test');
 
 var cursor = store.openCursor();
 
 cursor.onsuccess = function (event) {
   var res = event.target.result;
   if (res) {
     console.log('Key', res.key);
     console.dir('Data', res.value);
     res.continue();
   }
 }

監聽函式接受一個事件物件作為引數,該物件的target.result屬性指向當前資料記錄,該記錄的keyvalue分別回傳主鍵和鍵值(即實際存入的資料),continue()方法將游標移到下一個資料物件,如果當前資料物件已經是最后一個資料了,則游標指向null

openCursor()方法的第一個引數是主鍵值,或者一個 IDBKeyRange 物件,如果指定該引數,將只處理包含指定主鍵的記錄;如果省略,將處理所有的記錄,該方法還可以接受第二個引數,表示遍歷方向,默認值為next,其他可能的值為prevnextuniqueprevunique,后兩個值表示如果遇到重復值,會自動跳過,

(14)IDBObjectStore.openKeyCursor()

IDBObjectStore.openKeyCursor()用于獲取一個主鍵指標物件,

 IDBObjectStore.openKeyCursor()

67.8 IDBTransaction 物件

IDBTransaction 物件用來異步操作資料庫事務,所有的讀寫操作都要通過這個物件進行,

IDBDatabase.transaction()方法回傳的就是一個 IDBTransaction 物件,

 var db;
 var DBOpenRequest = window.indexedDB.open('demo', 1);
 
 DBOpenRequest.onsuccess = function(event) {
   db = DBOpenRequest.result;
   var transaction = db.transaction(['demo'], 'readwrite');
 
   transaction.oncomplete = function (event) {
     console.log('transaction success');
   };
 
   transaction.onerror = function (event) {
     console.log('transaction error: ' + transaction.error);
   };
 
   var objectStore = transaction.objectStore('demo');
   var objectStoreRequest = objectStore.add({ foo: 1 });
 
   objectStoreRequest.onsuccess = function (event) {
     console.log('add data success');
   };
 
 };

事務的執行順序是按照創建的順序,而不是發出請求的順序,

 var trans1 = db.transaction('foo', 'readwrite');
 var trans2 = db.transaction('foo', 'readwrite');
 var objectStore2 = trans2.objectStore('foo')
 var objectStore1 = trans1.objectStore('foo')
 objectStore2.put('2', 'key');
 objectStore1.put('1', 'key');

上面代碼中,key對應的鍵值最終是2,而不是1,因為事務trans1先于trans2創建,所以首先執行,

注意,事務有可能失敗,只有監聽到事務的complete事件,才能保證事務操作成功,

IDBTransaction 物件有以下屬性,

  • IDBTransaction.db:回傳當前事務所在的資料庫物件 IDBDatabase,
  • IDBTransaction.error:回傳當前事務的錯誤,如果事務沒有結束,或者事務成功結束,或者被手動終止,該方法回傳null
  • IDBTransaction.mode:回傳當前事務的模式,默認是readonly(只讀),另一個值是readwrite
  • IDBTransaction.objectStoreNames:回傳一個類似陣列的物件 DOMStringList,成員是當前事務涉及的物件倉庫的名字,
  • IDBTransaction.onabort:指定abort事件(事務中斷)的監聽函式,
  • IDBTransaction.oncomplete:指定complete事件(事務成功)的監聽函式,
  • IDBTransaction.onerror:指定error事件(事務失敗)的監聽函式,

IDBTransaction 物件有以下方法,

  • IDBTransaction.abort():終止當前事務,回滾所有已經進行的變更,
  • IDBTransaction.objectStore(name):回傳指定名稱的物件倉庫 IDBObjectStore,

67.9 IDBIndex 物件

IDBIndex 物件代表資料庫的索引,通過這個物件可以獲取資料庫里面的記錄,資料記錄的主鍵默認就是帶有索引,IDBIndex 物件主要用于通過除主鍵以外的其他鍵,建立索引獲取物件,

IDBIndex 是持久性的鍵值對存盤,只要插入、更新或洗掉資料記錄,參考的物件庫中的記錄,索引就會自動更新,

IDBObjectStore.index()方法可以獲取 IDBIndex 物件,

 var transaction = db.transaction(['contactsList'], 'readonly');
 var objectStore = transaction.objectStore('contactsList');
 var myIndex = objectStore.index('lName');
 
 myIndex.openCursor().onsuccess = function (event) {
   var cursor = event.target.result;
   if (cursor) {
     var tableRow = document.createElement('tr');
     tableRow.innerHTML =   '<td>' + cursor.value.id + '</td>'
                          + '<td>' + cursor.value.lName + '</td>'
                          + '<td>' + cursor.value.fName + '</td>'
                          + '<td>' + cursor.value.jTitle + '</td>'
                          + '<td>' + cursor.value.company + '</td>'
                          + '<td>' + cursor.value.eMail + '</td>'
                          + '<td>' + cursor.value.phone + '</td>'
                          + '<td>' + cursor.value.age + '</td>';
     tableEntry.appendChild(tableRow);
 
     cursor.continue();
   } else {
     console.log('Entries all displayed.');
   }
 };

IDBIndex 物件有以下屬性,

  • IDBIndex.name:字串,索引的名稱,
  • IDBIndex.objectStore:索引所在的物件倉庫,
  • IDBIndex.keyPath:索引的主鍵,
  • IDBIndex.multiEntry:布林值,針對keyPath為陣列的情況,如果設為true,創建陣列時,每個陣列成員都會有一個條目,否則每個陣列都只有一個條目,
  • IDBIndex.unique:布林值,表示創建索引時是否允許相同的主鍵,

IDBIndex 物件有以下方法,它們都是異步的,立即回傳的都是一個 IDBRequest 物件,

  • IDBIndex.count():用來獲取記錄的數量,它可以接受主鍵或 IDBKeyRange 物件作為引數,這時只回傳符合主鍵的記錄數量,否則回傳所有記錄的數量,
  • IDBIndex.get(key):用來獲取符合指定主鍵的資料記錄,
  • IDBIndex.getKey(key):用來獲取指定的主鍵,
  • IDBIndex.getAll():用來獲取所有的資料記錄,它可以接受兩個引數,都是可選的,第一個引數用來指定主鍵,第二個引數用來指定回傳記錄的數量,如果省略這兩個引數,則回傳所有記錄,由于獲取成功時,瀏覽器必須生成所有物件,所以對性能有影響,如果資料集比較大,建議使用 IDBCursor 物件,
  • IDBIndex.getAllKeys():該方法與IDBIndex.getAll()方法相似,區別是獲取所有主鍵,
  • IDBIndex.openCursor():用來獲取一個 IDBCursor 物件,用來遍歷索引里面的所有條目,
  • IDBIndex.openKeyCursor():該方法與IDBIndex.openCursor()方法相似,區別是遍歷所有條目的主鍵,

67.10 IDBCursor 物件

IDBCursor 物件代表指標物件,用來遍歷資料倉庫(IDBObjectStore)或索引(IDBIndex)的記錄

IDBCursor 物件一般通過IDBObjectStore.openCursor()方法獲得,

 var transaction = db.transaction(['rushAlbumList'], 'readonly');
 var objectStore = transaction.objectStore('rushAlbumList');
 
 objectStore.openCursor(null, 'next').onsuccess = function(event) {
   var cursor = event.target.result;
   if (cursor) {
     var listItem = document.createElement('li');
       listItem.innerHTML = cursor.value.albumTitle + ', ' + cursor.value.year;
       list.appendChild(listItem);
 
       console.log(cursor.source);
       cursor.continue();
     } else {
       console.log('Entries all displayed.');
     }
   };
 };

IDBCursor 物件的屬性,

  • IDBCursor.source:回傳正在遍歷的物件倉庫或索引,
  • IDBCursor.direction:字串,表示指標遍歷的方向,共有四個可能的值:next(從頭開始向后遍歷)、nextunique(從頭開始向后遍歷,重復的值只遍歷一次)、prev(從尾部開始向前遍歷)、prevunique(從尾部開始向前遍歷,重復的值只遍歷一次),該屬性通過IDBObjectStore.openCursor()方法的第二個引數指定,一旦指定就不能改變了,
  • IDBCursor.key:回傳當前記錄的主鍵,
  • IDBCursor.value:回傳當前記錄的資料值,
  • IDBCursor.primaryKey:回傳當前記錄的主鍵,對于資料倉庫(objectStore)來說,這個屬性等同于 IDBCursor.key;對于索引,IDBCursor.key 回傳索引的位置值,該屬性回傳資料記錄的主鍵,

IDBCursor 物件有如下方法,

  • IDBCursor.advance(n):指標向前移動 n 個位置,
  • IDBCursor.continue():指標向前移動一個位置,它可以接受一個主鍵作為引數,這時會跳轉到這個主鍵,
  • IDBCursor.continuePrimaryKey():該方法需要兩個引數,第一個是key,第二個是primaryKey,將指標移到符合這兩個引數的位置,
  • IDBCursor.delete():用來洗掉當前位置的記錄,回傳一個 IDBRequest 物件,該方法不會改變指標的位置,
  • IDBCursor.update():用來更新當前位置的記錄,回傳一個 IDBRequest 物件,它的引數是要寫入資料庫的新的值,

67.11 IDBKeyRange 物件

IDBKeyRange 物件代表資料倉庫(object store)里面的一組主鍵,根據這組主鍵,可以獲取資料倉庫或索引里面的一組記錄,

IDBKeyRange 可以只包含一個值,也可以指定上限和下限,它有四個靜態方法,用來指定主鍵的范圍,

  • IDBKeyRange.lowerBound():指定下限,
  • IDBKeyRange.upperBound():指定上限,
  • IDBKeyRange.bound():同時指定上下限,
  • IDBKeyRange.only():指定只包含一個值,

下面是一些代碼實體,

 // All keys ≤ x
 var r1 = IDBKeyRange.upperBound(x);
 
 // All keys < x
 var r2 = IDBKeyRange.upperBound(x, true);
 
 // All keys ≥ y
 var r3 = IDBKeyRange.lowerBound(y);
 
 // All keys > y
 var r4 = IDBKeyRange.lowerBound(y, true);
 
 // All keys ≥ x && ≤ y
 var r5 = IDBKeyRange.bound(x, y);
 
 // All keys > x &&< y
 var r6 = IDBKeyRange.bound(x, y, true, true);
 
 // All keys > x && ≤ y
 var r7 = IDBKeyRange.bound(x, y, true, false);
 
 // All keys ≥ x &&< y
 var r8 = IDBKeyRange.bound(x, y, false, true);
 
 // The key = z
 var r9 = IDBKeyRange.only(z);

IDBKeyRange.lowerBound()IDBKeyRange.upperBound()IDBKeyRange.bound()這三個方法默認包括端點值,可以傳入一個布林值,修改這個屬性,

與之對應,IDBKeyRange 物件有四個只讀屬性,

  • IDBKeyRange.lower:回傳下限
  • IDBKeyRange.lowerOpen:布林值,表示下限是否為開區間(即下限是否排除在范圍之外)
  • IDBKeyRange.upper:回傳上限
  • IDBKeyRange.upperOpen:布林值,表示上限是否為開區間(即上限是否排除在范圍之外)

IDBKeyRange 實體物件生成以后,將它作為引數輸入 IDBObjectStore 或 IDBIndex 物件的openCursor()方法,就可以在所設定的范圍內讀取資料,

 var t = db.transaction(['people'], 'readonly');
 var store = t.objectStore('people');
 var index = store.index('name');
 
 var range = IDBKeyRange.bound('B', 'D');
 
 index.openCursor(range).onsuccess = function (e) {
   var cursor = e.target.result;
   if (cursor) {
     console.log(cursor.key + ':');
 
     for (var field in cursor.value) {
       console.log(cursor.value[field]);
     }
     cursor.continue();
   }
 }

IDBKeyRange 有一個實體方法includes(key),回傳一個布林值,表示某個主鍵是否包含在當前這個主鍵組之內,

var keyRangeValue = IDBKeyRange.bound('A', 'K', false, false);

keyRangeValue.includes('F') // true
keyRangeValue.includes('W') // false

68. Web Worker

68.1 概述

JavaScript 語言采用的是單執行緒模型,也就是說,所有任務只能在一個執行緒上完成,一次只能做一件事,前面的任務沒做完,后面的任務只能等著,隨著電腦計算能力的增強,尤其是多核 CPU 的出現,單執行緒帶來很大的不便,無法充分發揮計算機的計算能力,

==Web Worker 的作用,就是為 JavaScript 創造多執行緒環境,允許主執行緒創建 Worker 執行緒,將一些任務分配給后者運行,==在主執行緒運行的同時,Worker 執行緒在后臺運行,兩者互不干擾,等到 Worker 執行緒完成計算任務,再把結果回傳給主執行緒,這樣的好處是,一些計算密集型或高延遲的任務可以交由 Worker 執行緒執行,主執行緒(通常負責 UI 互動)能夠保持流暢,不會被阻塞或拖慢,

Worker 執行緒一旦新建成功,就會始終運行,不會被主執行緒上的活動(比如用戶點擊按鈕、提交表單)打斷,這樣有利于隨時回應主執行緒的通信,但是,這也造成了 Worker 比較耗費資源,不應該過度使用,而且一旦使用完畢,就應該關閉,

Web Worker 有以下幾個使用注意點,

(1)同源限制

分配給 Worker 執行緒運行的腳本檔案,必須與主執行緒的腳本檔案同源,

(2)DOM 限制

Worker 執行緒所在的全域物件,與主執行緒不一樣,無法讀取主執行緒所在網頁的 DOM 物件,也無法使用documentwindowparent這些物件,但是,Worker 執行緒可以使用navigator物件和location物件,

(3)全域物件限制

Worker 的全域物件WorkerGlobalScope,不同于網頁的全域物件Window,很多介面拿不到,比如,理論上 Worker 執行緒不能使用console.log,因為標準里面沒有提到 Worker 的全域物件存在console介面,只定義了Navigator介面和Location介面,不過,瀏覽器實際上支持 Worker 執行緒使用console.log,保險的做法還是不使用這個方法,

(4)通信聯系

Worker 執行緒和主執行緒不在同一個背景關系環境,它們不能直接通信,必須通過訊息完成,

(5)腳本限制

Worker 執行緒不能執行alert()方法和confirm()方法,但可以使用 XMLHttpRequest 物件發出 AJAX 請求,

(6)檔案限制

Worker 執行緒無法讀取本地檔案,即不能打開本機的檔案系統(file://),它所加載的腳本,必須來自網路,

68.2 基本用法

68.2.1 主執行緒

主執行緒采用new命令,呼叫Worker()建構式,新建一個 Worker 執行緒,

var worker = new Worker('work.js');

Worker()建構式的引數是一個腳本檔案,該檔案就是 Worker 執行緒所要執行的任務,由于 Worker 不能讀取本地檔案,所以這個腳本必須來自網路,如果下載沒有成功(比如404錯誤),Worker 就會默默地失敗,

然后,主執行緒呼叫worker.postMessage()方法,向 Worker 發訊息,

worker.postMessage('Hello World');
worker.postMessage({method: 'echo', args: ['Work']});

worker.postMessage()方法的引數,就是主執行緒傳給 Worker 的資料,它可以是各種資料型別,包括二進制資料,

接著,主執行緒通過worker.onmessage指定監聽函式,接收子執行緒發回來的訊息,

worker.onmessage = function (event) {
  doSomething(event.data);
}

function doSomething() {
  // 執行任務
  worker.postMessage('Work done!');
}

上面代碼中,事件物件的data屬性可以獲取 Worker 發來的資料,

Worker 完成任務以后,主執行緒就可以把它關掉,

worker.terminate();

68.2.2 Worker 執行緒(self子執行緒)

Worker 執行緒內部需要有一個監聽函式,監聽message事件,

self.addEventListener('message', function (e) {
  self.postMessage('You said: ' + e.data);
}, false);

上面代碼中,self代表子執行緒自身,即子執行緒的全域物件,因此,等同于下面兩種寫法,

// 寫法一
this.addEventListener('message', function (e) {
  this.postMessage('You said: ' + e.data);
}, false);

// 寫法二
addEventListener('message', function (e) {
  postMessage('You said: ' + e.data);
}, false);

除了使用self.addEventListener()指定監聽函式,也可以使用self.onmessage指定,監聽函式的引數是一個事件物件,它的data屬性包含主執行緒發來的資料,self.postMessage()方法用來向主執行緒發送訊息,

根據主執行緒發來的資料,Worker 執行緒可以呼叫不同的方法,下面是一個例子,

self.addEventListener('message', function (e) {
  var data = e.data;
  switch (data.cmd) {
    case 'start':
      self.postMessage('WORKER STARTED: ' + data.msg);
      break;
    case 'stop':
      self.postMessage('WORKER STOPPED: ' + data.msg);
      self.close(); // Terminates the worker.
      break;
    default:
      self.postMessage('Unknown command: ' + data.msg);
  };
}, false);

上面代碼中,self.close()用于在 Worker 內部關閉自身,

68.2.3 Worker 加載腳本

Worker 內部如果要加載其他腳本,有一個專門的方法importScripts()

importScripts('script1.js');

該方法可以同時加載多個腳本,

importScripts('script1.js', 'script2.js');

68.2.4 錯誤處理

主執行緒可以監聽 Worker 是否發生錯誤,如果發生錯誤,Worker 會觸發主執行緒的error事件,

worker.onerror = function (event) {
  console.log(
    'ERROR: Line ', event.lineno, ' in ', event.filename, ': ', event.message
  );
};

// 或者
worker.addEventListener('error', function (event) {
  // ...
});

Worker 內部也可以監聽error事件,

68.2.5 關閉 Worker

使用完畢,為了節省系統資源,必須關閉 Worker,

// 主執行緒
worker.terminate();

// Worker 執行緒
self.close();

68.3 資料通信

前面說過,主執行緒與 Worker 之間的通信內容,可以是文本,也可以是物件,需要注意的是,**這種通信是拷貝關系,即是傳值而不是傳址,Worker 對通信內容的修改,不會影響到主執行緒,**事實上,瀏覽器內部的運行機制是,先將通信內容串行化,然后把串行化后的字串發給 Worker,后者再將它還原,

主執行緒與 Worker 之間也可以交換二進制資料,比如 File、Blob、ArrayBuffer 等型別,也可以在執行緒之間發送,下面是一個例子,

// 主執行緒
var uInt8Array = new Uint8Array(new ArrayBuffer(10));
for (var i = 0; i < uInt8Array.length; ++i) {
  uInt8Array[i] = i * 2; // [0, 2, 4, 6, 8,...]
}
worker.postMessage(uInt8Array);

// Worker 執行緒
self.onmessage = function (e) {
  var uInt8Array = e.data;
  postMessage('Inside worker.js: uInt8Array.toString() = ' + uInt8Array.toString());
  postMessage('Inside worker.js: uInt8Array.byteLength = ' + uInt8Array.byteLength);
};

但是,拷貝方式發送二進制資料,會造成性能問題,比如,主執行緒向 Worker 發送一個 500MB 檔案,默認情況下瀏覽器會生成一個原檔案的拷貝,為了解決這個問題,JavaScript 允許主執行緒把二進制資料直接轉移給子執行緒,但是一旦轉移,主執行緒就無法再使用這些二進制資料了,這是為了防止出現多個執行緒同時修改資料的麻煩局面,這種轉移資料的方法,叫做Transferable Objects,這使得主執行緒可以快速把資料交給 Worker,對于影像處理、聲音處理、3D 運算等就非常方便了,不會產生性能負擔,

如果要直接轉移資料的控制權,就要使用下面的寫法,

// Transferable Objects 格式
worker.postMessage(arrayBuffer, [arrayBuffer]);

// 例子
var ab = new ArrayBuffer(1);
worker.postMessage(ab, [ab]);

68.4 同頁面的 Web Worker

通常情況下,Worker 載入的是一個單獨的 JavaScript 腳本檔案,但是也可以載入與主執行緒在同一個網頁的代碼,

<!DOCTYPE html>
  <body>
    <script id="worker" type="app/worker">
      addEventListener('message', function () {
        postMessage('some message');
      }, false);
    </script>
  </body>
</html>

上面是一段嵌入網頁的腳本,注意必須指定<script>標簽的type屬性是一個瀏覽器不認識的值,上例是app/worker

然后,讀取這一段嵌入頁面的腳本,用 Worker 來處理,

var blob = new Blob([document.querySelector('#worker').textContent]);
var url = window.URL.createObjectURL(blob);
var worker = new Worker(url);

worker.onmessage = function (e) {
  // e.data === 'some message'
};

上面代碼中,先將嵌入網頁的腳本代碼,轉成一個二進制物件,然后為這個二進制物件生成 URL,再讓 Worker 加載這個 URL,這樣就做到了,主執行緒和 Worker 的代碼都在同一個網頁上面,

68.5 實體:Worker 執行緒完成輪詢

有時,瀏覽器需要輪詢服務器狀態,以便第一時間得知狀態改變,這個作業可以放在 Worker 里面,

function createWorker(f) {
  var blob = new Blob(['(' + f.toString() + ')()']);
  var url = window.URL.createObjectURL(blob);
  var worker = new Worker(url);
  return worker;
}

var pollingWorker = createWorker(function (e) {
  var cache;

  function compare(new, old) { ... };

  setInterval(function () {
    fetch('/my-api-endpoint').then(function (res) {
      var data = res.json();

      if (!compare(data, cache)) {
        cache = data;
        self.postMessage(data);
      }
    })
  }, 1000)
});

pollingWorker.onmessage = function () {
  // render data
}

pollingWorker.postMessage('init');

上面代碼中,Worker 每秒鐘輪詢一次資料,然后跟快取做比較,如果不一致,就說明服務端有了新的變化,因此就要通知主執行緒,

68.6 實體: Worker 新建 Worker

Worker 執行緒內部還能再新建 Worker 執行緒(目前只有 Firefox 瀏覽器支持),下面的例子是將一個計算密集的任務,分配到10個 Worker,

主執行緒代碼如下,

var worker = new Worker('worker.js');
worker.onmessage = function (event) {
  document.getElementById('result').textContent = event.data;
};

Worker 執行緒代碼如下,

// worker.js

// settings
var num_workers = 10;
var items_per_worker = 1000000;

// start the workers
var result = 0;
var pending_workers = num_workers;
for (var i = 0; i < num_workers; i += 1) {
  var worker = new Worker('core.js');
  worker.postMessage(i * items_per_worker);
  worker.postMessage((i + 1) * items_per_worker);
  worker.onmessage = storeResult;
}

// handle the results
function storeResult(event) {
  result += event.data;
  pending_workers -= 1;
  if (pending_workers <= 0)
    postMessage(result); // finished!
}

上面代碼中,Worker 執行緒內部新建了10個 Worker 執行緒,并且依次向這10個 Worker 發送訊息,告知了計算的起點和終點,計算任務腳本的代碼如下,

// core.js
var start;
onmessage = getStart;
function getStart(event) {
  start = event.data;
  onmessage = getEnd;
}

var end;
function getEnd(event) {
  end = event.data;
  onmessage = null;
  work();
}

function work() {
  var result = 0;
  for (var i = start; i < end; i += 1) {
    // perform some complex calculation here
    result += 1;
  }
  postMessage(result);
  close();
}

68.7 API

68.7.1 主執行緒

瀏覽器原生提供Worker()建構式,用來供主執行緒生成 Worker 執行緒,

var myWorker = new Worker(jsUrl, options);

Worker()建構式,可以接受兩個引數,第一個引數是腳本的網址(必須遵守同源政策),該引數是必需的,且只能加載 JS 腳本,否則會報錯,第二個引數是配置物件,該物件可選,它的一個作用就是指定 Worker 的名稱,用來區分多個 Worker 執行緒,

// 主執行緒
var myWorker = new Worker('worker.js', { name : 'myWorker' });

// Worker 執行緒
self.name // myWorker

Worker()建構式回傳一個 Worker 執行緒物件,用來供主執行緒操作 Worker,Worker 執行緒物件的屬性和方法如下,

  • Worker.onerror:指定 error 事件的監聽函式,
  • Worker.onmessage:指定 message 事件的監聽函式,發送過來的資料在Event.data屬性中,
  • Worker.onmessageerror:指定 messageerror 事件的監聽函式,發送的資料無法序列化成字串時,會觸發這個事件,
  • Worker.postMessage():向 Worker 執行緒發送訊息,
  • Worker.terminate():立即終止 Worker 執行緒,

68.7.2 Worker 執行緒

Web Worker 有自己的全域物件,不是主執行緒的window,而是一個專門為 Worker 定制的全域物件,因此定義在window上面的物件和方法不是全部都可以使用,

Worker 執行緒有一些自己的全域屬性和方法,

  • self.name: Worker 的名字,該屬性只讀,由建構式指定,
  • self.onmessage:指定message事件的監聽函式,
  • self.onmessageerror:指定 messageerror 事件的監聽函式,發送的資料無法序列化成字串時,會觸發這個事件,
  • self.close():關閉 Worker 執行緒,
  • self.postMessage():向產生這個 Worker 的執行緒發送訊息,
  • self.importScripts():加載 JS 腳本,

(完)

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/291219.html

標籤:其他

上一篇:看完 TypeScript 系列文章,進大廠了!!!

下一篇:快來每日上分,2021前端面試題10道(附答案與決議)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more