Map, Set, WeakMap and WeakSet

現在已知有 object 儲存 key 的資料,array 儲存 index 的資料,但這對現實世界來說還不夠。

Map

map 跟 object 一樣,唯一不同的是 key 可以是任何類型的值。map 的方法如下:

  • new Map() – creates the map.

  • map.set(key, value) – stores the value by the key.

  • map.get(key) – returns the value by the key, undefined if key doesn’t exist in map.

  • map.has(key) – returns true if the key exists, false otherwise.

  • map.delete(key) – removes the value by the key.

  • map.clear() – clears the map

  • map.size – returns the current element count.

let map = new Map();

map.set('1', 'str1');   // a string key
map.set(1, 'num1');     // a numeric key
map.set(true, 'bool1'); // a boolean key

// remember the regular Object? it would convert keys to string
// Map keeps the type, so these two are different:
alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3

// object key
let john = { name: "John" };
// for every user, let's store their visits count
let visitsCountMap = new Map();
// john is the key for the map
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123

// object with object key
let john = { name: "John" };
let visitsCountObj = {}; // try to use an object
visitsCountObj[john] = 123; // try to use john object as the key
// That's what got written!
alert( visitsCountObj["[object Object]"] ); // 123

// before map exist
// we add the id field
let john = { name: "John", id: 1 };
let visitsCounts = {};
// now store the value by id
visitsCounts[john.id] = 123;
alert( visitsCounts[john.id] ); // 123

// chain 
map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');

Map from Object

// array of [key, value] pairs
let map = new Map([
  ['1',  'str1'],
  [1,    'num1'],
  [true, 'bool1']
]);

// Object.entries() returns [ ["name","John"], ["age", 30] ]
let map = new Map(Object.entries({
  name: "John",
  age: 30
}));

Iteration over Map

  • map.keys() – returns an iterable for keys,

  • map.values() – returns an iterable for values,

  • map.entries() – returns an iterable for entries [key, value], it’s used by default in for..of.

let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion
}

// iterate over values (amounts)
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50
}

// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
  alert(entry); // cucumber,500 (and so on)
}

// map 有內建 forEach 方法
// runs the function for each (key, value) pair
recipeMap.forEach( (value, key, map) => {
  alert(`${key}: ${value}`); // cucumber: 500 etc
});

Set

set 是一個值的集合,裡面的值不能重複。替代方案是插入值時用 arr.find() 檢查是否有該值,但會造成性能變差。

  • new Set(iterable) – creates the set, and if an iterable object is provided (usually an array), copies values from it into the set.

  • set.add(value) – adds a value, returns the set itself.

  • set.delete(value) – removes the value, returns true if value existed at the moment of the call, otherwise false.

  • set.has(value) – returns true if the value exists in the set, otherwise false.

  • set.clear() – removes everything from the set.

  • set.size – is the elements count.

let set = new Set();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

// visits, some users come multiple times
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);

// set keeps only unique values
alert( set.size ); // 3

for (let user of set) {
  alert(user.name); // John (then Pete and Mary)
}

Iteration over Set

  • set.keys() – returns an iterable object for values,

  • set.values() – same as set.keys, for compatibility with Map,

  • set.entries() – returns an iterable object for entries [value, value], exists for compatibility with Map.

let set = new Set(["oranges", "apples", "bananas"]);

for (let value of set) alert(value);

// the same with forEach:
set.forEach((value, valueAgain, set) => {
  alert(value);
});

WeakMap and WeakSet

WeakMap / WeakSet 不會阻止 JavaScript 清除 key 的記憶體。換句話說當 key 不存在,存在同個屬性的資料也會不見,使用在當物件存在的時候才需要的資料,物件消失資料也消失。

// delete object 
let john = { name: "John" };
// the object can be accessed, john is the reference to it
// overwrite the reference
john = null;
// the object will be removed from memory

// store in array object still exist
let john = { name: "John" };
let array = [ john ];
john = null; // overwrite the reference
// john is stored inside the array, so it won't be garbage-collected
// we can get it as array[0]

// store in map object still exict
let john = { name: "John" };
let map = new Map();
map.set(john, "...");
john = null; // overwrite the reference
// john is stored inside the map,
// we can get it by using map.keys()

// weakMap / weakSet 只能使用 object 當 key 值
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, "ok"); // works fine (object key)
// can't use a string as the key
weakMap.set("test", "Whoops"); // Error, because "test" is not an object

// store in weakMap object desroty
let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // overwrite the reference
// john is removed from memory!

// 如果物件被移除,儲存值也會被移除
weakMap.set(john, "secret documents");
// if john dies, secret documents will be destroyed automatically

// 手動清理不要的物件
let john = { name: "John" };
// map: user => visits count
let visitsCountMap = new Map();
// john is the key for the map
visitsCountMap.set(john, 123);
// now john leaves us, we don't need him anymore
john = null;
// but it's still in the map, we need to clean it!
alert( visitsCountMap.size ); // 1
// and john is also in the memory, because Map uses it as the key

// weakMap 當物件消失值也會消失
let john = { name: "John" };
let visitsCountMap = new WeakMap();
visitsCountMap.set(john, 123);
// now john leaves us, we don't need him anymore
john = null;
// there are no references except WeakMap,
// so the object is removed both from the memory and from visitsCountMap automatically

// weakSet exaample
let messages = [
    {text: "Hello", from: "John"},
    {text: "How goes?", from: "John"},
    {text: "See you soon", from: "Alice"}
];
// fill it with array elements (3 items)
let unreadSet = new WeakSet(messages);
// use unreadSet to see whether a message is unread
alert(unreadSet.has(messages[1])); // true
// remove it from the set after reading
unreadSet.delete(messages[1]); // true
// and when we shift our messages history, the set is cleaned up automatically
messages.shift();
// no need to clean unreadSet, it now has 2 items
// (though technically we don't know for sure when the JS engine clears it)

weakMap 只有以下方法:

  • weakMap.get(key)

  • weakMap.set(key, value)

  • weakMap.delete(key)

  • weakMap.has(key)

weakSet

  • It is analogous to Set, but we may only add objects to WeakSet (not primitives).

  • An object exists in the set while it is reachable from somewhere else.

  • Like Set, it supports add, has and delete, but not size, keys() and no iterations.

Last updated