在看完前三天的 Array & Object 組合技,感覺只要這兩個東西練得夠熟,應該就可以 cover 大部分關於資料儲存結構的問題了。
或許一般教學 Javascript,的確是到這就差不多了,但這系列文是不斷邁向更「好」的,所以我們再讓自己多學一點!
因為你看到了嗎?Array 跟 Object 他們也在進化啦!
Set 中文可以翻成「集合」,數學上的那個「集合」,所以 Set 可以比較容易做到「交集」、「聯集」、「差集」等動作。
Set 的重點是「元素不可重複」,語法像下方這樣,需要用 new 關鍵字來產生一個 Set,如果有初始值則帶入一個 Array 到參數內:
new Set([iterable]);
Set 可以說是 Array 的進化版,但主要有兩點跟 Array 不同:
聽起來這進化幅度有點小欸,第二點是不是退化了啊?而且就只是從「可重複」變成「不可重複」而已,那我也可以在 Array 裡面判斷是否重複呀:
const arr = ['Susan', 'Allen', 'Jack'];
const newItem = 'Allen';
if (!arr.includes(newItem)) {
arr.push(newItem);
}
是的,看起來就是多了個 if 判斷而已,但如果我有很多個地方都要做 push
的動作,甚至不只是 push,連 unshift
、splice
等 method 也都會有新增元素到陣列的動作,那我的 if 判斷式不就加不完了?
就算真的都克服了上述的麻煩,寫出來的程式也的確是「對」的,但如果換另一個工程師接手維護,光用看的也沒辦法立刻知道,哪個 Array 會重複,哪個 Array 不會重複。
上述的感覺就像是:我接手了一份 code,裡面全都用
let
來宣告變數,完全沒有const
。程式一定能跑沒問題,但我就完全不知道哪些變數會變動、哪些不會。
也就是說,Array 這個進化其實不是功能上的進化,而是可讀性與可維護性的進化。
最簡單的用法,就是把一個帶有重複元素的陣列,丟進去 Set,然後再透過 spread oparator 轉回 Array:
const arr = ['Susan', 'Allen', 'Jack', 'Allen'];
const arrSet = new Set(arr);
const uniqueArray = [...arrSet];
console.log(uniqueArray);
執行結果
['Susan', 'Allen', 'Jack']
比如我是個送貨員,手上有今天所有要配送的訂單,但有些地址是重複的,我想要到一個地點就把當地所有貨送完。
const arr = [
{ customer: 'Allen', address: '新北市政府'},
{ customer: 'Susan', address: '台大醫院'},
{ customer: 'Jack', address: '板橋高鐵站'},
{ customer: 'Alice', address: '新北市政府'},
];
const arrSet = new Set(arr.map(item => item.address));
console.log(`今天總共要送 ${arrSet.size} 個地方`);
console.log(Array.from(arrSet).join('、'));
執行結果
3
新北市政府、台大醫院、板橋高鐵站
大哉問,我第一次接觸到 Set 的時候,雖然知道這是為了模擬數學上的「集合」,但,「集合」應該是沒有順序的才對吧?
關於順序的部分,有發現嗎?上面兩個範例中,雖然我都沒有辦法使用像是 arrSet[0]
之類,透過 index 存取的語法,看起來好像 Set 沒有順序的概念,但當我透過 [...arrSet]
或者 Array.from(arrSet)
轉換成 Array 時,它的順序都跟原本一樣。
代表 Javascript 的 Set 其實是有紀錄順序的(可參考 MDN,但很不明顯,再補充個 StackOverflow):
new Set(arr)
或 Array.from(arr)
的 arr 順序Set.add()
新增的元素順序因此,雖然 Set 比 Array 少了 index 的操作,但卻幫我們處理掉「重複」的問題,而這也是我們真正要使用它的原因,可以透過在 Array 跟 Set 之間切換(當然不要太頻繁),把 Array 最怕的重複問題解決掉。
當我們使用 Set.add()
指令時,Javascript 背後是如何判斷有沒有重複的呢?
背後其實是使用嚴格相等(===)
也就是說,如果 Set 裡面放的是 non-primitive 的元素要特別小心,因為 non-primitive 是 by reference 來判斷,所以通常都會當作「不重複」處理(因為記憶體位址不同):
const arr = [
{ customer: 'Allen', address: '新北市政府'},
{ customer: 'Allen', address: '新北市政府'},
];
const arrSet = new Set(arr);
console.log(arrSet);
arrSet.add({ customer: 'Allen', address: '新北市政府'});
console.log(arrSet);
執行結果
Set(2) {
{ customer: 'Allen', address: '新北市政府'},
{ customer: 'Allen', address: '新北市政府'}
}
Set(3)) {
{ customer: 'Allen', address: '新北市政府'},
{ customer: 'Allen', address: '新北市政府'},
{ customer: 'Allen', address: '新北市政府'}
}
另外,NaN 和 undefined 都可以被放置在 Set 中(儘管 NaN !== NaN)。
Map 跟 Object 很像,使用起來最主要的差別是:
new map([iterable]);
雖然 key 可以用 Object、Array、甚至 function,但因為 key 不能重複,仍然會回到「如何判斷 key 有沒有重複」的問題。
答案跟前面介紹的 Set 非常像,就是 嚴格相等(===) 來判斷,所以如果要用 non-primitive 來當 key 要特別小心:
const personMap = new Map();
personMap.set({
height: 173,
weight: 63
}, 'Joey');
// 可能。。。有個身高體重跟 Joey 一模一樣的 Susan
personMap.set({
height: 173,
weight: 63
}, 'Susan');
console.log(personMap.size);
console.log(personMap);
執行結果
2
Map(2) {{…} => "Joey", {…} => "Susan"}
補充,上面的範例也可以改寫成這樣,直接給初始值,初始值要是 Array of Array:
const personMap = new Map([
[{ height: 173, weight: 63 }, 'Joey'],
[{ height: 173, weight: 63 }, 'Susan']
]);
console.log(personMap.size);
console.log(personMap);
Object 跟 Map 很像,但在 key/value 的順序方面,Object 的 key/value 基本上還是會根據設置(set)的順序,但是根據 MDN 的說法,它的排序還是有一些複雜因素影響(起碼如果數字的 key 就不會按照 set 順序),所以盡量不要相信 Object key 的順序XD
但 Map 就不一樣了,會按照 key/value 的 set 順序,可以透過 Object.keys
/ Object.values
/ Object.entries
看到這些跡象。
比如說,我們要幫學生設定的 key/value,分別用 座號/名字:
const studentObj = {};
studentObj['22'] = 'Joey';
studentObj['33'] = 'Allen';
studentObj['11'] = 'Susan';
console.log(Object.keys(studentObj));
const studentMap = new Map();
studentMap.set('22', 'Joey');
studentMap.set('33', 'Allen');
studentMap.set('11', 'Susan');
console.log(studentMap.keys());
執行結果
["11", "22", "33"]
MapIterator {"22", "33", "11"}
Map 跟 Object 很像,一些基本的情境其實兩邊可以互相取代,但比較適合 Map 的情境是:
Set 跟 Map 算是比較特殊用途才會出現的,因此說真的就算直接無視它們也是活得好好的,不過多學一點總是好的,因為當使用 Object、Array 的過程中覺得「好像哪裡卡卡的」的時候,就會有一道光降臨到頭頂上,會有個長著翅膀的人飛過來問你,「要不試試 Set 或 Map?」
獨一無二的個體
即便外表相似
靈魂仍在不同的地址
Set MDN
Map MDN
es6-map-vs-object-what-and-when
why-does-js-keep-insertion-order-in-set
Set 可以說是 Array 的進化版
印象中 Set 的背後的實作還是 hash table,所以在存取資料的速度上也比 array 還要快(這也是進化的一部分)
原來如此,我這次沒有研究到這一塊(筆記),感謝補充!
我發現每篇文章最後面都有一小段的 (詩句?)
天平的兩端
藏著各自的風景
寫著各自的故事漫天星點
在每一個連結與跨越
化作指引的星輝自己寫下自己的屬性
自己決定自己的命運獨一無二的個體
即便外表相似
靈魂仍在不同的地址
太厲害了!
這位看倌眼睛真利!而且還幫我全部連在一起了~
我也說不上這是(殘缺的)新詩還什麼,單純是從我的理解,轉換成另一種視角來看那天的主題,或許等 30 天過去,再來試試看能不能從新詩反推回去是哪一天的主題 XDDD (怎麼感覺有點像 Map
)
靈魂仍在不同的地址
精神分裂XD