年底了“谈个对象”吧

2,203 阅读4分钟

年底了,都回家过年了,是时候好好谈谈对象了。今天要谈的对象是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

WeakMapMap 类似,主要的不同点有两处:

  1. WeakMap 的只接受对象作为键名。
const map = new WeakMap();
map.set(1, 2);
// TypeError: Invalid value used as weak map key

map.set({}, 1);
  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 实例的长度可能会随时变化,所以从规则上就禁止去访问 WeakMapsize 属性,自然也不会提供用于遍历的那几个属性了。

总结

从一些共同点以及不同点介绍了对象以及 Map 的特性,希望能帮助到你了解他们的优缺点,进而在适合的场景选用适合的数据结构。

关注我们