什么时候使用Maps代替普通的JavaScript对象?
普通的JavaScript对象{key:'value'}保存结构化数据。但是普通的JS对象有其局限性:
只能将字符串和符号用作对象的键。如果我们使用其他任何说法,将数字作为对象的键,则在访问这些键期间,我们将看到这些键将隐式转换为字符串,从而使我们失去类型的一致性。const names = {1:“一个”,2:“两个”};Object.keys(名称); // ['1','2']
通过将JS标识符写为对象的键名(例如toString,构造函数等),有可能会意外覆盖原型的继承属性。
另一个对象不能用作对象的键,因此无法通过将该对象写为另一个对象的键来为该对象写入额外的信息,并且该另一个对象的值将包含该额外的信息
对象不是迭代器
无法直接确定对象的大小
Maps解决了Objects的这些限制,但是我们必须将Maps作为Objects的补充而不是替代。基本上,Map只是数组数组,但我们必须将该数组数组作为带有new关键字的参数传递给Map对象,否则,仅对于数组数组,Map的有用属性和方法不可用。请记住,数组或数组中的键/值对必须仅用逗号分隔,没有像普通对象中那样的冒号。
决定使用Map还是Object的3个技巧:
在直到运行时才知道键的情况下,才在对象上使用映射,因为如果这些键覆盖了对象的继承属性,则由用户输入或在不知不觉中形成的键可能会破坏使用该对象的代码,因此在这些情况下map是更安全的。当所有键为相同类型且所有图为相同类型时,也请使用图。
如果需要将原始值存储为键,请使用映射。
如果需要对单个元素进行操作,请使用对象。
使用地图的好处是:
1. Map接受任何键类型并保留键类型:
我们知道,如果对象的键不是字符串或符号,则JS会将其隐式转换为字符串。相反,Map接受任何类型的键:字符串,数字,布尔值,符号等。Map保留原始键类型。在这里,我们将数字用作地图内的键,并且它将保留为数字:
const numbersMap= new Map();
numbersMap.set(1, 'one');
numbersMap.set(2, 'two');
const keysOfMap= [...numbersMap.keys()];
console.log(keysOfMap); // [1, 2]
在地图内部,我们甚至可以将整个对象用作键。有时,我们可能想存储一些与对象相关的数据,而又不将这些数据附加到对象本身内部,以便我们可以使用精益对象,但又想存储有关该对象的某些信息。在这些情况下,我们需要使用Map,以便我们可以将Object作为键,并将Object的相关数据作为value。
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'
我们可以通过使用适当的Map解决不直接访问该值的问题。
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. Map对键名没有限制:
对于普通的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
如果遇到用户输入创建键的情况,那么我们必须将这些键放在Map中而不是普通对象中。这是因为用户可以选择自定义字段名称,例如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'
由于Map是可迭代的,这就是为什么我们不需要entry()方法来迭代Map和键的解构的原因,值数组可以直接在Map上完成,就像在Map内一样,每个元素都以键值对的形式存在,并以逗号分隔。
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
但是对于Maps,我们可以使用map.size属性直接访问Map的大小。
const examsMap= new Map([['John Rambo', '80%'], ['James Bond', '60%']]);
console.log(examsMap.size);