主な違いは、オブジェクトは文字列とシンボル キーのみをサポートするのに対し、マップは多かれ少なかれすべてのキー タイプをサポートすることです。
私がそうすればobj[123] = true
、私はよりもむしろObject.keys(obj)
得るでしょう。Map はキーのタイプを保持し、それを返します。これは素晴らしいことです。マップでは、オブジェクトをキーとして使用することもできます。伝統的にこれを行うには、オブジェクトをハッシュするための何らかの一意の識別子をオブジェクトに与える必要がありました (標準の一部として JavaScript のようなものを見たことはないと思います)。マップは順序の保存も保証するので、全体的に保存に適しています。["123"]
[123]
[123]
getObjectId
実際のマップとオブジェクトの間には、いくつかの長所と短所があります。オブジェクトは、JavaScript のコアに非常に緊密に統合されているため、長所と短所の両方を獲得しています。
当面の利点は、要素へのアクセスを容易にするオブジェクトの構文サポートがあることです。また、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 クラスがありますが、パフォーマンスとメモリの使用量は厳しく、実際、Map for Objects のように見える独自のプロキシを作成すると、現在 Proxy よりも優れたパフォーマンスを発揮します。
Maps の大きな欠点は、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 に比べてパフォーマンスが大幅に低下する可能性があります。
他のスクリプト言語では、Map、Object、および Array に明示的な非スカラー型があるため、多くの場合、このような問題はありません。Web 開発は、PHP がプロパティに A/M を使用して配列/マップをオブジェクトとマージし、JavaScript が M/O を拡張する配列とマップ/オブジェクトをマージするなどの処理を行わなければならない非スカラー型の場合は苦痛です。複雑な型をマージすることは、高レベルのスクリプト言語にとって天災です。
これまでのところ、これらは主に実装に関する問題ですが、基本的な操作のパフォーマンスも重要です。性能もエンジンや使い方次第なので複雑です。間違いを排除することはできないので、テストを一粒の塩で受けてください(急がなければなりません)。また、独自のテストを実行して確認する必要があります. 非常に大きなオブジェクト/マップに対する 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に関して同じように動作しましたが、 Map は 5 倍のメモリを使用しました。このテストでは 4 つのキーを使用しましたが、前回のテストでは 1 つのキーのみを設定したため、メモリ オーバーヘッドの削減が説明されます。このテストを数回実行しましたが、マップ/オブジェクトは、全体的な速度の点で、Chrome 全体で多かれ少なかれ首と首を争っています。小さなオブジェクト用の Firefox では、マップ全体よりも明らかにパフォーマンスが優れています。
もちろん、これには大きく異なる可能性のある個々のオプションは含まれていません。これらの数値を使用してマイクロ最適化することはお勧めしません。ここから得られることは、経験則として、非常に大きなキー値ストアにはマップを、小さなキー値ストアにはオブジェクトをより強く考慮するということです。
それを超えて、これら2つの最良の戦略は、それを実装し、最初に機能させることです. プロファイリングを行うときは、オブジェクト キーの削除のケースで見られるように、エンジンの癖が原因で、見たときには遅いとは思わないことが時々信じられないほど遅くなる可能性があることに留意することが重要です。