JS之路 Day08 - What is Set ?
前言
這兩天會來講Set/Map
,這是ES6
後才新增的兩種資料結構,主要是用來處理一些array
跟object
沒辦法完全顧及的領域,也因如此,其實許多地方跟array
跟object
非常相似,那今天就從set
開始吧!
set
是集合的意思,在JavaScript
裡是唯一值的集合,意思就是說,set
裡面的每一個值都只能出現一次。
什麼是集合?
set 的中文翻譯是集合的意思,在 JavaScript 裡這個語法是唯一值的集合,而數學上的集合定義,去爬了維基百科,得到以下結論:
指具有某種特定性質的事物的母體,集合裡的事物稱作元素。
下圖就是一個有一些多邊形的集合。關於集合我的理解是可以把它當作一堆類型差不多的東西,而在JavaScript
就是一堆值,而在array
確實就只有一大堆值,跟Set
的差別就在於,JS 的Set
值是獨一無二的。
出處: https://zh.wikipedia.org/zh-tw/%E9%9B%86%E5%90%88_%28%E6%95%B0%E5%AD%A6%29
set
常用方法與屬性
- new Set() => 創建一個
set
- add() => 往
set
裡面新增一個元素 - delete() => 從
set
裡面刪除一個元素 - has() => 判斷
set
有沒有存在指定的元素 - clear() => 刪除
set
裡面所有東西 - size => 回傳整個
set
裡面元素的種類量
以下簡單實作一次:
1 | const vicSet = new Set([1, 2, 2, 3]); |
拿不出的值,跟沒有 index 的Set
我嘗試從set
裡面拿到值,但發現拿不出來,why?
1 | let vicSet = new Set([1, 2, 3, 4, 5]); |
因為set
實際上就沒有 index,所以沒有辦法從set
獲取值,可能也沒有必要,不然就會發明出一個叫做 get()的方法對吧?
我想只需要知道,某一個值是不是還存在於集合裡面,所以有 has()的方法可以檢查,畢竟set
的特點是獨一無二的值,都是唯一的,順序沒有意義。
沒有必要在set
獲取值的原因是,如果目的是為了按照順序存取值,然後去獲取它,**那就應該要去用array
,而不是Set
**。
可以想像成是,假如我有五顆蘋果要塞入一個盤子集合裡面:
用set
的方式 => 盤子裡有一個種類 - 蘋果 (不知道誰先誰後)。
用array
的方式 => 蘋果陸陸續續地進到了盤子裡面 (知道誰先誰後)。
1 | const plate = ["apple", "apple", "apple", "apple", "apple"]; |
那我又想要有set
的特性,又想要有array
的 index 使用方式要怎麼做?
用set
把資料都處理完後,再轉成array
,就可以了。
1 | let vicSet = new Set([1, 2, 3, 4, 5]); |
Set 可以被迭代
這代表著使用 for..of
或 forEach
来遍歷 Set
。
基本上跟array
都一樣,因為都是 Iterable(可迭代)。
1 | let set = new Set(["apple", "vic", "book"]); |
Set
迭代方法
- set.keys() => 遍歷 set
- set.values() => 遍歷 set
- entries() => 遍歷 set 後再返回一個實體
1 | const vicSet = new Set([1, 2, 2, 3, 4, 5]); |
Set 實際上使用時機=> 刪除 arr 的重複值
這邊會用到一個概念 Spread Operator
(展開運算符)。
Spread Operator
es6 才出來的一個新語法,來介紹一下它的作用。
其實就是展開的概念,看下面我寫的簡單範例應該就能了解。
1 | const A = [4, 5, 6]; |
...
在當Spread Operator
時,後面一定接著一個陣列,然後功用就是會把這個陣列給展開,像是上面範例那樣,常常用於來連接陣列,但是還有另外一種方法。
只要...
前面沒東西時,後面也是會展開,但因為前面沒東西,就像是直接複製的感覺,所以也可以來當作陣列的淺拷貝(淺拷貝意思是改 arr2 不會影響到 arr)。
1 | const arr = [1, 2, 3]; |
方法一
使用剛剛前面講到的Spread Operator
,搭配set
的特性,先把陣列裡面的值都變成set
獨一無二的值之後,再用Spread Operator
複製起來到一個新的陣列,大功告成。
1 | function unique(arr) { |
方法二
首先要先了解Array.from
是什麼,在這裡要知道的是它可以把set
的東西轉成陣列,所以一樣是先把陣列裡面的值都變成set
獨一無二的值之後,再轉成一個新的陣列,完成。
1 | function unique(arr) { |
WeakSet
最後來講講跟Set
長得很像的WeakSet
,其實不會差太多,但還是有一些差異。
像是會習慣在Set
使用array
但是在WeakSet
使用時會報錯,這是因為在WeakSet
只能增加object
。
1 | let set = new WeakSet([1, 2, 3, 4, 5]); |
而跟WeakSet
一樣的地方是會保留Set
的一些方法:
- add()
- delete()
- has()
但是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
的特性,獨一無二沒辦法重複。
主要用來判斷是跟否,而不是拿來做使用,所以是沒辦法拿出來獲取的。
1 | let weakSet = new WeakSet(); |
要直接清除掉只要再使用null
就好。
1 | a = null; |
簡單來說,WeakSet
可以做的幾件事情:
- 幫其他地方的物件做一個額外的存取空間(集合)。
- 將物件弱連結(weak)到集合中 => add() 。
- 判斷這個物件有沒有成功有連結(佔用)了 => true、false。
- 從集合中刪除這個物件 => null 。
什麼情境會使用到呢?
個人覺得是很怕犯錯,需要避免錯誤時,可以在這個被「隔離」的集合內做檢查。
而 MDN 上面有提到說,可以用來檢測循環引用,這個我看起來也因為需要避免錯誤(迭代時涉及循環引用的錯誤),有興趣可以去研究看看。
reference
[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