現代的JavaScriptおれおれアドベントカレンダー2017 – 19日目

良いか???

概要

Array#map() じゃなくて Map というビルトインコンストラクタです。

辞書 (dictionary) みたいなノリで使えます。

const map = new Map();

map.set(100, 'Number 100');
console.log(map.get(100));  // => "Number 100"

for (let [key, value] of map) {
    console.log(`[${key}] = [${value}]`);
}

使い方

new で作って名前と値の組み合わせの情報を格納します。

情報の追加と取得

set() で追加(と上書き)、 get() で取得。

const map = new Map();
const key = 100;

// set
map.set(key, { message: 'Hello World!' });

// get
const value = map.get(key);

concole.log(key, value);

set() は map オブジェクト自体を返すので、メソッドチェインみたいに書けます。

map
  .set(100, 'Hello')
  .set(101, 'World')
  .set(102, '!')

あんまり書かない方が良いと思うけど。

有無の確認

const map = new Map();

console.log(map.has(1));  // => false

map.set(1, 'one');
console.log(map.has(1));  // => true

削除

delete() でキーを指定して消します。削除できた場合は true が、もともと値を持っていなかった場合は false が帰ります。 delete 演算子と違うね。

あと clear() で全部消します。こちらは常に undefined を返す。

const map = new Map();
let deleted;

map.set(100, 'Hello World!');
map.set(101, 'Goodmorning Universe!');
map.set(102, 'How is it going man?');

console.log('Size:', map.size);  // => Size: 3
console.log('#101', map.get(101));  // => #101 Goodmorning Universe!

// ひとつ削除
deleted = map.delete(101);
console.log('Size:', map.size);  // => Size: 2
console.log('#101', map.get(101));  // => #101 undefined
console.log('Delete successfully?', deleted);  // => true

// 削除済みのものを削除
deleted = map.delete(101);
console.log('Delete successfully?', deleted);  // => false

// 全部削除
map.clear();
console.log('Size:', map.size);  // => Size: 0

キーは何でも

オブジェクトの場合は基本的に toString() で文字列に変換されるんだけど、 Map の場合は型も含めてそのままキーになります。 1 と "1" は別物扱いです。

const map = new Map();

// 数値の1と文字列の"1"
map.set(1, 'Number 1');
map.set('1', 'String 1');

console.log(map.get(1));  // => "Number 1"
console.log(map.get('1'));  // => "String 1"

各種オブジェクトをそのまま突っ込むこともできます。

繰り返す

forEach() あるいは for-of ループでぐるぐるできます。

あと size という、配列の length みたいなプロパティがあります。

const map = new Map();
let deleted;

map.set(100, 'Hello World!');
map.set(101, 'Goodmorning Universe!');
map.set(102, 'How is it going man?');

console.log('Size:', map.size);  // => Size: 3

map.forEach((value, key) => {
    console.log('forEach', key, value);
})

for (let [key, value] of map) {
    console.log('for-of', key, value);
}

forEach() 以外の、 Array.prototype 系のメソッドはないです。

キー、値をまとめて取得

keys() でキーだけ、 values() で値だけ、さらに entries() でキーと値の組のイテレータを得られます。

const map = new Map();

map.set(100, 'Hello World!');
map.set(101, 'Goodmorning Universe!');
map.set(102, 'How is it going man?');

for (let key of map.keys()) {
    console.log('key', key);
}

for (let value of map.values()) {
    console.log('value', value);
}

for (let [key, value] of map.entries()) {
    console.log('key-value', key, value);
}

[] アクセスはだめ

マップじゃなくて普通のインスタンスプロパティへのアクセスになります。

const map = new Map();

map.set(100, 'Hello World!');
map[101] = 'Goodmorning Universe!';

console.log(map.size);  // => 1
console.log(map.get(100));  // => "Hello World!"
console.log(map.get(101));  // => undefined
console.log(map['101']);  // => "Goodmorning Universe!"

オブジェクトに追加情報を持たせる

他に独自の情報を持ちたいけど元のオブジェクトに触りたくないなーというとき、そのオブジェクト自体をキーにして、追加情報を別途置いておくことができます。

const map = new Map();

const el = document.querySelector('#foo');
const someDataForTheElement = {};
map.set(el, someDataForTheElement);

でも別インスタンスになると別物っていう扱いになっちゃうので、永続的に使える識別子があればそっちの方が良さそう。

使い道

と、ここまで書いてきたけど正直これっていう使い道が思いつかない……。

オブジェクト Object (ないし {} )でも同じようなことはできます。今までずっとこっちでやってきましたね。

const map = {};
map[100] = 'A hundred';
map[101] = 'A hundred and one';

console.log(100, map[100]);  // => "A hundred"

Object.keys(map)
    .forEach((key) => {
        const value = map[key];
        console.log(key, value);
    });

反復処理とかちょっとアレだけどまあどうにかなるし、まだあんまりよくわかんない。

Object と Map の違い

  • [] の代わりに set() 、 get() を使う
  • キーに文字列以外も使える( 1 と "1" が別物扱い)
  • 反復処理 for-of できる
  • size で数を取れる

MDNには

こう載ってる。

『オブジェクトとマップの比較』より。

これは Map をいつも使えばいいということではありません。オブジェクトはまだ多くの場面で使えます。Map インスタンスはコレクションとして使う場合のみに役に立ちます。以前オブジェクトをこのように使っていたコードに Map を使うことを考えてみるべきです。オブジェクトはメンバ変数とメソッドを備えたレコードとして使われます。もしまだどちらを使えばいいかわからないなら、以下の質問に答えてみてください。

  • キーがいつも実行時までわからない、またはキーを直接調べる必要がありますか?
  • すべての値が同じ型で、交換して使用できますか?
  • 文字列でないキーが必要ですか?
  • キーと値のペアを時々、追加または削除しますか?
  • 簡単に量が変わるキーと値のペアがありますか?
  • そのコレクションをイテレートしますか?

これらはあなたが Map をコレクションとして使いたいときのサインです。もし対照的に、固定された量のキーがあり、それら個々に操作し、Mapの使い方と区別する場合、オブジェクトを使いましょう。

その他

NaN が合致する

JavaScriptの細かい話で良く出てくるやつなんですが、 NaN “Not a Number” は特殊な値で、自身と等値になりません。

const n = NaN;
console.log(n === n);  // false (wow)

が、マップのキーとして使う場合、(内部で)同じ値とみなしてくれます。

const map = new Map();

map.set(NaN, 'Not a Number');
console.log(map.get(NaN));  // => "Not a Number"
map.set(NaN, '!');
console.log(map.get(NaN));  // => "!"

もちろん NaN と文字列 "NaN" は合致しません。やったね。

オブジェクト Object のキー

上の方で「 obj[key] の key は基本的に文字列に」みたいに言ったけど、 Symbol てのも使えるようになりました。

参考

  • ECMAScript® 2017 Language Specification
    • 23.1 Map Objects
    • 23.1.3 Properties of the Map Prototype Object … 各種メソッドとプロパティ
    • 7.2.10 SameValueZero ( x, y ) … get() でキーの合致を確認する内部処理
    • 12.3.2.1 Runtime Semantics: Evaluation … obj[key] の解釈
    • 7.1.14 ToPropertyKey ( argument ) … obj[key] の key の解釈
  • Map – JavaScript | MDN
  • Symbol – JavaScript | MDN