JavaScriptのマップとオブジェクト


290

私はchromestatus.comを見つけたところ、1日の数時間を失った後、次の機能エントリが見つかりました。

マップ:マップオブジェクトは単純なキー/値マップです。

それは私を混乱させました。通常のJavaScriptオブジェクトは辞書ですが、辞書とどうMap違うのですか?概念的には同じです( マップと辞書の違いは何ですか?

ドキュメントのchromestatusリファレンスは、次のいずれにも役立ちません。

マップオブジェクトは、キーと値のペアのコレクションであり、キーと値の両方が任意のECMAScript言語値である可能性があります。個別のキー値は、マップのコレクション内の1つのキー/値ペアでのみ発生する可能性があります。マップの作成時に選択された比較アルゴリズムを使用して識別される個別のキー値。

Mapオブジェクトは、その要素を挿入順に反復できます。マップオブジェクトは、ハッシュテーブルまたは他のメカニズムを使用して実装する必要があります。これにより、平均して、コレクション内の要素の数に対して線形ではないアクセス時間が提供されます。このMapオブジェクト仕様で使用されるデータ構造は、Mapオブジェクトの必要な監視可能なセマンティクスを記述することのみを目的としています。実行可能な実装モデルになることは意図されていません。

…私にはまだオブジェクトのように聞こえるので、明らかに何かを逃しました。

JavaScriptが(十分にサポートされている)Mapオブジェクトを取得するのはなぜですか?それは何をするためのものか?


回答:


286

mozillaによると:

Mapオブジェクトは、その要素を挿入順に反復できます。for..ofループは、反復ごとに[key、value]の配列を返します。

そして

オブジェクトは、キーを値に設定し、それらの値を取得し、キーを削除し、何かがキーに格納されているかどうかを検出できるという点でマップに似ています。このため、オブジェクトはこれまでマップとして使用されてきました。ただし、オブジェクトとマップの間には、マップの使用を改善する重要な違いがあります。

オブジェクトにはプロトタイプがあるため、マップにはデフォルトのキーがあります。ただし、これはmap = Object.create(null)を使用してバイパスできます。オブジェクトのキーは文字列であり、マップの任意の値にすることができます。オブジェクトのサイズを手動で追跡する必要がある一方で、マップのサイズを簡単に取得できます。

実行時までキーが不明な場合、およびすべてのキーが同じタイプで、すべての値が同じタイプの場合は、オブジェクトのマップを使用します。

個々の要素を操作するロジックがある場合は、オブジェクトを使用します。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

順序どおりの反復可能性は、すべてのブラウザーで同じパフォーマンスを保証するという理由もあって、開発者から長い間求められてきた機能です。だから私にとってそれは大きな問題です。

このmyMap.has(key)方法は特に便利で、myMap.sizeプロパティも便利です。


13
おそらく、欠点は、挿入順序を維持するために、マップが(ただし、同じ桁の範囲内で)より多くのメモリを必要とすることです。
John Kurlak、2014年

4
マップには、ここで説明した順序以外にも他の機能があります(任意のオブジェクトをキーとして使用、キーとプロップの分離など)。ただし、FWIWはプレーンオブジェクトプロパティの反復順序がES2015で定義されている場合があります。stackoverflow.com/a/32149345を参照してください。
JMM 2015年

2
「オブジェクトにはプロトタイプがあるので、マップにはデフォルトのキーがありmap = Object.create(null)ます」と言ったとき、意味がわかりませんでしたただし、これはを使用してバイパスできます。デフォルトのキーとは何ですか?キーはどのように関係していObject.prototypeますか?
15

4
Chromeでの私のテストでは、マップが順序を維持するためにメモリを大幅に使用しないことが示されました。100万個のキーに対して0.1KB多いと思いますが、それは順序を維持するためのものではないと思います。ただし、その〜0.1KBは一定のオーバーヘッドのようです。代わりに1つのキーで100万のマップを作成して比較すると、オブジェクトよりもはるかに大きくなります。
jgmjgm 2016年

2
@luxonあなたはそこにオブジェクトを作成しています。ES6の仕様では、シンボルnewと一緒に演算子を使用する必要があります。Mapつまりnew Map、マップオブジェクトを作成するためです。var a = {}の省略形です(これは以下と同等です)var a = Object.create(Object.prototype)
dudewad 2017

104

主な違いは、オブジェクトは文字列キーのみをサポートするのに対し、マップは多かれ少なかれすべてのキータイプをサポートすることです。

私がそうすれば、私はobj[123] = trueというよりもObject.keys(obj)得るでしょう。Mapはキーのタイプを保持して返すので、すばらしいです。マップでは、オブジェクトをキーとして使用することもできます。従来、これを行うには、オブジェクトにハッシュするための何らかの一意の識別子をオブジェクトに付与する必要がありました(標準の一部としてJSのようなものを見たことがないと思います)。また、マップは順序の保存を保証するので、保存のために全体的に優れており、いくつかのソートを行う必要がなくなることもあります。["123"][123][123]getObjectId

実際のマップとオブジェクトの間には、いくつかの長所と短所があります。オブジェクトは、JavaScriptのコアに非常に緊密に統合されている長所と短所の両方を備えています。これにより、オブジェクトは、主要なサポートの違いをはるかに超えるMapと区別されます。

直接的な利点は、オブジェクトへの構文サポートがあり、要素に簡単にアクセスできることです。また、JSONによる直接サポートもあります。ハッシュとして使用する場合、プロパティのないオブジェクトを取得するのは面倒です。デフォルトでは、オブジェクトをハッシュテーブルとして使用する場合、オブジェクトは汚染さhasOwnPropertyれるため、プロパティにアクセスするときにそれらを呼び出す必要があります。ここでは、デフォルトでオブジェクトが汚染されている方法と、ハッシュとして使用できる汚染されていないオブジェクトを作成する方法を示しています。

({}).toString
    toString() { [native code] }
JSON.parse('{}').toString
    toString() { [native code] }
(Object.create(null)).toString
    undefined
JSON.parse('{}', (k,v) => (typeof v === 'object' && Object.setPrototypeOf(v, null) ,v)).toString
    undefined

オブジェクトの汚染は、コードをより煩わしく、遅くするだけでなく、セキュリティに影響を与える可能性があります。

オブジェクトは純粋なハッシュテーブルではありませんが、より多くのことをしようとしています。hasOwnProperty長さを簡単に取得できない(Object.keys(obj).length)などの頭痛の種があります。オブジェクトは、純粋にハッシュマップとして使用するためのものではなく、動的に拡張可能なオブジェクトとしても使用するためのものです。そのため、これらを純粋なハッシュテーブルとして使用すると、問題が発生します。

さまざまな一般的な操作の比較/リスト:

    Object:
       var o = {};
       var o = Object.create(null);
       o.key = 1;
       o.key += 10;
       for(let k in o) o[k]++;
       var sum = 0;
       for(let v of Object.values(m)) sum += v;
       if('key' in o);
       if(o.hasOwnProperty('key'));
       delete(o.key);
       Object.keys(o).length
    Map:
       var m = new Map();
       m.set('key', 1);
       m.set('key', m.get('key') + 10);
       m.foreach((k, v) => m.set(k, m.get(k) + 1));
       for(let k of m.keys()) m.set(k, m.get(k) + 1);
       var sum = 0;
       for(let v of m.values()) sum += v;
       if(m.has('key'));
       m.delete('key');
       m.size();

いくつかのオプション、アプローチ、方法論などがあり、さまざまな浮き沈みがあります(パフォーマンス、簡潔、ポータブル、拡張可能など)。オブジェクトは言語の中核であるので少し奇妙なので、オブジェクトを操作するための多くの静的メソッドがあります。

マップの利点に加えて、キータイプを保持し、オブジェクトなどをキーとしてサポートできるという利点に加えて、オブジェクトが持つ多くの副作用から隔離されます。Mapは純粋なハッシュであり、同時にオブジェクトになろうとすることに混乱はありません。マップは、プロキシ機能を使用して簡単に拡張することもできます。オブジェクトには現在Proxyクラスがありますが、パフォーマンスとメモリ使用量は厳しく、実際には、オブジェクトのマップがProxyよりもパフォーマンスが優れているように見える独自のプロキシを作成しています。

マップの大きな欠点は、JSONで直接サポートされていないことです。解析は可能ですが、いくつかのハングアップがあります。

JSON.parse(str, (k,v) => {
    if(typeof v !== 'object') return v;
    let m = new Map();
    for(k in v) m.set(k, v[k]);
    return m;
});

上記は深刻なパフォーマンスヒットをもたらし、文字列キーもサポートしません。JSONエンコーディングはさらに難しく、問題があります(これは多くのアプローチの1つです):

// An alternative to this it to use a replacer in JSON.stringify.
Map.prototype.toJSON = function() {
    return JSON.stringify({
        keys: Array.from(this.keys()),
        values: Array.from(this.values())
    });
};

これは、純粋にマップを使用している場合はそれほど悪くはありませんが、タイプを混合したり、非スカラー値をキーとして使用したりすると問題が発生します(JSONは、IEの循環オブジェクト参照のように、この種の問題に完全ではありません)。私はそれをテストしていませんが、可能性としては、stringifyに比べてパフォーマンスが大幅に低下する可能性があります。

他のスクリプト言語は、マップ、オブジェクト、配列の明示的な非スカラー型を持っているため、多くの場合、そのような問題はありません。PHPがプロパティのA / Mを使用して配列/マップをオブジェクトにマージしたり、JSがマップ/オブジェクトを配列拡張M / Oにマージしたりするなど、非スカラー型のWeb開発はしばしば苦痛です。複雑な型をマージすることは、高水準スクリプト言語の悪魔の悩みの種です。

これまでのところ、これらは主に実装に関する問題ですが、基本的な操作のパフォーマンスも重要です。エンジンと使用方法に依存するため、パフォーマンスも複雑です。私は間違いを排除することができないので、塩の粒で私のテストを受けてください(私はこれを急ぐ必要があります)。また、独自のテストを実行して、非常に具体的な単純なシナリオのみを調べ、大まかな指標のみを示すことを確認する必要があります。非常に大きいオブジェクト/マップに対するChromeのテストによると、オブジェクトのパフォーマンスは、O(1)ではなく、明らかにキーの数に比例する削除のために、より悪くなります。

Object Set Took: 146
Object Update Took: 7
Object Get Took: 4
Object Delete Took: 8239
Map Set Took: 80
Map Update Took: 51
Map Get Took: 40
Map Delete Took: 2

Chromeの取得と更新には明らかに大きな利点がありますが、削除のパフォーマンスは恐ろしいものです。この場合、マップはわずかに多くのメモリを使用します(オーバーヘッド)が、数百万のキーでテストされているオブジェクト/マップは1つだけなので、マップのオーバーヘッドの影響はうまく表現されません。メモリ管理を使用すると、プロファイルを正しく読み取っている場合、オブジェクトは以前にも解放されているように見えます。これは、オブジェクトを優先する利点の1つかもしれません。

この特定のベンチマークのFirefoxでは、これは別の話です。

Object Set Took: 435
Object Update Took: 126
Object Get Took: 50
Object Delete Took: 2
Map Set Took: 63
Map Update Took: 59
Map Get Took: 33
Map Delete Took: 1

この特定のベンチマークでは、Firefoxでオブジェクトから削除しても問題は発生しませんが、他のベンチマークでは、特にChromeのように多くのキーがある場合に問題が発生することをすぐに指摘しておきます。大きなコレクションの場合、マップはFirefoxで明らかに優れています。

しかし、これで話は終わりではありません。多くの小さなオブジェクトやマップはどうですか?私はこれの簡単なベンチマークを行いましたが、上記の操作で少数のキーを使用して最良のパフォーマンスを発揮する完全なもの(設定/取得)ではありません。このテストは、メモリと初期化に関するものです。

Map Create: 69    // new Map
Object Create: 34 // {}

繰り返しになりますが、これらの数値は異なりますが、基本的にはObjectが優れています。マップ上のオブジェクトのリードは極端な場合があります(約10倍優れています)が、平均で約2〜3倍優れています。極端なパフォーマンスの急上昇は両方の方法で機能するようです。私はこれをChromeと作成でテストしただけで、メモリ使用量とオーバーヘッドのプロファイルを作成しました。Chromeでは、1つのキーを持つマップが1つのキーを持つオブジェクトの約30倍のメモリを使用しているように見えるので、私はかなり驚いています。

上記のすべての操作(4つのキー)で多くの小さなオブジェクトをテストするには:

Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139

メモリ割り当てに関しては、これらは解放/ GCに関して同じように動作しましたが、マップは5倍のメモリを使用しました。このテストでは4つのキーを使用しましたが、前回のテストと同様に、キーを1つだけ設定したので、これはメモリオーバーヘッドの削減を説明します。私はこのテストを数回実行しましたが、マップ/オブジェクトは、全体的な速度の点で、Chromeにとって全体的に多かれ少なかれ首位を占めています。小さなオブジェクト向けのFirefoxでは、マップ全体に比べて明らかにパフォーマンスが優れています。

もちろんこれには、大きく異なる可能性のある個々のオプションは含まれません。これらの数値を使用してマイクロ最適化を行うことはお勧めしません。経験則として、非常に大きなキー値ストアの場合はマップを、小さなキー値ストアの場合はオブジェクトを検討することをお勧めします。

それを超えて、これら2つを使用する最良の戦略は、それを実装して最初に機能させることです。プロファイリングするときは、オブジェクトキーの削除のケースで見られるようなエンジンの癖のために、それらを見るときに遅くなるとは思えないことが時々信じられないほど遅くなる可能性があることを覚えておくことが重要です。


シリアライザビリティの欠如は、多くの開発者にとって本当の痛みです。ES6マップをlocalstorage(または他の場所)に永続化する方法の賛成票を見てくださいそしてES6マップをJSON.stringifyする方法は?
フランクリンユー

数はミリ秒、バイト、またはオブジェクトの合計ですか?
StefansArya

そのようにmsを取りました(かかった何かは、使用されたものを言うために短く、この場合、それはアップタ​​イムを使用します)。これは古いテストですが、ベンチマークコードはもうありません。おそらく今はかなり違います。たとえば、削除の問題は修正されたと思います。
jgmjgm

27

これまでの回答では、以下の点については言及されていないと思いますので、言及する価値はあると思います。


マップは大きくなる可能性があります

クロームで、私は得ることができます16.7で百万のキー/値のペアをMap11.1通常のオブジェクトと万人。とのペアはほぼ正確に50%増えMapます。どちらもクラッシュする前に約2GBのメモリを消費するので、Chrome によるメモリ制限に関係しているのではないかと思います(編集Mapsはい、2 つ入力すると、クラッシュする前にそれぞれ830万ペアしか取得できません)。このコードを使用して、自分でテストできます(明らかに、同時に実行するのではなく、個別に実行します)。

var m = new Map();
var i = 0;
while(1) {
    m.set(((10**30)*Math.random()).toString(36), ((10**30)*Math.random()).toString(36));
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}
// versus:
var m = {};
var i = 0;
while(1) {
    m[((10**30)*Math.random()).toString(36)] = ((10**30)*Math.random()).toString(36);
    i++;
    if(i%1000 === 0) { console.log(i/1000,"thousand") }
}

オブジェクトにはすでにいくつかのプロパティ/キーがあります

これは前に私をつまずきました。定期的なオブジェクトが持っているtoStringconstructorvalueOfhasOwnPropertyisPrototypeOfおよびその他の既存のプロパティの束。これはほとんどのユースケースでは大きな問題ではないかもしれませんが、以前は問題を引き起こしていました。

マップは遅くなる可能性があります:

起因して.get、関数呼び出しのオーバーヘッドと内部の最適化の欠如、地図はかなり遅くなることがいくつかのタスクのための、昔ながらのJavaScriptオブジェクトより。


1
あなたの意見では、意味論はここでのパフォーマンスを上回っていますか?辞書が必要な場合、マップは完璧に聞こえますが、遅い検索を受け入れるのは困難です。辞書のポイント全体を高速に検索しませんか?
user2954463 2017

3
あなたは1100万キー/値のペアとしている罰金など、既存のキーを気にしないならば、私は間違いなく、昔ながらのオブジェクトと一緒に行きたいtoStringconstructorなど(つまり、あなたの鍵は、彼らと衝突する可能性は極めて低いです)。彼らとの仕事にしているが容易-である例えば増加obj[i] = (obj[i] || 0) + 1し、一方、Mapそれはだmap.set(i, (map.get(i) || 0) + 1)物事が不乱雑得ることができる方法はまだそれほど悪くされていないが、それはただのショー。マップには確かにユースケースがありますが、多くの場合、プレーンオブジェクトで十分です。

あなたはデフォルトを取り除くことに注意してくださいtoStringconstructor書き込むことによって、(など)オブジェクトのプロパティobj = Object.create(null)の代わりにobj = {}

17

JavaScriptは動的に型指定されるため、オブジェクトは辞書のように動作し、いつでもオブジェクトのプロパティを追加または削除できます。

ただし、次のMap()理由により、新しい機能ははるかに優れています。

  • それは提供してgetsethas、およびdelete方法。
  • 文字列だけでなく、キーの任意のタイプを受け入れます。
  • 簡単にfor-of使用できるイテレータを提供し、結果の順序を維持します。
  • 反復またはコピー中にプロトタイプおよびその他のプロパティが表示されるエッジケースはありません。
  • 何百万ものアイテムをサポートしています。
  • 非常に高速であり、JavaScriptエンジンが向上するにつれて高速化し続けます。

99%の時間だけを使用する必要がありますMap()。ただし、文字列ベースのキーのみを使用していて、最大の読み取りパフォーマンスが必要な場合は、オブジェクトの方が適しています。

詳細は、(ほとんどすべての)JavaScriptエンジンがオブジェクトをバックグラウンドでC ++クラスにコンパイルすることです。これらのタイプは「アウトライン」によってキャッシュされ再利用されるため、まったく同じプロパティを持つ新しいオブジェクトを作成すると、エンジンは既存のバックグラウンドクラスを再利用します。これらのクラスのプロパティへのアクセスパスは非常に最適化されており、の検索よりもはるかに高速ですMap()

プロパティを追加または削除すると、キャッシュされたバッキングクラスが再コンパイルされます。そのため、多くのキーの追加と削除があるオブジェクトをディクショナリとして使用すると非常に遅くなりますが、オブジェクトを変更せずに既存のキーの読み取りと割り当てを行うと非常に高速になります。

そのため、文字列キーを使用した1回の読み取りが多いワークロードがある場合はobject、専用の高性能辞書としてを使用しますが、それ以外の場合はを使用しMap()ます。


オブジェクトget set has deleteも機能などを提供しますが、それはそれほどエレガントではありません(悪くはありません)。どのようMapに反復するのが使いやすいですか?同意できるかわかりません。
アンドリュー

@Andrew私はメソッドについて取り上げていますが、機能は使用しているものと結果によっても異なります。プロトタイププロパティとネイティブプロパティがループに表示されず、同じ順序を維持する通常のJSイテレータを使用するため、反復が簡単です。
マニガンダム

11

他の答えに加えて、マップはオブジェクトよりも扱いにくく、冗長であることがわかりました。

obj[key] += x
// vs.
map.set(map.get(key) + x)

これは重要です。コードが短いほど、読み取りが速くなり、直接的に表現力が増し、プログラマーの頭にしっかりと留まるためです。

別の側面:set()は値ではなくマップを返すため、割り当てを連鎖させることは不可能です。

foo = obj[key] = x;  // Does what you expect
foo = map.set(key, x)  // foo !== x; foo === map

マップのデバッグもより困難です。以下では、実際にマップにあるキーを確認することはできません。それを行うには、コードを記述する必要があります。

マップイテレーターの評価

オブジェクトは任意のIDEで評価できます。

オブジェクトを評価するWebStorm


4
これらすべてを考慮すると、マップは時期尚早の最適化のようです。
PRMan

10

概要:

  • Object:データがキーと値のペアとして格納されるデータ構造。オブジェクトでは、キーは数値、文字列、または記号でなければなりません。値は、他のオブジェクト、関数など、何でもかまいません。オブジェクトは、順序付けされていないデータ構造です。つまり、キーと値のペアの挿入シーケンスは記憶されません。
  • ES6 Map:データがキーと値のペアとして格納されるデータ構造。これで一意のキー値にマップ。キーと値はどちらも任意のデータ型にすることができます。マップは反復可能なデータ構造です。これは、挿入のシーケンスが記憶され、for..ofループなどの要素にアクセスできることを意味します

主な違い:

  • A Mapは順序付けられて反復可能ですが、オブジェクトは順序付けられておらず反復可能ではありません

  • 任意のタイプのデータをMapキーとして配置できますが、オブジェクトはキーとして数値、文字列、または記号のみを持つことができます。

  • Mapから継承しMap.prototypeます。これにより、あらゆる種類のユーティリティ関数とプロパティが提供され、Mapオブジェクトの操作が非常に簡単になります。

例:

オブジェクト:

let obj = {};

// adding properties to a object
obj.prop1 = 1;
obj[2]    =  2;

// getting nr of properties of the object
console.log(Object.keys(obj).length)

// deleting a property
delete obj[2]

console.log(obj)

地図:

const myMap = new Map();

const keyString = 'a string',
    keyObj = {},
    keyFunc = function() {};

// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');

console.log(myMap.size); // 3

// getting the values
console.log(myMap.get(keyString));    // "value associated with 'a string'"
console.log(myMap.get(keyObj));       // "value associated with keyObj"
console.log(myMap.get(keyFunc));      // "value associated with keyFunc"

console.log(myMap.get('a string'));   // "value associated with 'a string'"
                         // because keyString === 'a string'
console.log(myMap.get({}));           // undefined, because keyObj !== {}
console.log(myMap.get(function() {})) // undefined, because keyFunc !== function () {}

出典:MDN


4

明確に定義された順序で反復可能であること、およびキーとして任意の値を使用-0できること(を除く)に加えて、マップは次の理由で役立ちます。

  • 仕様では、平均してマップ操作がサブリニアになるように強制しています。

    オブジェクトの愚かな実装では、ハッシュテーブルなどが使用されるため、プロパティの検索はおそらく平均して一定になります。そうすれば、オブジェクトはマップよりもさらに高速になる可能性があります。しかし、それは仕様で要求されていません。

  • オブジェクトは予期しない厄介な動作をする可能性があります。

    たとえばfoo、新しく作成されたオブジェクトにプロパティを設定しておらずobjobj.fooundefinedを返すと想定しているとします。ただしfoo、から継承された組み込みプロパティである可能性がありますObject.prototype。またはobj.foo、割り当てを使用して作成しようとしましたがObject.prototype、値を保存する代わりに実行時にセッターを実行しました。

    マップはこのようなことを防ぎます。まあ、いくつかのスクリプトがで混乱しない限りMap.prototype。そしてObject.create(null)、あまりにも動作しますが、その後は構文初期化子単純なオブジェクトを失うことになります。


4

プレーンJavaScriptオブジェクトの代わりにマップを使用するのはいつですか?

プレーンなJavaScriptオブジェクト{key: 'value'}は構造化データを保持します。ただし、プレーンなJSオブジェクトには制限があります。

  1. オブジェクトのキーとして使用できるのは文字列と記号のみです。オブジェクトのキーとして数値などの他のことを使用すると、それらのキーにアクセスしているときに、それらのキーが文字列に変換され、暗黙的に型の一貫性が失われます。const names = {1: 'one'、2: 'two'}; Object.keys(names); // ['1'、 '2']

  2. JS識別子をオブジェクトのキー名(toString、コンストラクタなど)として書き込むことにより、プロトタイプから継承されたプロパティを誤って上書きする可能性があります。

  3. 別のオブジェクトをオブジェクトのキーとして使用することはできないため、そのオブジェクトを別のオブジェクトのキーとして書き込むことによってそのオブジェクトに追加情報を書き込むことはできず、その別のオブジェクトの値には追加情報が含まれます

  4. オブジェクトはイテレータではありません

  5. オブジェクトのサイズを直接決定することはできません

オブジェクトのこれらの制限はマップによって解決されますが、マップを置換ではなくオブジェクトの補足と見なす必要があります。基本的に、Mapは配列の配列ですが、配列の配列を引数として新しいキーワードでMapオブジェクトに渡す必要があります。そうでない場合、配列の配列に対してのみ、Mapの便利なプロパティとメソッドを使用できません。また、配列の配列内のキーと値のペアまたはマップはカンマでのみ区切る必要があり、プレーンオブジェクトのようにコロンは使用しないでください。

マップとオブジェクトのどちらを使用するかを決定するための3つのヒント:

  1. 実行時までキーが不明な場合は、オブジェクトのマップを使用します。ユーザー入力によって形成されたキーは、オブジェクトの継承されたプロパティをそれらのキーが上書きする場合、オブジェクトを使用するコードを無意識のうちに破壊する可能性があるため、マップはより安全です。すべてのキーが同じタイプであり、すべてのマップが同じタイプである場合にもマップを使用します。

  2. プリミティブ値をキーとして保存する必要がある場合は、マップを使用します。

  3. 個々の要素を操作する必要がある場合は、オブジェクトを使用します。

マップを使用する利点は次のとおりです。

1.マップは任意のキータイプを受け入れ、キーのタイプを保持します。

オブジェクトのキーが文字列またはシンボルでない場合、JSはそれを暗黙的に文字列に変換します。逆に、Mapは任意のタイプのキー(文字列、数値、ブール値、記号など)を受け入れ、Mapは元のキータイプを保持します。ここでは、マップ内のキーとして数値を使用しますが、数値のままです。

const numbersMap= new Map();

numbersMap.set(1, 'one');

numbersMap.set(2, 'two');

const keysOfMap= [...numbersMap.keys()];

console.log(keysOfMap);                        // [1, 2]

マップ内では、オブジェクト全体をキーとして使用することもできます。無駄のないオブジェクトを操作できるようにオブジェクトに関する情報を保存したいので、オブジェクト自体の内部にこのデータを添付せずに、オブジェクト関連のデータを保存したい場合があります。それらの場合、オブジェクトをキーとして、オブジェクトの関連データを値として作成できるように、マップを使用する必要があります。

const foo= {name: foo};

const bar= {name: bar};

const kindOfMap= [[foo, 'Foo related data'], [bar, 'Bar related data']];

ただし、この方法の欠点は、配列全体をループして目的の値を取得する必要があるため、キーで値にアクセスする複雑さです。

function getBy Key(kindOfMap, key) {
    for (const [k, v]  of kindOfMap) {
        if(key === k) {
            return v;
        }
    }
    return undefined;
}

getByKey(kindOfMap, foo);            // 'Foo related data'

適切なマップを使用することで、値に直接アクセスできないというこの問題を解決できます。

const foo= {name: 'foo'};

const bar= {name: 'bar'};

const myMap= new Map();

myMap.set(foo, 'Foo related data');
myMap.set(bar, 'Bar related data');

console.log(myMap.get(foo));            // 'Foo related data'

WeakMapを使用してこれを行うこともできますが、const myMap = new WeakMap()と記述するだけです。MapとWeakMapの違いは、WeakMapはキー(ここではオブジェクト)のガベージコレクションを可能にするため、メモリリークを防ぎ、WeakMapはオブジェクトとしてのみキーを受け入れ、WeakMapはメソッドのセットを減らしました。

2.マップにはキー名に対する制限はありません。

プレーンなJSオブジェクトの場合、プロトタイプから継承したプロパティを誤って上書きする可能性があり、危険な場合があります。ここでは、actorオブジェクトのtoString()プロパティを上書きします。

const actor= {
    name: 'Harrison Ford',
    toString: 'Actor: Harrison Ford'
};

次に、fn isPlainObject()を定義して、指定された引数がプレーンオブジェクトであるかどうかを確認します。このfnはtoString()メソッドを使用してそれをチェックします。

function isPlainObject(value) {
    return value.toString() === '[object Object]';
}

isPlainObject(actor);        // TypeError : value.toString is not a function

// this is because inside actor object toString property is a string instead of inherited method from prototype

Mapはキー名に制限はなく、toString、コンストラクタなどのキー名を使用できます。ここで、actorMapオブジェクトにはtoStringという名前のプロパティがありますが、actorMapオブジェクトのプロトタイプから継承されたメソッドtoString()は完全に機能します。

const actorMap= new Map();

actorMap.set('name', 'Harrison Ford');

actorMap.set('toString', 'Actor: Harrison Ford');

function isMap(value) {
  return value.toString() === '[object Map]';
}

console.log(isMap(actorMap));     // true

ユーザー入力がキーを作成する状況がある場合、それらのキーをプレーンオブジェクトではなくマップ内に取得する必要があります。これは、ユーザーがtoString、コンストラクタなどのカスタムフィールド名を選択する可能性があるためです。プレーンオブジェクトのそのようなキー名は、後でこのオブジェクトを使用するコードを破壊する可能性があります。したがって、正しい解決策は、ユーザーインターフェイスの状態をマップにバインドすることです。マップを解除する方法はありません。

const userCustomFieldsMap= new Map([['color', 'blue'], ['size', 'medium'], ['toString', 'A blue box']]);

3.マップは反復可能です:

プレーンオブジェクトのプロパティを反復処理するには、Object.entries()またはObject.keys()が必要です。Object.entries(plainObject)は、オブジェクトから抽出されたキーと値のペアの配列を返します。これらのキーと値を分解して、通常のキーと値の出力を取得できます。

const colorHex= {
  'white': '#FFFFFF',
  'black': '#000000'
}

for(const [color, hex] of Object.entries(colorHex)) {
  console.log(color, hex);
}
//
'white' '#FFFFFF'   
'black' '#000000'

マップは反復可能であるため、マップを反復してキーを分解するためにentries()メソッドを必要としないので、値の配列はマップ内で直接実行できます。各要素は、コンマで区切られたキーと値のペアの配列として存在します。 。

const colorHexMap= new Map();
colorHexMap.set('white', '#FFFFFF');
colorHexMap.set('black', '#000000');


for(const [color, hex] of colorHexMap) {
  console.log(color, hex);
}
//'white' '#FFFFFF'   'black' '#000000'

また、map.keys()はキーのイテレータを返し、map.values()は値のイテレータを返します。

4.地図のサイズを簡単に知ることができます

プレーンオブジェクトのプロパティの数を直接決定することはできません。オブジェクトのキーを含む配列を返すObject.keys()のようなヘルパーfnが必要です。次に、lengthプロパティを使用して、キーの数またはプレーンオブジェクトのサイズを取得できます。

const exams= {'John Rambo': '80%', 'James Bond': '60%'};

const sizeOfObj= Object.keys(exams).length;

console.log(sizeOfObj);       // 2

ただし、マップの場合、map.sizeプロパティを使用してマップのサイズに直接アクセスできます。

const examsMap= new Map([['John Rambo', '80%'], ['James Bond', '60%']]);

console.log(examsMap.size);

1

これらの2つのヒントは、マップとオブジェクトのどちらを使用するかを決定するのに役立ちます。

  • 実行時までキーが不明な場合、およびすべてのキーが同じタイプで、すべての値が同じタイプの場合は、オブジェクトのマップを使用します。

  • オブジェクトは各キーを数値、ブール値、またはその他のプリミティブ値のいずれかの文字列として扱うため、プリミティブ値をキーとして格納する必要がある場合は、マップを使用してください。

  • 個々の要素を操作するロジックがある場合は、オブジェクトを使用します。

ソース:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections#Object_and_Map_compared


2
これらのヒントは、これらの基準によって物事を分割することが容易ではない傾向があるため、特に役立つとは思われません。キー/値が同じタイプである場合にマップがメリットである理由の最初の1つではわかりません。クラス/構造体のようなオブジェクト、コレクションのようなマップを使用しようとしているように聞こえます。2番目のものは、要点に達していないように書かれています。これは、文字列の同等の型( "1"と1)が混在している場合、またはキーの型を保持する必要がある場合にマップを使用することを意味します。最後は最初と同じだと思います。オブジェクトが何であるかを知らないので、あいまいです。
jgmjgm

0

これは私が覚えている短い方法です:KOI

  1. キー。オブジェクトキーは文字列または記号です。マップキーには、数字(1と "1"は異なる)、オブジェクトNaNなど===を指定するNaN !== NaNこともできます。1つの例外を除いて、キーを区別するために使用されますがNaN、キーとして使用できます。
  2. 注文。挿入順序は記憶されます。だから、[...map]または[...map.keys()]特定の順序があります。
  3. インターフェース。:オブジェクトobj[key]またはobj.a(いくつかの言語では、[][]=本当にインターフェイスの一部です)。地図は持っているget()set()has()delete()あなたが使用できることなど注意map[123]が、プレーンなJSオブジェクトとしてそれを使用しています。

0

Mozillaによると

JavaScriptでのオブジェクトとマップの簡単な例。

オブジェクト -マップと同じ概念に従います。つまり、データの格納にキーと値のペアを使用します。しかし、特定の状況ではマップのパフォーマンスが向上するわずかな違いがあります。

マップ-は、データをペアの形で格納するのに役立つデータ構造です。ペアは、一意のキーと、キーにマップされた値で構成されます。重複を防ぐのに役立ちます。

主な違い

  • Mapはオブジェクトのインスタンスですが、その逆は当てはまりません。

var map = new Map();
var obj = new Object(); 
console.log(obj instanceof Map);   // false
console.log(map instanceof Object);  // true

  • Objectでは、キーフィールドのデータ型は整数、文字列、および記号に制限されています。一方、マップでは、キーフィールドは任意のデータ型(整数、配列、オブジェクト)にすることができます

var map = new Map();//Empty 
map.set(1,'1');
map.set('one', 1);
map.set('{}', {name:'Hello world'});
map.set(12.3, 12.3)
map.set([12],[12345])

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

  • マップでは、要素の元の順序が保持されます。これはオブジェクトの場合には当てはまりません。

let obj ={
  1:'1',
  'one':1,
  '{}': {name:'Hello world'},
  12.3:12.3,
  [12]:[100]
}
console.log(obj)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.