Object 和 Map的差异
Object 可以设置数字、字符串、Boolean、null、undefined为键值,这些键值最终都会被转换为字符串类型。
var obj = {
1: 'a',
'1': 'a',
true: 'a',
null: 'a',
undefined: 'a'
};
Map 可以使用任何JavaScript数据类型作为键。
初始化之后,可以使用set()方法再添加键/值对。另外,可以使用get()和has()进行查询,可以通过size属性获取映射中的键/值对的数量,还可以使用delete()和clear()删除值。
const m = new Map();
const m1 = new Map([
['key1', 'val1'],
['key2', 'val2'],
['key3', 'val3']
]);
console.log(m1.size); // 3
const m2 = new Map({
[Symbol.iterator]: function* () {
yield ['key1', 'val1'];
yield ['key2', 'val2'];
yield ['key3', 'val3'];
}
});
console.log(m2.size); // 3
const m3 = new Map([[]]);
console.log(m3.has(undefined)); // true
console.log(m3.get(undefined)); // undefined
const m = new Map();
console.log(m.has('firstName')); // false
console.log(m.get('firstName')); // undefined
console.log(m.size); // 0
m.set('firstName', 'Matt').set('lastName', 'OMG');
console.log(m.has('firstName')); // true
console.log(m.get('firstName')); // 'Matt'
console.log(m.size); // 2
m.delete('firstName');
console.log(m.has('firstName')); // false
console.log(m.has('lastName')); // true
console.log(m.size); // 1
m.clear();
console.log(m.has('firstName')); // false
console.log(m.has('lastName')); // false
console.log(m.size); // 0
const m = new Map();
const functionKey = function() {};
const symbolKey = Symbol();
const objectKey = new Object();
m.set(functionKey, 'functionValue');
m.set(symbolKey, 'symbolValue');
m.set(objectKey, 'objectValue');
console.log(m.get(functionKey)); // functionValue
console.log(m.get(symbolKey)); // symbolValue
console.log(m.get(objectKey)); // objectValue
// SameValueZero比较意味着独立实例不冲突
console.log(m.get(function() {})); // undefined
与严格相等一样,在映射中用作键和值的对象及其他“集合”类型,在自己的内容或属性被修改时仍然保持不变。
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']
与 Object 类型的一个主要差异是,Map 实例会维护键值对的插入顺序,因此可以根据插入顺序执行迭代操作。
const m = new Map([ ['key1', 'val1'],
['key2', 'val2'],
['key3', 'val3']
]);
console.log(m.entries === m[Symbol.iterator]); // true
for(let pair of m.entries) {
console.log(pair);
} // ['key1', 'val1'] ['key2', 'val2'] ['key3', 'val3']
for(let pair of m[Symbol.iterator]()) {
console.log(pair);
} // ['key1', 'val1'] ['key2', 'val2'] ['key3', 'val3']
选择Object还是Map
1.内存占用
内存占用Object和Map的工程级实现在不同浏览器间存在明显差异,但存储单个键/值对所占用的内存数量都会随键的数量线性增加。批量添加或删除键/值对则取决于各浏览器对该类型内存分配的工程实现。不同浏览器的情况不同,但给定固定大小的内存,Map大约可以比Object多存储50%的键/值对。
2.插入性能
向Object和Map中插入新键/值对的消耗大致相当,不过插入Map在所有浏览器中一般会稍微快一点儿。对这两个类型来说,插入速度并不会随着键/值对数量而线性增加。如果代码涉及大量插入操作,那么显然Map的性能更佳。
3.查找速度
查找速度与插入不同,从大型Object和Map中查找键/值对的性能差异极小,但如果只包含少量键/值对,则Object有时候速度更快。在把Object当成数组使用的情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效的布局。这对Map来说是不可能的。对这两个类型而言,查找速度不会随着键/值对数量增加而线性增加。如果代码涉及大量查找操作,那么某些情况下可能选择Object更好一些。
4.删除性能
删除性能使用delete删除Object属性的性能一直以来饱受诟病,目前在很多浏览器中仍然如此。为此,出现了一些伪删除对象属性的操作,包括把属性值设置为undefined或null。但很多时候,这都是一种讨厌的或不适宜的折中。而对大多数浏览器引擎来说,Map的delete()操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择Map。
WeakMap
ECMAScript 6新增的“弱映射”(WeakMap)是一种新的集合类型,为这门语言带来了增强的键/值对存储机制。WeakMap中的“weak”(弱),描述的是JavaScript垃圾回收程序对待“弱映射”中键的方式。
弱映射中的键只能是Object或者继承自Object的类型,尝试使用非对象设置键会抛出TypeError
。值的类型没有限制。
WeakMap中“weak”表示弱映射的键
是“弱弱地拿着”的。意思就是,这些键不属于正式的引用,不会阻止垃圾回收。
但要注意的是,弱映射中值的引用可不是“弱弱地拿着”的。只要键存在,键/值对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收。