這兩天會來講Set/Map
,這是ES6
後才新增的兩種資料結構,主要是用來處理一些array
跟object
沒辦法完全顧及的領域,也因如此,其實許多地方跟array
跟object
非常相似,那今天就從set
開始吧!
set
是集合的意思,在JavaScript
裡是唯一值的集合,意思就是說,set
裡面的每一個值都只能出現一次。
set的中文翻譯是集合的意思,在JavaScript裡這個語法是唯一值的集合,而數學上的集合定義,去爬了維基百科,得到以下結論:
指具有某種特定性質的事物的母體,集合裡的事物稱作元素。
下圖就是一個有一些多邊形的集合。關於集合我的理解是可以把它當作一堆類型差不多的東西,而在JavaScript
就是一堆值,而在array
確實就只有一大堆值,跟Set
的差別就在於,JS的Set
值是獨一無二的。
set
常用方法與屬性set
set
裡面新增一個元素set
裡面刪除一個元素set
有沒有存在指定的元素set
裡面所有東西set
裡面元素的種類量以下簡單實作一次:
const vicSet = new Set([1, 2, 2, 3]);
console.log(vicSet); // Set(3) { 1, 2, 3 }
// ↑一開始創建出來,set裡面是空的
set.add(4);
console.log(vicSet); // Set(4) { 1, 2, 3, 4 }
// ↑幫set新增加了4這個元素進去
set.delete(1);
console.log(vicSet); // Set(3) { 2, 3, 4 }
// ↑把1這個元素從set完整的刪除
console.log(vicSet.has(1), vicSet.has(2)); // false true
// ↑判斷set裡面有沒有這些指定的元素
console.log(vicSet.size); // 3
// ↑這個屬性將會找出set裡面元素的種類
set.clear();
console.log(vicSet); // Set(0) {}
// ↑把map裡面所有東西都清空
Set
我嘗試從set
裡面拿到值,但發現拿不出來,why?
let vicSet = new Set([1, 2, 3, 4, 5]);
console.log(cicSet[0]);
// undefined
因為set
實際上就沒有index,所以沒有辦法從set
獲取值,可能也沒有必要,不然就會發明出一個叫做get()的方法對吧?
我想只需要知道,某一個值是不是還存在於集合裡面,所以有has()的方法可以檢查,畢竟set
的特點是獨一無二的值,都是唯一的,順序沒有意義。
沒有必要在set
獲取值的原因是,如果目的是為了按照順序存取值,然後去獲取它,那就應該要去用array
,而不是Set
。
可以想像成是,假如我有五顆蘋果要塞入一個盤子集合裡面:
用set
的方式 => 盤子裡有一個種類 - 蘋果 (不知道誰先誰後)。
用array
的方式 => 蘋果陸陸續續地進到了盤子裡面 (知道誰先誰後)。
const plate = ["apple", "apple", "apple", "apple", "apple"];
console.log(plate[3]); // apple, 第四顆進去的蘋果
const plate2 = new Set();
plate2.add(["apple", "apple", "apple", "apple", "apple"]);
console.log(plate2); //Set(1), 一個種類 蘋果
set
的特性,又想要有array
的index使用方式要怎麼做?用set
把資料都處理完後,再轉成array
,就可以了。
let vicSet = new Set([1, 2, 3, 4, 5]);
let arrVicSet = Array.from(vicSet);
console.log(arrVicSet[0]); // 1
這代表著使用 for..of
或 forEach
来遍歷 Set
。
基本上跟array
都一樣,因為都是Iterable(可迭代)。
let set = new Set(["apple", "vic", "book"]);
for (const order of set) {
console.log("for..of 方法: " + order);
}
set.forEach((order) => {
console.log("forEach: " + order);
});
// for..of 方法: apple
// for..of 方法: vic
// for..of 方法: book
// forEach: apple
// forEach: vic
// forEach: book
Set
迭代方法const vicSet = new Set([1, 2, 2, 3, 4, 5]);
console.log(vicSet.keys()); //[Set Iterator] { 1, 2, 3, 4, 5 }
console.log(vicSet.values()); //[Set Iterator] { 1, 2, 3, 4, 5 }
console.log(vicSet.entries()); //[Set Entries] { [ 1, 1 ], [ 2, 2 ], [ 3, 3 ], [ 4, 4 ], [ 5, 5 ] }
這邊會用到一個概念 Spread Operator
(展開運算符)。
es6才出來的一個新語法,來介紹一下它的作用。
其實就是展開的概念,看下面我寫的簡單範例應該就能了解。
const A = [4, 5, 6];
const B = [1, 2, 3, ...A];
console.log(B); //[ 1, 2, 3, 4, 5, 6 ]
...
在當Spread Operator
時,後面一定接著一個陣列,然後功用就是會把這個陣列給展開,像是上面範例那樣,常常用於來連接陣列,但是還有另外一種方法。
只要...
前面沒東西時,後面也是會展開,但因為前面沒東西,就像是直接複製的感覺,所以也可以來當作陣列的淺拷貝(淺拷貝意思是改arr2不會影響到arr)。
const arr = [1, 2, 3];
const arr2 = [...arr];
console.log(arr2); //[1, 2, 3]
使用剛剛前面講到的Spread Operator
,搭配set
的特性,先把陣列裡面的值都變成set
獨一無二的值之後,再用Spread Operator
複製起來到一個新的陣列,大功告成。
function unique(arr) {
return [...new Set(arr)];
}
let values = [1, 2, 2, 2, 3, 4, 4, 4, 5, 5];
let newValues = unique(values);
console.log(newValues); // [ 1, 2, 3, 4, 5 ]
首先要先了解Array.from
是什麼,在這裡要知道的是它可以把set
的東西轉成陣列,所以一樣是先把陣列裡面的值都變成set
獨一無二的值之後,再轉成一個新的陣列,完成。
function unique(arr) {
return Array.from(new Set(arr));
}
let values = [1, 2, 2, 2, 3, 4, 4, 4, 5, 5];
let newValues = unique(values);
console.log(newValues);; // [ 1, 2, 3, 4, 5 ]
最後來講講跟Set
長得很像的WeakSet
,其實不會差太多,但還是有一些差異。
像是會習慣在Set
使用array
但是在WeakSet
使用時會報錯,這是因為在WeakSet
只能增加object
。
let set = new WeakSet([1, 2, 3, 4, 5]);
console.log(set);//TypeError: Invalid value used in weak set
而跟WeakSet
一樣的地方是會保留Set
的一些方法:
但是size
就沒辦法使用要特別注意。
那Set
要變成WeakSet
的用意在哪裡?從字面意思上來看,weak
是虛弱的意思,而虛弱的Set
也沒辦法讓人直覺想到東西。
從MDN的解釋來看:
The WeakSet is weak, meaning references to objects in a WeakSet are held weakly. If no other references to an object stored in the WeakSet exist, those objects can be garbage collected.
來源:MDN
後面那段大意是說如果不存在對儲存在物件的其他引用,就可以把這些物件進行垃圾回收。
意思是如果有個物件使用 WeakSet
之後,如果其他物件都不再引用這個物件,那就會有一個垃圾回收的機制,這個機制可能就是把這個對象所佔用的內存通通消滅掉,丟到垃圾桶。
這樣做有一些好處,首先變成Weak
(弱)狀態的同時,可以想像成多了一個空間,可以拿來檢查,同時因為有Set
的特性,獨一無二沒辦法重複。
主要用來判斷是跟否,而不是拿來做使用,所以是沒辦法拿出來獲取的。
let weakSet = new WeakSet();
let a = { a: 1 };
let b = { b: 1 };
let c = { c: 1 };
weakSet.add(a);
weakSet.add(b);
console.log(weakSet); // WeakSet { <items unknown> }
console.log(weakSet.has(a)); // true
console.log(weakSet.has(c)); // false
要直接清除掉只要再使用null
就好。
a = null;
console.log(weakSet.has(a)); // false
簡單來說,WeakSet
可以做的幾件事情:
什麼情境會使用到呢?
個人覺得是很怕犯錯,需要避免錯誤時,可以在這個被「隔離」的集合內做檢查。
而MDN上面有提到說,可以用來檢測循環引用,這個我看起來也因為需要避免錯誤(迭代時涉及循環引用的錯誤),有興趣可以去研究看看。
[1] MDN - Set
[2] W3Schools - JavaScript Sets
[3] The Modern JavaScript Tutorial - Map and Set
[4] JS 原力覺醒 Day29 - Set / Map
[5] PJCHENder -JavaScript 集合(Set)
[6] 前端工程師用 javaScript 學演算法 - 集合 Set
[7] 維基百科 - 集合 (數學)
[8] How to get index based value from Set