Object和Map

146 阅读4分钟

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”表示弱映射的键是“弱弱地拿着”的。意思就是,这些键不属于正式的引用,不会阻止垃圾回收。但要注意的是,弱映射中值的引用可不是“弱弱地拿着”的。只要键存在,键/值对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收。