讀古今文學網 > 學習JavaScript數據結構與算法(第2版) > 6.2 創建集合 >

6.2 創建集合

目前的JavaScript實現是基於2011年6月發佈的ECMAScript 5.1(現代瀏覽器均已支持),它包括了我們在之前章節已經提到過的Array類的實現。ECMAScript 6也包括了Set類的實現,本章稍後會介紹它的用法。本章中,我們要實現的類就是以ECMAScript 6中Set類的實現為基礎的。

以下是我們的Set類的骨架:

function Set {
  let items = {};
}

  

有一個非常重要的細節,我們使用對像而不是數組來表示集合(items)。但也可以用數組實現。在這裡我們用對像來實現,稍微有點兒不一樣,也學習一下實現相似數據結構的新方法。同時,JavaScript的對象不允許一個鍵指向兩個不同的屬性,也保證了集合裡的元素都是唯一的。

接下來,需要聲明一些集合可用的方法(我們會嘗試模擬與ECMAScript 6實現相同的Set類)。

  • add(value):向集合添加一個新的項。

  • delete(value):從集合移除一個值。

  • has(value):如果值在集合中,返回true,否則返回false

  • clear:移除集合中的所有項。

  • size:返回集合所包含元素的數量。與數組的length屬性類似。

  • values:返回一個包含集合中所有值的數組。

6.2.1 has(value)方法

首先要實現的是has(value)方法。這是因為它會被addremove等其他方法調用。下面看看它的實現:

this.has = function(value){
  return value in items;
};

  

既然我們使用對像來存儲集合的值,就可以用JavaScript的in操作符來驗證給定的值是否是items對象的屬性。

但這個方法還有更好的實現方式,如下:

this.has = function(value){
  return items.hasOwnProperty(value);
};

  

所有JavaScript對象都有hasOwnProperty方法。這個方法返回一個表明對象是否具有特定屬性的布爾值。

6.2.2 add方法

接下來要實現add方法:

this.add = function(value){
  if (!this.has(value)){
    items[value] = value; //{1}
    return true;
  }
  return false;
};

  

對於給定的value,可以檢查它是否存在於集合中。如果不存在,就把value添加到集合中(行{1}),返回true,表示添加了這個值。如果集合中已經有這個值,就返回false,表示沒有添加它。

 添加一個值的時候,把它同時作為鍵和值保存,因為這樣有利於查找這個值。

6.2.3 removeclear方法

下面要實現remove方法:

this.remove = function(value){
  if (this.has(value)){
    delete items[value]; //{2}
    return true;
  }
  return false;
};

  

remove方法中,我們會驗證給定的value是否存在於集合中。如果存在,就從集合中移除value(行{2}),返回true,表示值被移除;否則返回false

既然用對像來存儲集合的items對象,就可以簡單地使用delete操作符從items對像中移除屬性(行{2})。

使用Set類的示例代碼如下:

let set = new Set;
set.add(1);
set.add(2);

  

出於好奇,如果在執行以上代碼之後,在控制台(console.log)輸出items變量,谷歌Chrome就會輸出如下內容:

Object {1: 1, 2: 2}

  

 可以看到,這是一個有兩個屬性的對象。屬性名就是添加到集合的值,同時它也是屬性值。

如果想移除集合中的所有值,可以用clear方法:

this.clear = function{
  items = {}; // {3}
};

  

要重置items對象,需要做的只是把一個空對像重新賦值給它(行{3})。我們也可以迭代集合,用remove方法依次移除所有的值,但既然有更簡單的方法,那樣做就太麻煩了。

6.2.4 size方法

下一個要實現的是size方法(返回集合中有多少項)。這個方法有三種實現方式。

第一種方法是使用一個length 變量,每當使用addremove方法時控制它,就像在上一章中使用LinkedList類一樣。

第二種方法,使用JavaScript內建的Object類的一個內建函數(ECMAScript 5以上版本):

this.size = function{
  return Object.keys(items).length; //{4}
};

  

JavaScript的Object類有一個keys方法,它返回一個包含給定對像所有屬性的數組。在這種情況下,可以使用這個數組的length屬性(行{4})來返回items對象的屬性個數。以上代碼只能在現代瀏覽器中運行(比如IE9以上版本、Firefox 4以上版本、Chrome 5以上版本、Opera 12以上版本、Safari 5以上版本,等等)。

第三種方法是手動提取items對象的每一個屬性,記錄屬性的個數並返回這個數字。這個方法可以在任何瀏覽器上運行,和之前的代碼是等價的:

this.sizeLegacy = function{
  let count = 0;
  for(let key in items) { //{5}
    if(items.hasOwnProperty(key)) //{6}
    ++count; //{7}
  }
  return count;
};

  

遍歷items對象的所有屬性(行{5}),檢查它們是否是對像自身的屬性(避免重複計數——行{6})。如果是,就遞增count變量的值(行{7}),最後在方法結束時返回這個數字。

 不能簡單地使用for-in語句遍歷items對象的屬性,並遞增count變量的值。還需要使用hasOwnProperty方法(以驗證items對像具有該屬性),因為對象的原型包含了額外的屬性(屬性既有繼承自JavaScript的Object類的,也有屬於對像自身,未用於數據結構的)。

6.2.5 values方法

values方法也應用了相同的邏輯,提取items對象的所有屬性,以數組的形式返回:

this.values = function{
  let values = ;
  for (let i=0, keys=Object.keys(items); i<keys.length; i++) {
    values.push(items[keys[i]]);
  }
  return values;
};

  

以上代碼只能在現代瀏覽器中運行。在本書中我們使用的測試瀏覽器是Chrome和Firefox,因此代碼可以工作。

如果想讓代碼在任何瀏覽器中都能執行,可以用與之前代碼等價的下面這段代碼:

this.valuesLegacy = function{
  let values = ;
  for(let key in items) { //{7}
    if(items.hasOwnProperty(key)) { //{8}
      values.push(items[key]);
    }
  }
  return values;
};

  

所以,首先遍歷items對象的所有屬性(行{7}),把它們添加一個數組中(行{8}),並返回這個數組。該方法類似於我們開發的sizeLegacy方法,但我們添加一個數組,而不是計算屬性個數。

6.2.6 使用Set

現在數據結構已經完成了,看看如何使用它吧。試著執行一些命令,測試我們的Set類:

let set = new Set;

set.add(1);
console.log(set.values); //輸出[\"1\"]
console.log(set.has(1)); //輸出true
console.log(set.size); //輸出1

set.add(2);
console.log(set.values); //輸出[\"1\", \"2\"]
console.log(set.has(2)); //true
console.log(set.size); //2

set.remove(1);
console.log(set.values); //輸出[\"2\"]

set.remove(2);
console.log(set.values); //輸出

  

現在我們有了一個和ECMAScript 6中非常類似的Set類實現。如前所述,也可以用數組替代對象,存儲元素。既然我們在第2章、第3章和第4章都用過數組,知道有不同方式實現同樣的東西,這也不錯。