年底了,都回家过年了,是时候好好谈谈对象了。今天要谈的对象是Map和WeakMap。先对这两个对象进行一个简单的介绍总结:
Map
的 key 可以是任何类型的值,Object
的键值只能是 string 和 symbols。Map
实例按照插入的顺序存储数据Map
实例的属性更容易删除WeakMap
相比Map
提供了更优的垃圾回收机制
下面进入正题。
Map 是什么
看下 MDN 解释:
The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
关键词:键值对、按插入顺序有序排列、键或者值可以为任何类型的数据。
语法
new Map([iterable])
我们再看一下 Map
实例上的属性和方法。
初始化一个 Map 实例:
const map = new Map(); // 空的 Map
map.set(1, "first");
map.get(1); // 'first'
const newMap = new Map([
[1, "zhangsan"],
[2, "lisi"]
]);
newMap.get(1); // "zhangsan"
newMap.get(2); // "lisi"
键值
对象里的键值只能为字符串。而 Map
实例如上解释可以为任何类型的数据。
const obj = {};
obj[1] = "one";
obj["1"]; // one
obj[1]; // one
const map = new Map();
map.set(1, "one");
map.set("1", "another one");
// map 包含2组键值对: 1, 'one' 以及 '1', 'another one'
这里再说一下,Map
实例当在比较键名的时候采用 sameValueZero 的规则,有点像严格等于 ===
,但又有两个例外:NaN 和 NaN、+0 和-0,Map 认为它们一样。
另外,Map 实例还支持链式调用设置键值对,对象只能一次设置一个键值对:
const map = new Map();
map
.set(1, "one")
.set(2, "two")
.set(3, "three");
删除属性
删除对象上的属性值时会一直返回 true,除非这个属性是
删除一个 Map
上的属性时,如果这个属性存在会返回 true,如果不存在则返回 false。
// 删除对象属性
const obj = {
name: "zhangsan"
};
delete obj.name; // true
delete obj.age; // true,不存在这个属性同样会返回 true
// 删除Map属性
const map = new Map();
map.set("name", "zhangsan");
map.delete("name"); // true
map.delete("age"); // false
如果删除对象上所有的属性呢?或许可以这样:
const obj = { 1: 234 };
obj = {};
但是这样的话,你其实只是把一个新的空对象复制给这个实例,而原先的旧对象并不一定就被删除了(要看垃圾回收机制),如果别的地方对它还有引用,那它就还会一直存在。
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
delete obj[key];
}
}
Map
实例就能很好地处理这个问题:map.clear();
。
遍历
如开头 MDN
上的解释一样, Map
的遍历更好预测。
const obj = {};
obj[5] = "five";
obj[4] = "four";
Object.entries(obj); // [ ['4', 'four'], ['5', "five"] ]
const map = new Map();
map
.set(5, "five")
.set(4, "four")
.entries(); // [ 5 => "five", 4 => "four" ]
和对象类似,Map
也有 3 种方法用于遍历对象:
map.keys()
返回键名组成的数组map.values()
返回键值组成的数组map.entries()
返回包含一组组键值对的可遍历的数组
相互转换
Map
构造函数接收一个数组或者可迭代(iterable
)对象,所以对象转 Map
实例的时候我们可以用 Object.entries
方法处理:
const obj = {
one: 1,
two: 2
};
const map = new Map(Object.entries(obj));
console.log(map.get("one")); // 1
那么又如何把 Map
实例转成对象呢?还好我们有 Object.fromEntries
方法,用法和 Object.entries
相反:
const map = new Map();
map.set("one", 1);
map.set("two", 2);
const obj = Object.fromEntries(map);
console.log(obj.one); // 1
Map vs WeakMap
WeakMap
与 Map
类似,主要的不同点有两处:
WeakMap
的只接受对象作为键名。
const map = new WeakMap();
map.set(1, 2);
// TypeError: Invalid value used as weak map key
map.set({}, 1);
WeakMap
键名所指向的对象,不计入垃圾回收机制。
let obj = { name: "Matt" }; // object can be accessed
let map = new Map();
map.set(obj, true);
obj = null; // 尽管对象被重写了,但是旧对象依然可以在map里访问到
WeakMap
是怎么处理这种情况的:
let obj = { name: "Matt" };
let weakMap = new WeakMap();
weakMap.set(obj, true);
obj = null; // 重写对象,同时也会在 weakMap 里被删除
// weakMap 现在是空
WeakMap
只有四种方法:get
, set
, delete
, has
。
之所以没有 size
等属性或者方法,就是因为这个机制, WeakMap
实例的长度可能会随时变化,所以从规则上就禁止去访问 WeakMap
的 size
属性,自然也不会提供用于遍历的那几个属性了。
总结
从一些共同点以及不同点介绍了对象以及 Map
的特性,希望能帮助到你了解他们的优缺点,进而在适合的场景选用适合的数据结构。