Study notes 18 - Map

Before ECMAScript6, the implementation of "key / value" storage in JavaScript can be completed conveniently and efficiently by using Object, that is, using Object attributes as keys and using attributes to reference values.
As a new feature of ECMAScript, Map is a new collection type, which brings a real key / value storage mechanism to the language. Most of the features of Map can be realized by Object type, but there are still some subtle differences between them. Which one to use in specific practice is still worth careful screening.

1 Basic API

Use the new keyword and the Map constructor to create an empty Map:

const m = new Map();

If you want to initialize the instance while creating, you can pass an iteratable object to the Map constructor, which needs to contain an array of key / value pairs. Each key / value pair in the iteratable object is inserted into the new mapping instance in iterative order:

const m1 = new Map([
	["key1","val1"].
	["key2","val2"],
	["key3","val3"]
]);
alert(m1.size);//3


After initialization, you can use the set() method to add key / value pairs. In addition, you can use get() and has() to query. You can obtain the number of key / value pairs in the mapping through the size attribute. You can also use delete() and clear() to delete values.

const m = new Map();

alert(m.has("firstName"));//false
alert(m.get("firstName"));//undefined
alert(m.size);//0

m.set("firstName","Matt")
 .set("lastName","Frisbie");

alert(m.has("firstName");//true
alert(m.get("firstName"));//Matt
alert(m.size);//2

m.delete("firstName");//Delete only this key / value pair

alert(m.has("firstName"));//false
alert(m.has("lastName"));//true
alert(m.size);//1

m.clear();//Clear all key / value pairs in this mapping instance

alert(m.has("firstName"));//false
alert(m.gas("lastName"));//false
alert(m.size);//0

The set() method returns the mapping instance, so multiple operations can be concatenated, including initialization declaration:

const m = new Map().set("key1","val1");

m.set("key2","val2")
 .set("key3","val3");
alert(m.size);//3

Unlike Object, which can only use numeric values, strings, or symbols as keys, Map can use any JavaScript data type as a key. The internal use of SameValueZero comparison operation in Map is basically equivalent to using strict Object equality criteria to check the matching of keys.

Like strict equality, objects and other collection types used as key values in the mapping remain unchanged when their contents or properties are modified:

const m = new Map();

const objKey = {},
	  objVal = {},
	  arrKey = [],
	  arrVal = [];
	 
m.set(objKey,objVal);
m.set(arrKey,arrVal);

objKey.foo = "foo";
objVal.bar = "bar";
arrKey.push("foo");
arrVal.push("bar");

console.log(m.get(objKey));//{bar:"bar"}
console.log(m.get(arrKey));//["bar"]

The Same ValueZero comparison can also lead to unexpected conflicts:

const m = new Map();

const a = 0/"",
	  b = 0/"",
	  pz = +0,
	  nz = -0;

alert(a===b);//false
alert(pz === nz);//true

m.set(a,"foo");
m.set(pz,"bar");

alert(m.get(b));//foo
alert(m.get(nz));//bar

2 sequential iteration

A major difference from the Object type is that the Map instance maintains the insertion order of key value pairs, so you can perform iterative operations according to the insertion order. The mapping instance can provide an Iterator, which can generate an array in the form of [key,value] in insertion order. This Iterator can be obtained through the entries() method (or the Symbol.iterator attribute, which refers to entries()):

const m = new Map([
	["key1","val1"],
	["key2","val2"],
	["key3","val3"]
]);
alert(m.entries === m(Symbol.iterator);//true

for(let pair of m.entries()){
	alert(pair);
}

Because entries() is the default iterator, you can directly use the extension operation on the mapping instance to convert the mapping into an array:

const m = new Map([
	["key1","val1"],
	["key2","val2"],
	["key3","val3"]
]);

console.log([...m]);[[key1,val1],[key2,val2],[key3,val3]]

Keys and values can be modified during iterator traversal, but references inside the map cannot be modified. Of course, this does not prevent modifying the properties inside the object as keys or values, because this does not affect their identity in the map instance:

const m1 = new Map([
	["key1","val1"]
]);

//The original value of the string as the key cannot be modified
for(let key of m1.keys()){
	key = "newKey";
	alert(key);//newKey
	alert(m1.get("key1"));//val1
}

const keyObj = {id:1};

const m = new Map([
	[keyObj,"val1"]
]);

//The properties of the object as the key are modified, but the object still references the same value inside the mapping
for(let key of m.keys()){
	key.id = "newKey";
	alert(key);//{id:"newKey"}
	alert(m.get(keyObj));//val1
}
alert(keyObj);//{id:"newKey"}

3. Select Object or Map

For most Web development tasks, choosing Object or Map is just a matter of personal preference and has little impact. However, for developers who care about memory and performance, there are significant differences between objects and mappings.
1. Memory occupation
The engineering level implementations of Object and Map have obvious differences between different browsers, but the amount of memory used to store a single key / value pair will increase linearly with the number of keys. Batch addition or deletion of key / value pairs depends on the engineering implementation of this type of memory allocation by each browser. Different browsers have different conditions, but given a fixed size of memory, Map can store about 50% more key / value pairs than Object.
2. Insertion performance
Inserting new key / value pairs into Object and Map costs roughly the same, but inserting Map is generally a little faster in all browsers. For both types, the insertion speed does not increase linearly with the number of key / value pairs. If the code involves a lot of insertion work, it is obvious that Map has better performance.
3. Search speed
Unlike insertion, the performance difference of finding key / value pairs from large objects and maps is minimal. However, if only a small number of key / value pairs are included, the Object is sometimes faster. When the Object is used as an array (such as using continuous integers as attributes), the browser engine can optimize and use a more efficient layout in memory. This is impossible for Map. For both types, the lookup speed does not increase linearly with the number of key / value pairs. If the code involves a lot of search work, it may be better to choose Object in some cases.
4. Delete performance
The performance of using delete to delete Object attributes has been criticized for a long time, and it is still the case in many browsers. For this reason, there are some operations of pseudo deleting Object properties, including setting the property value to undefined or null. But many times, this is a nasty or inappropriate compromise. For most browser engines, the delete() operation of Map is faster than insertion and search. If the code involves a large number of delete operations, there is no doubt that Map should be selected.

Tags: Javascript

Posted by agnalleo on Mon, 18 Apr 2022 10:18:11 +0930