目前的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)
方法。這是因為它會被add
、remove
等其他方法調用。下面看看它的實現:
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 remove
和clear
方法
下面要實現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
變量,每當使用add
或remove
方法時控制它,就像在上一章中使用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章都用過數組,知道有不同方式實現同樣的東西,這也不錯。