PS:原文只介紹了Array和Set的一些簡單操作,詳細API放在文章底部。
Array和Set是什麼
每一個使用JavaScript程式設計的人都應該對Array非常熟悉。 通常,我們可以這樣形容它:陣列是表示一種儲存在連續空間中的結構型別(內容可以為number,object等)。
例如:[1, 2, 3, 4, 5]
(原文這裡說法存在歧義,具體陣列在記憶體中的儲存方式可以參考連結一、連結二)
而Set更像是一種抽象的資料型別。它只包含不同的元素/物件,不需要連續分配儲存空間。
例如:{1, 2, 3}
它們之間最大的差別就是Array中的元素是可以重複的,而Set中的元素不可重複 。除此之外,Array被認為是一種索引集合,而Set是一種鍵的集合。
索引集合是指按下標作為索引值排序的資料結構
鍵的集合使用key訪問元素,訪問順序與元素插入順序一致。
很簡單對吧?現在可能有人會問,既然這兩者如此不同,為什麼還要將它們進行比較?
在程式設計時,我們可以使用Array或Set來儲存相同的資料集。但根據使用情況,我們應該選用正確的資料結構以助於提供最佳的解決方案。為了實現這個目標,首先我們需要了解它們分別是什麼,有什麼特點和能力。前面的文章中我們對這兩種資料結構已經有了初步的瞭解,接下來就來看看它們的構建方式。
如何構建
Array
陣列的構建非常簡單直接,在JS中宣告一個陣列你可以直接使用字面上的語法:
var arr = []; //空陣列
var arr = [1,2,3]; //元素為1,2,3的陣列
複製程式碼
或者使用建構函式:
var arr = new Array(); //空陣列
var arr = new Array(1,2,3);//元素為1,2,3的陣列
複製程式碼
甚至這種更酷的方法:
var arr = Array.from("123"); //["1","2","3"]
複製程式碼
注:
一條建議——除了非用不可的情況儘量不要使用new Array()
,原因如下:
new Array()
的效能比[]
慢很多[]
節約更多輸入的時間- 你可能會犯一些經典的錯誤,比如:
var arr1 = new Array(10); //長度為10,每個元素都為undefined
var arr2 = [10]; // arr2[0] = 10 且 arr2.length = 1;
var arr3 = new Array(1,2,3); //[1,2,3]
var arr4 = [1,2,3];//[1,2,3]
複製程式碼
所以,儘量保持一個原則——簡單化
Set
Set使用內建的建構函式初始化,沒有其它簡便的方法。
Set([iterable])
想要建立一個Set
,需要使用new
方法,例如:
var emptySet = new Set();
var exampleSet = new Set([1,2,3]);
複製程式碼
不能使用如下方式:
new Set(1);
複製程式碼
Set接收一個可遍歷的物件作為其輸入引數,並將遍歷元素依次作為Set中的元素生成物件。因此,我們可以通過陣列作為引數去建立Set——但建立的Set中只包含陣列中的非重複元素,如上文提到,Set中元素不可重複。
當然,我們也能通過Array.from()
方法將Set還原為陣列:
var set = new Set([1,2,3]); // {1,2,3}
var arr = Array.from(set);//[1,2,3]
複製程式碼
現在,我們知道了如何構建這兩種資料結構,接下來讓我們對Array / Set提供的最基本方法進行比較。
訪問元素
- 首先Set不支援像Array一樣通過索引隨機訪問元素
console.log(set[0]); //undefined
console.log(arr[0]); //1
複製程式碼
- 更重要的是,因為Array中的資料儲存在連續的空間中,所以CPU能夠通過預處理操作更快地進行資料訪問。因此,與其他型別的抽象資料型別進行比較,訪問Array中的元素(按順序,例如for迴圈)通常會更快更有效。
- 判斷一個元素是否在Set中的語法比判斷是否在Array中更簡潔。Set:
Set.prototype.has(value)
。Array:Array.prototype.indexOf(value)
。
console.log(set.has(0)); // boolean - false
console.log(arr.indexOf(0)); // -1
console.log(set.has(1)); //true
console.log(arr.indexOf(1)); //0
複製程式碼
這意味著在Array中,我們需要額外建立檢查元素是否在Array中的條件:
var isExist = arr.indexOf(1) !== -1;
複製程式碼
注:ES6提供了與Set.prototype.has()
相似的方法Array.prototype.includes()
來判斷元素是否在陣列中,但這個方法並沒有廣泛地被瀏覽器支援——比如IE..
插入元素
Array
- 向陣列中插入元素可以使用時間複雜度為O(1)的
Array.prototype.push()
方法快速完成——元素將會被插入到陣列的末尾。
arr.push(4); //[1,2,3,4]
複製程式碼
- 或者可以使用時間複雜度為O(n)的
Array.prototype.unshift()
方法實現——元素被插入到陣列的頭部。
arr.unshift(3); //[3,1,2,3]
arr.unshift(5, 6); //[5,6,3,1,2,3]
複製程式碼
Set
- 只有一個方法可以在Set中插入元素——
Set.prototype.add()
。由於Set需要保持其“元素不可重複”的特性,所以每次呼叫add()
方法,Set都需要便利所有元素以確保無重複元素,通常情況下add()
方法的時間複雜度應為O(n)。不過Set的內部採用雜湊表的實現方式,因此可以達到O(1)的時間複雜度。
set.add(3); //{1,2,3}
set.add(4); //{1,2,3,4}
複製程式碼
刪除元素
Array
- 使陣列如此受歡迎的原因之一是它提供了許多不同的方法去刪除元素,例如:
var arr = [5, 6, 1, 2, 3, 4]
複製程式碼
pop()
——刪除並返回最後一個元素,時間複雜度O(1)
arr.pop();//return 4, [5,6,1,2,3]
複製程式碼
shift()
——刪除並返回第一個元素,時間複雜度O(n)
arr.shift(); //return 5; [6,1,2,3]
複製程式碼
splice(index, delectCount)
——刪除從index開始的delectCount個元素。時間複雜度為O(n)
arr.splice(0,1); //[1,2,3]
複製程式碼
Set
var set = new Set([1, 2, 3, 4]);
複製程式碼
delete(element)
——刪除指定的element
set.delete(4); //{1,2,3}
複製程式碼
clear()
——清空Set中所有元素
set.clear(); //{}
複製程式碼
Array並未提供原生方法刪除指定元素,我們需要藉助其它方法找到元素對應下標,通過splice()
刪除,而Set可以直接使用delete()
進行刪除。
另外,對比Set只提供了一些基本的運算元據的方法,Array提供了更多實用的原生方法(reduce()
,map()
,sort()
等),所以有些人可能會想,為什麼我們比起Array會更喜歡Set?
Array和Set的應用場景
- 首先,Set與Array是不同的兩種資料結構,它並不是要完全替換Array,而是提供額外的資料型別來完成Array缺少的一些功能。
- 由於Set只包含不同的元素,如果我們只希望儲存不同的元素,使用Set會更好。
- 可以利用Set的一些原生方法輕鬆的完成陣列去重,查詢陣列公共元素及不同元素等操作。Set的
delete()
方法,使求兩個Set的交/並集的操作比求兩個陣列的交/並集的操作更簡單。 - 陣列適用於希望保留重複元素,可能進行大量修改(增、刪操作),或是希望通過索引對元素進行快速訪問的場景。(試想對一個Set進行二分查詢——如何獲取到Set的中間元素?)
結論
總之,就我而言對比Array,Set並沒有明顯優勢,除了在特定場景中,例如當我們想要以最小成本維護不重複的資料,或者使用到大量不同的資料集時只需要使用最基本的集合操作而無需直接訪問元素。
否則,Array通常是更好的選擇。因為在需要時獲取元素更節省CPU工作量。
歡迎討論。
其他資料連結: