First of all, let’s welcome“Map“Tell me a little bit about yourself “Map“A map is a classic type of data structure in which data is defined as“key/value“Exists in the form of key-value pairs
Map | Object | |
---|---|---|
The default value | The default contains no values, only explicitly inserted keys | An Object has a prototype. The key name of the prototype may conflict with the key name of its own Object |
type | any | String 或 Symbol |
The length of the | The number of key-value pairs is obtained by the size attribute | The number of key-value pairs can only be calculated manually |
performance | The performance is better when key pairs are frequently added or deleted | No optimization is made for scenarios where key/value pairs are frequently added and deleted |
Basic usage of Map
Accept any type of keystroke key, be any!!
const testMap = new Map(a)
let str = 'Not studying today'. num = Awesome!. keyFunction = function () {},
keySymbol = Symbol('Web'), keyNull = null. keyUndefined = undefined. keyNaN = NaN // Add key-value pairs // Basic usage testMap.set('key'.'value') // Map(1) {"key" => "value"} testMap.set(str, 'Spicy chicken tomorrow') testMap.set(num, 'front Sneaker') testMap.set(keyFunction, 'Your function is awesome.') testMap.set(keySymbol, 'Big front end') testMap.set(keyNull, 'I'm a Null') testMap.set(keyUndefined, 'I am an undiam') testMap.set(keyNaN, 'I'm a NaN') testMap.get(function () {}) //undefined testMap.get(Symbol('Web')) //undefined // Although NaN! == NaN but it makes no difference as a Map key name testMap.get(NaN) //" I am a NaN" testMap.get(Number('NaN')) //" I am a NaN" Copy the code
Except for NaN, the get method of “Map” is obtained by comparing key names for equality (===), and returning undefined if they are not equal
Compare Map and Object
define
//Map
const map = new Map(a);map.set('key'.'value'); // Map(1) {"key" => "value"}
map.get('key'); // 'value'
//Object const someObject = {}; someObject.key = 'value'; someObject.key; // 'value' Copy the code
It’s clear here that the definition behavior is very similar, and I’m sure you haven’t figured out exactly when “Map” is a best practice, so don’t move on.
The key type
JavaScript “Object” only accepts two types of key names: String and Symbol. You can use other types of key names, but ultimately JavaScript implicitly converts them to strings
const obj = {}
// Look directly at some of the more special key names
obj[true] = 'Boolean'
obj[1] = 'Number'
obj[{'front end':'Sneaker'}] = '666'
Object.keys(obj) // ["1", "true", "[object Object]"] Copy the code
Take a look at “Map”, which accepts any type of key name and retains its key name type.
const map = new Map(a);map.set(1.'value');
map.set(true.'value');
map.set({'key': 'value'}, 'value');
for (const key of map.keys()) {
console.log(key); } / / 1 // true // {key: "value"} // In addition, Map also supports re as key names map.set(/^1[3456789]\d{9}$/.'Regular phone number') //Map(1) {/^1[3456789]\d{9}$/ =>" Copy the code
“Map” supports regular expressions as key names, which is not allowed in Object
Prototype Prototype
“Object” is different from “Map” in that it is more than superficial. The “Map” contains only the key-value pairs you define, but the “Object” Object has some built-in properties from its prototype
const newObject = {};
newObject.constructor; ƒ Object() {[native code]}
Copy the code
If you don’t do it right, it can lead to problems and bugs that you didn’t expect
const countWords = (words) = > {
const counts = { };
for (const word of words) {
counts[word] = (counts[word] || 0) + 1;
}
return counts; }; const counts = countWords(['constructor'.'creates'.'a'.'bug']); // {constructor: "function Object() { [native code] }1", creates: 1, a: 1, bug: 1} Copy the code
This example was inspired by the book Effective TypeScript [1]
The iterator
“Map” is iterable and can be iterated directly, such as forEach loops or for… of… cycle
//forEach
const map = new Map(a);map.set('key1'.'value1');
map.set('key2'.'value2');
map.set('key3'.'value3');
map.forEach((value, key) = > { console.log(key, value); }); // key1 value1 // key2 value2 // key3 value3 //for... of... for(const entry of map) { console.log(entry); } // ["key1", "value1"] // ["key2", "value2"] // ["key3", "value3"] Copy the code
But for“Object“It is not possible to iterate directly, and attempts to iterate will result in an error
const object = {
key1: 'value1'. key2: 'value2'. key3: 'value3'.};
for(const entry of object) { console.log(entry); } // Uncaught TypeError: object is not iterable Copy the code
You need an extra step to retrieve its key name, key value, or key-value pair
for(const key of Object.keys(object)) {
console.log(key);
}
// key1
// key2
// key3 for(const value of Object.values(object)) { console.log(value); } // value1 // value2 // value3 for(const entry of Object.entries(object)) { console.log(entry); } // ["key1", "value1"] // ["key2", "value2"] // ["key3", "value3"] for(const [key,value] of Object.entries(object)) { console.log(key,value); } //"key1", "value1" //"key2", "value2" //"key3", "value3" Copy the code
You can also use for… in… To iterate through the loop key name
for(const key in object) {
console.log(key);
}
// key1
// key2
// key3 Copy the code
Element order and length
Map keeps track of length so that it can be accessed in O(1) complexity
const map = new Map(a);map.set('key1'.'value1');
map.set('key2'.'value2');
map.set('key3'.'value3');
map.size; / / 3
Copy the code
On the other hand, for “Object”, if you want to obtain the attribute length of the Object, you need to iterate it manually, making it O(n) complexity and the attribute length n
In the example mentioned above, we can see that “Map” always returns key names in insertion order. But “Object” is not. Starting with ES6, the String and Symbol keys are stored in order, but keys saved to strings by implicit conversion are out of order
const object = { };
object['key1'] = 'value1';
object['key0'] = 'value0';
object; // {key1: "value1", key0: "value0"}
object[20] = 'value20';
object; // {20: "value20", key1: "value1", key0: "value0"} Object.keys(object).length; / / 3 Copy the code
Object/Map application scenarios
The above is the basic difference between “Map” and “Object”, and the difference should be considered when solving the problem.
- whenInsertion orderIs what you need to consider when solving the problem, and is currently required to use divisionString 和 SymbolOther than the key name, then“Map“It’s the best solution
- If you needIterate over the key-value pairs(and need to consider the order), then I think it needs to be prioritized“Map“.
- “Map“Is a pure hash structure, and“Object“No (it has its own internal logic).Map 在Frequently add or delete key-value pairsBetter performance and higher performance in the scenario of. This is also a priority when you need to manipulate data frequentlyMap
- For another practical example, if you have a user action feature for custom fields that you can customize from a form, it’s best to use a Map, because there’s a good chance it willDestroy the original object
const userCustomFields = {
'color': 'blue'. 'size': 'medium'. 'toString': 'A blue box'
};
Copy the code
In this case, the user-defined toString will destroy the original object, and the “Map” key will accept any type and have no effect
function isMap(value) {
return value.toString() === '[object Map]';
}
const actorMap = new Map(a); actorMap.set('name'.'Harrison Ford'); actorMap.set('toString'.'Actor: Harrison Ford'); // Works! isMap(actorMap); // => true Copy the code
- “Object” is perfectly useful when you need to work with properties, especially when you need to work with JSON data. Since a “Map” can be of any type, there is no native way to convert it to JSON.
var map = new Map(a)map.set('key'.'value')
JSON.stringify(map) / / "{}"
Copy the code
- If you want to keep unique logic and attributes in an Object, you can only use Object
var obj = {
id: 1. name: "Front-end Sneaker". speak: function(){
return `Object Id: The ${this.id}, with Name: The ${this.name}`;
} } console.log(obj.speak());//Object Id: 1, with Name: front-end Sneaker. Copy the code
- When you need to passRegular expressionWhen deciding to process some business logic,“Map“Will be your best solution
const actions = (a)= >{
const functionA = (a)= >{/*do sth*/}
const functionB = (a)= >{/*do sth*/}
const functionC = (a)= >{/*send log*/}
returnnewMap([
[/^guest_[1-4]$/,functionA], [/^guest_5$/,functionB], [/^guest_.*$/,functionC], / /... ]) } const onButtonClick = (identity,status) = >{ let action = [...actions()].filter(([key,value]) = >(key.test(`${identity}_${status}`))) action.forEach(([key,value]) = >value.call(this)) } Copy the code
By taking advantage of the nature of the array loop, any logic that meets the re condition is executed, so you can execute both the public logic and the individual logic at the same time. Because of the re, you can open your imagination to unlock more gameplay and see more elegant ways to write complex JavaScript decisions
Conclusion:
“Object” objects are generally good for holding structured data, but they also have limitations:
- The key name accept type can only be usedStringorSymbol
- Custom key names are easily inherited from stereotypesKey name conflict(e.g.toString.constructorEtc.)
- The object/re cannot be used as a key name and these problems can be solved by “Map”, providing benefits such as iterators and easy size look-ups
Do not use “Map” as a substitute for plain “Object”, but as a complement to plain objects
If you like xiaobian, you can pay attention to the following public number, learn more about dry goods.
The resources
[1]
Effective TypeScript: https://www.oreilly.com/library/view/effective-typescript/9781492053736/
[2]
MDN: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map
[3]
dmitripavlutin: https://dmitripavlutin.com/maps-vs-plain-objects-javascript
[4]
medium: https://medium.com/javascript-in-plain-english