點燈坊

失くすものさえない今が強くなるチャンスよ

如何取得以 Object 為 Key 的 Map ?

Sam Xiao's Avatar 2019-11-25

Map 為 ECMAScript 2015 新支援的資料結構,類似 Object 但功能更強,尤其 Key 可為任何型態包含 Object;若 Key 為 Object 時,由於 ECMAScript 語言特性,將無法簡單使用 get() 取得資料。

Version

macOS Catalina 10.15.1
VS Code 1.40.1
Quokka 1.0.261
ECMAScript 2015

Map

Primitive

let data = new Map([
  [1, 'FP in JavaScript'],
  [2, 'RxJS in Action'],
  [3, 'Speaking JavaScript']
])

let fn = val => data.get(val)
                   
fn(1) // ?

直接以 array 傳入 Map 的 constructor 建立 map,若 key 為 primitive,可直接以 get() 取得。

map000

Object

let data = new Map([
  [{ identity: 'admin', status: 1 }, 0],
  [{ identity: 'admin', status: 2 }, 1],
  [{ identity: 'member', status: 1 }, 2],
  [{ identity: 'member', status: 2 }, 3],
  [{ identity: 'all', status: 'default' }, 4]
])

let fn = (identity, status) => data.get({ identity, status })

fn('admin', 2); // ?
fn('admin', 3); // ?

若 map 的 key 為 object,直覺會想用 object literal 湊出 object,然後使用 get() 取值。

map001

會發現因為找不到而回傳 undefined

因為 ECMAScript 對於 object 的比較是 reference,所以新湊的 object 的 reference 一定與 data 內 object 不相同,因此一定找不到而回傳 undefined

let data = new Map([
  [{ identity: 'admin', status: 1 }, 0],
  [{ identity: 'admin', status: 2 }, 1],
  [{ identity: 'member', status: 1 }, 2],
  [{ identity: 'member', status: 2 }, 3],
  [{ identity: 'all', status: 'default' }, 4]
]);

let fn = (identity, status) => [...data]
  .filter(([k, _]) => k.identity === identity && k.status === status)
  .map(([_, v]) => v)
  .reduce((_, x) => x, 0)

fn('admin', 2); // ?
fn('admin', 3); // ?

使用 ES6 的 spread operator 將 map 展開再形成 array,透過其 filter() 語法針對 object 的 value 做比較。

唯 map 轉成 array 後為兩層 array,須再使用 array destructuring [k, v] 分解 key / value,map() 只取 value 後仍然是 array,最後使用 reduce() 取得單一 value。

map002

Conclusion

  • 當 map 的 key 為 object 時,無法簡單以 get() 取得 value,必須將 map 轉成 array,透過 array 的 filter()map()reduce() 合作才能取得 value