preface

In everyday JavaScript projects, the most common data structure we use is key-value pairs in various forms. In JavaScript, in addition to the basic Object format, ES6’s new Map format is also key-value pair format. Their usage is very similar in many cases. I don’t know if there is anyone like me who has struggled to choose which to use? In my latest project, I had the trouble of comparing which one to use.

This article will discuss the differences between Object and Map, and compare Object and Map from multiple perspectives:

  • Differences in usage: In some cases the usage can be quite different
  • Syntax difference: create, add, delete, check and change the syntax difference
  • Performance differences: speed and memory footprint

Hopefully, after reading this article, you’ll be able to make more appropriate choices for future projects.

Use contrast

  1. For Object, the key can only be a string, a number, or a Symbol. In the case of a Map, it can be any type. (Including Date, Map, or custom objects)

  2. Elements in the Map retain the order in which they were inserted; Objects are not inserted in exactly the same order, but sorted according to the following rules:

    • Non-negative integers are listed first, in ascending order
    • And then all the strings, negative integers, floating point numbers are listed in the order in which they were inserted
    • It’s listed at the endSymbol.SymbolIt’s also sorted by insertion order
  3. Reading the Map’s length is easy by calling its.size() method; Reading the length of Object requires additional calculation: object.keys (obj).length

  4. Maps are iterable, so key-value pairs can be iterated through a for of loop or.foreach () method; By default, regular Object key-value pairs are non-iterable and can only be accessed through a for in loop (or by using Object.keys(o), Object.values(o), object.entries (o) to get keys or values) in the order mentioned above.

    const o = {};
    const m = new Map(a); o[Symbol.iterator] ! = =undefined; // false
    m[Symbol.iterator] ! = =undefined; // true
    Copy the code
  5. When a key is added to a Map, it does not overwrite the key on its prototype; When a new key is added to Object, it is possible to overwrite the key on its prototype:

    Object.prototype.x = 1;
    const o = {x:2};
    const m = new Map([[x,2]]);
    o.x; // 2, x = 1 is covered
    m.x; // 1, x = 1 will not be covered
    Copy the code
  6. JSON supports Object but not Map by default. To transfer a Map through JSON, use the.tojson () method and pass in a restore function in json.parse () to restore it.

    JSON serialization and parsing: JSON serialization and parsing

    const o = {x:1};
    const m = new Map([['x'.1]]);
    const o2 = JSON.parse(JSON.stringify(o)); // {x:1}
    const m2 = JSON.parse(JSON.stringify(m)) / / {}
    Copy the code

The syntactic comparison

The difference at creation time

Obejct

const o = {}; // Object literals
const o = new Object(a);// Call the constructor
const o = Object.create(null); // Call the static method object.create
Copy the code

For Object, we choose Object literals 95%+ of the time, which are not only the easiest to write, but also much more efficient in terms of speed than the following function calls. The only case in which a constructor might be used is to explicitly encapsulate a primitive type; Object. Create allows you to prototype an Object.

Map

const m = new Map(a);// Call the constructor
Copy the code

Unlike Object, maps don’t have a lot of fancy ways to create them and are usually created using only their constructors.

In addition to the above methods, We can also call Object and Map using the function.prototype.apply (), function.prototype.call (), reflect.apply(), reflect.construct () methods Or the object.create () method, which will not be expanded here.

The difference when adding/reading/deleting elements

Obejct

const o = {};
// Add/modify
o.x = 1;
o['y'] = 2;
/ / read
o.x; / / 1
o['y']; / / 2
// Or use the conditional attribute access expression new in ES2020o? .x;/ / 1o? .'y']; / / 2
/ / delete
delete o.b;
Copy the code

For new elements, it seems easier to use the first method, but it has some limitations:

  • Attribute names cannot contain Spaces or punctuation marks
  • Attribute names cannot start with a number

For more on conditional property access expressions, see this: Conditional property access Expressions

Map

const m = new Map(a);// Add/modify
m.set('x'.1);
/ / read
map.get('x');
/ / delete
map.delete('b');
Copy the code

The Map method is also very convenient to use for simple additions and deletions. However, for linkage operations, the rules in Map will be slightly bloated:

const m = new Map([['x'.1]]);
// To increment x by one, we need to do this:
m.set('x', m.get('x') + 1);
m.get('x'); / / 2

const o = {x: 1};
// It is much easier to make changes on objects:
o.x++;
o.x / / 2
Copy the code

The performance comparison

Let’s discuss the performance of Objects and maps. I don’t know if you have heard that the performance of Map is better than that of Object, but I have seen it many times. Even in JS elevation 4, the performance of Map is better than that of Object. However, performance generalizations are very general, so I’m going to do some tests to compare and contrast them.

The test method

The performance tests I have done here are based on the V8 engine. The speed is measured by the performance. Now () function provided with the JS standard library, and the memory usage is checked by the Memory in the Chrome Devtool.

For speed tests, many times performance.now() will return 0 because a single operation is too fast. So I went through 10,000 cycles and judged the time difference. Since the loop itself takes up some of the time, the following test is only a rough guide.

Performance at creation time

The code used for the test is as follows:

let n,  n2 = 5;
/ / speed
while (n2--) {
  let p1 = performance.now();
  n = 10000;
  while (n--) { let o = {}; }
  let p2 = performance.now();
  n = 10000;
  while (n--) { let m = new Map(a); }let p3 = performance.now();
  console.log(`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`);
}
/ / memory
class Test {}
let test = new Test();
test.o = o;
test.m = m;
Copy the code

The first thing to compare is how objects and maps behave when created. The speed of creation is as follows:

We can see that Object creation is faster than Map creation. For memory usage, the following is true:

Our focus is on Retained Size, which represents the space it’s assigned. (that is, the size of memory freed upon deletion)

By comparison, we can see that an empty Object takes up less space than an empty Map. So Object wins this round.

Performance when adding elements

The code used for the test is as follows:

console.clear();
let n,  n2 = 5;
let o = {}, m = new Map(a);/ / speed
while (n2--) {
  let p1 = performance.now();
  n = 10000;
  while (n--) { o[Math.random()] = Math.random(); }
  let p2 = performance.now();
  n = 10000;
  while (n--) { m.set(Math.random(), Math.random()); }
  let p3 = performance.now();
  console.log(`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`);
}
/ / memory
class Test {}
let test = new Test();
test.o = o;
test.m = m;
Copy the code

The speed for new elements is as follows:

We can see that Map is faster than Object when creating new elements. For memory usage, the following is true:

By comparison, Object takes up about 78% more memory than Map for a given number of elements. I’ve also done a lot of testing and found that with enough elements, this percentage is pretty stable. So it’s more efficient to use maps when you need to do a lot of new operations and store a lot of data.

Performance when reading elements

The code used for the test is as follows:

let n;
let o = {}, m = new Map(a); n =10000;
while (n--) { o[Math.random()] = Math.random(); }
n = 10000;
while (n--) { m.set(Math.random(), Math.random()); }

let p1 = performance.now();
for (key in o) { let k = o[key]; }
let p2 = performance.now();
for ([key] of m) { let k = m.get(key); }
let p3 = performance.now();
`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`
Copy the code

The speed for reading elements is as follows:

Through comparison, we can find that Object has a slight advantage, but the overall difference is not significant.

Performance when an element is deleted

I don’t know if you’ve heard that the delete operator is poor in performance, and there are many times when the value of the delete operator is set to undefined rather than used for performance. However, v8’s recent improvements have made it much more efficient.

The code used for the test is as follows:

let n;
let o = {}, m = new Map(a); n =10000;
while (n--) { o[Math.random()] = Math.random(); }
n = 10000;
while (n--) { m.set(Math.random(), Math.random()); }

let p1 = performance.now();
for (key in o) { delete o[key]; }
let p2 = performance.now();
for ([key] of m) { m.delete(key); }
let p3 = performance.now();
`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`
Copy the code

The speed for removing elements is as follows:

We can find that the speed of Object is slightly better during the deletion operation, but the overall difference is not great.

A special case

In addition to the most basic case, there is a special case. Remember when we talked about key ordering in Object? We mentioned that the non-negative integers are listed first. V8 actually treats non-negative integers as keys differently from other types as keys. The key parts of negative integers are treated as arrays. That is, non-negative integers with some continuity are treated as fast arrays, while too sparse are treated as slow arrays.

For fast arrays, it has contiguous memory, so reads and writes are faster and use less memory. More on this: Exploring the underlying implementation of “arrays” under the JS V8 engine

When the key is a continuous non-negative integer, the performance is as follows:

We can see that not only is Object faster on average, it also takes up much less memory.

conclusion

Through comparison, we can find that Map and Object have their own strengths and we should make different choices in different situations. So I summarized what I thought was a better time to use Map and Object.

Using the Map:

  • The stored key is not a string/number/orSymbolWhen choosingMapBecause theObjectDoes not support
  • When storing large amounts of data, selectMapBecause it takes up less memory
  • Select when you need to add/remove many elementsMapBecause it’s faster
  • To preserve the insertion order, selectMapBecause theObjectIt changes the order
  • To iterate/iterate, selectMapBecause it is iterable by default, iteration is easier

The Object of use:

  • Just simple data structure when selectingObjectBecause it takes up less memory when data is low and is more efficient when new
  • Need to useJSONTo transfer files, selectObjectBecause theJSONNo default supportMap
  • Select when multiple key values need to be evaluatedObjectBecause the syntax is more concise
  • Select when you need to override keys on the prototypeObject

Although Map can be more efficient than Object in many cases, Object will always be the most basic reference type in JS, and it does more than store key-value pairs.

reference

Explore the JS V8 engine under the “array” implementation

Fast properties in V8

Shallow, Retained, and Deep Size

Slow delete of object properties in JS in V8

ES6 — Map vs Object — What and when?

JavaScript Advanced Programming (version 4)

JavaScript: The Definitive Guide (7th Edition)

This article is participating in the “Nuggets 2021 Spring Recruitment Campaign”, click to view: activity details