深入理解 Map、WeakMap、Set、WeakSet 数据结构

519 阅读11分钟

一、简单了解一下这些数据结构

Map(字典)

Map 是一种新的集合类型,为 ECMAScript 语言带来了真正的键/值存储机制,Map 的大多数特性都可以通过 Obeject 类型实现,但二者之间还是存在一些细微的差异。

  • 本质上是键值对的集合,类似集合
  • 可以遍历,方法很多可以跟各种数据格式转换:set、get、has、delete、clear、keys、values、entries、forEach

WeakMap

WeakMap 对象是一组键值对的集合,其中的键是弱引用对象,而值可以是任意。

WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。

  • 只接受对象作为键名(null 除外),不接受其他类型的值作为键名
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的
  • 不能遍历,方法有 get、set、has、delete

Set(集合)

它是 ES6 新增的一种数据结构,类似于数组,但成员是唯一且无序的,没有重复的值。

它是一个 构造函数,用来生成 Set 数据结构

  • 成员唯一、无序 
  • [value, value],键值与键名是一致的(或者说只有键值,没有键名) 
  • 可以遍历,方法有:add、delete、has、clear、keys、values、entries、forEach

WeakSet

WeakSet 对象允许你将 弱引用对象 储存在一个集合中

  • 成员唯一、都是对象
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM节点,不容易造成内存泄漏
  • 不能遍历,方法有 add、delete、has

Object(对象)

引用类型指的是 object

object 包括内置对象、宿主对象、自定义对象

内置对象中有 Object、Function、Array、String、Number、Boolean 等原生对象构造函数

在 JavaScript 中,一切皆对象(除 undefined、null 外)

二、Map 和 WeakMap

Map

Map 是 ES6 中的一个数据结构,它类似于对象,但它的 key 可以是任何类型,不仅限于字符串或 Symbol 类型。Map 的使用场景包括需要对一组键值对进行处理时,或者需要根据键查找值时。

使用示例:

let demo1 = new Map()

实例对象的属性:

  1. size,返回 Map 实例包含的 键值对 总数

实例对象的方法:

  1. get(key):返回键所对应的值,如果键不存在则返回 undefined。
  2. set(key, value):设置键值对。
  3. has(key):返回一个布尔值,表示 WeakMap 中是否存在该键。
  4. delete(key):删除指定键的键值对。
  5. clear():清空 WeakMap 中的所有键值对。
// 创建一个空的
Mapconst myMap = new Map();
// 添加键值对
myMap.set('key1''value1');
myMap.set('key2''value2');
myMap.set('key3''value3');
// 获取指定键对应的值
console.log(myMap.get('key1')); // 输出:value1
// 检查是否存在指定的键
console.log(myMap.has('key4')); // 输出:false

// 删除指定键对应的键值对
myMap.delete('key2');

// 清空 Map 中所有的键值对
myMap.clear();

// 使用 entries() 遍历 Map 中的所有键值对
for (const [key, value] of myMap.entries()) {
  console.log(`${key}${value}`);
}

WeakMap

WeakMap 对象是一种新的集合类型,它是一组 键值对 的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

WeakMap 是 Map 的兄弟类型,其 API 也是 Map 的子集。

弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,如果没有其他的变量或属性引用这个对象值,则这个对象将会被垃圾回收掉(不考虑该对象还存在于 WeakSet 中),所以,WeakSet 对象里有多少个成员元素,取决于垃圾回收机制有没有运行,运行前后成员个数可能不一致,遍历结束之后,有的成员可能取不到了(被垃圾回收了)。

WeakMap 的使用方法和 Map 相似,它可以存储键值对,并且可以使用 get()、set()、has()、delete() 等方法进行操作。它的主要用途是作为一种存储对象私有数据的方式,因为 WeakMap 中存储的数据不会被视为对象的属性,因此不会影响垃圾回收。

WeakMap 有以下属性和方法:

  1. size:返回 WeakMap 中键值对的数量。
  2. get(key):返回键所对应的值,如果键不存在则返回 undefined。
  3. set(key, value):设置键值对。
  4. has(key):返回一个布尔值,表示 WeakMap 中是否存在该键。
  5. delete(key):删除指定键的键值对。
  6. clear():清空 WeakMap 中的所有键值对。

WeakMap 不同时间段的数据可能是不同的,

const wm = new WeakMap();
const obj = {};

wm.set(obj, "value");
console.log(wm.get(obj)); // "value"

wm.delete(obj);
console.log(wm.has(obj)); // false

三、Set 和 WeakSet

Set

Set 是 ES6 中新增的数据结构,用于存储一组唯一的值(不重复),类似于数组,但是成员的值都是唯一的,没有重复的值。

使用方法

let demo1 = new Set()

实例对象的属性:

  1. constructor:构造函数,默认为 Set 函数。
  2. size:返回 Set 实例的成员总数。

实例对象的方法:

  1. add(value):向 Set 对象添加一个新的元素,返回该 Set 对象。
  2. clear():清空 Set 对象所有的元素,没有返回值。
  3. delete(value):从 Set 对象中删除指定的元素,返回一个布尔值,表示删除是否成功。
  4. has(value):判断 Set 对象中是否存在指定的元素,返回一个布尔值。

使用场景

  1. 去重。可以通过 Set 来去除数组中的重复项,也可以将字符串中的重复字符去除。
  2. 交集、并集、差集等操作。可以通过 Set 的一些方法来实现集合运算,例如交集可以通过两个 Set 对象使用 filter() 方法实现。
  3. ES6 中 Map 和 WeakMap 的实现。Set 内部实现方式和 Map 类似,可以使用 Set 作为 Map 中的键值对的 Key,这样就可以实现 Map 的功能。同时,WeakSet 内部也使用了 Set 实现。
const mySet = new Set();

mySet.add(1);
mySet.add(2);
mySet.add(3);
mySet.add(2); // 不会重复添加

console.log(mySet); // Set(3) {1, 2, 3}
console.log(mySet.size); // 3
console.log(mySet.has(2)); // true

mySet.delete(2);
console.log(mySet); // Set(2) {1, 3}

mySet.clear();
console.log(mySet); // Set(0) {}

使用 Set 进行数组去重

const arr2 = [1222234556]
console.log([...new Set(arr2)])

Set 中的特殊值

Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:

  1. +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复
  2. undefined 与 undefined 是恒等的,所以不重复
  3. NaN 与 NaN 是不恒等的,但是在 Set 中认为NaN与NaN相等,所有只能存在一个,不重复。

WeakSet

WeakSet 是 ES6 中新增的一种集合类型,它与 Set 类似,不同之处在于它只能存储对象,且只持有对象的弱引用。也就是说,当一个对象被 WeakSet 引用时,如果这个对象不再被其他变量或属性引用,则这个对象会被自动回收,而不需要手动将它从 WeakSet 中删除。

WeakSet 的使用方法和 Set 很相似,也有 add、has 和 delete 方法。不同的是,WeakSet 不支持 forEach 和 size 属性,也不能遍历其中的所有元素。因为 WeakSet 中的元素是对象的弱引用,所以不能保证它们的引用可用,也不能保证元素的顺序。

WeakSet 的主要应用场景是管理 DOM 节点的引用,当某个 DOM 节点被删除时,其在 WeakSet 中的引用也会自动被删除,从而避免了内存泄漏。

下面是 WeakSet 的常用属性和方法:

  1. WeakSet.prototype.add(value): 向 WeakSet 中添加一个对象。
  2. WeakSet.prototype.has(value): 判断 WeakSet 中是否存在某个对象。
  3. WeakSet.prototype.delete(value): 从 WeakSet 中删除某个对象。

下面是一个使用 WeakSet 的示例:

let ws = new WeakSet();
let obj1 = { a: 1 };
let obj2 = { b: 2 };

ws.add(obj1);
ws.add(obj2);
console.log(ws.has(obj1)); // true
console.log(ws.has(obj2)); // true

obj1 = null;
console.log(ws.has(obj1)); // false
console.log(ws.has(obj2)); // true

在这个示例中,我们创建了一个 WeakSet,并向其中添加了两个对象。之后我们将 obj1 设为 null,这样它就不再被任何变量引用了,于是它会被自动回收。这时候,我们再次调用 ws.has(obj1) 的结果为 false,说明 obj1 已经被删除了,而 obj2 还存在于 WeakSet 中。

四、Object(对象)

属性继承

JavaScript 对象可以从一个称为原型的对象里继承属性。对象的方法通常是继承的属性。这种”原型式继承“(prototypal inheritance)是 JavaScript 的核心特征。

使用方法

var obj = new Object(); // 效果如同 var obj = {}

实例对象的属性

  1. Object.prototype.constructor:一个引用值,指向 Object 构造函数
  2. Object.prototype.proto:指向一个对象,当一个 object 实例化时,使用该对象作为实例化对象的原型

实例对象的方法

  1. Object.assign():通过复制一个或多个对象来创建一个新的对象
  2. Object.create():使用指定的原型对象和属性创建一个新对象
  3. Object.defineProperty():给对象添加一个属性并指定该属性的配置
  4. Object.defineProperties():给对象添加多个属性并分别指定它们的配置
  5. Object.entries():返回给定对象自身可枚举属性的 [key, value] 数组
  6. Object.keys():返回一个包含所有给定对象自身可枚举属性名称的数组
  7. Object.values():返回给定对象自身可枚举值的数组

原型方法

  1. Object.prototype.hasOwnProperty():返回一个布尔值,用于表示一个对象自身是否包含指定的属性,该方法并不会查找原型链上继承来的属性用 hasOwnProperty 就能检测出,它能区别自身属性与继承属性
  2. Object.prototype.isPrototypeOf():返回一个布尔值,用于表示该方法所调用的对象是否在指定对象的原型链中
  3. Object.prototype.toString():返回一个代表该对象的字符串。
  4. Object.prototype.valueOf():返回指定对象的原始值

五、区别

Map 与 Set 的区别是什么?

共同点:集合、字典 都是储存不重复的值

不同点:

  1. Map 是键值对,Set 是不重复的值
  2. Map 可以通过 get 方法获取值,而 Set 不能因为它只有值
  3. Set 的值是唯一的可以用来做数组去重,Map 由于没有格式限制,可以做数据存储

Map 与 Object 的区别是什么?

Object:是最常用的一种引用类型数据,可用于存储键值对的集合,在 ECMAScript 1st 里添加的

Map:是键值对的集合,采用 Hash 结构存储,在 ECMAScript 2015 版本里新增的

相同点:键值对的动态集合,支持增加删除键值对

不同点概述:

  1. Object 键类型必须是 String 或者 Symbal(否则会进行类型转换);Map 键可以说任意类型
  2. Object key 是无序的,Map key 是有序的,按插入顺序返回
  3. Map 可以通过 size 属性直接访问键值对数量,Object 只能手动计算
  4. Object 添加或修改属性使用点或者中括号形式,Map 使用 set 方法插入键值对

不同点详细介绍:

  1. 键的类型
    1. Object:键类型必须是 String 或者 Symbol,如果非 String 类型,会进行数据类型转换
    2. Map:键可以是任意类型,包括对象,数组,函数等。不会进行数据类型转换。
  2. 键的顺序
    1. Object:key是无序的,不会按照添加到顺序返回
    2. Map:key 是有序的,按照插入的顺序返回
  3. 键值对 size
    1. Object:只能手动计算,通过 Object.keys() 方法或者通过 for…in 循环统计
    2. Map:直接通过 size 属性访问
  4. 键值对的访问
    1. Object:添加或者修改属性,通过点或者中括号的形式
    2. Map:添加和修改 Key-value
  5. 迭代器
    1. Object:不具有 迭代器-iterator 特性(Object 本身不具有 iterator 特性,默认情况下不能使用 for…of 进行遍历)
    2. Map:Map 结构的 keys(), values(), entries() 方法返回值都具有 iterator 特性
  6. JSON 序列化
    1. Object:Object 类型可以通过 JSON.stringify() 进行序列化操作
    2. Map:Map 结构不能直接进行 JSON 序列化

应用场景

Object

  1. 仅做数据存储,并且属性仅为字符串或者 Symbol 类型
  2. 需要进行序列化转换为 json 传输时
  3. 当做一个对象的实例,需要保留自己的属性和方法时

Map

  1. 会频繁更新和删除键值对时
  2. 存储大量数据时,尤其时 key 类型未知的情况下
  3. 需要频繁进行迭代处理

选择 Object 还是 Map

对于 web 开发任务来说,选择 Object 还是 Map 只是个人偏好问题,但对于在乎内存和性能的开发者来说,对象和映射之间确实存在显著区别。

  • 内存占用:给定固定大小的内存,Map 可以比 Object 多存储 50% 的键值对。
  • 插入性能:插入 Map 稍微快一点。
  • 查找速度:Object 更优。
  • 删除性能:Map 的 delete() 操作都比插入和查找更快。