ES6 中 Map 和 Set

简介: ES6 中 Map 和 Set

image.png


Map

ES6 以前在 js 中实现 key/value 的存储形式都可以使用 Object 来实现,也就是对象的 属性做为键,当需要获取 value 时,必须要通过对应的 key 去访问。

Map 和 Object 的区别

  • key 上的区别
  • Object 的 key 类型只能是 number | string | symbol 类型
  • Map 的 key 值类型可以包含引用类型
  • 它们值的类型没有限制
/**
  Object
**/
var obj = {
  1: 1,
  '2': '2',
  [Symbol('sy')]: function () { },
  [true]: true,
  [{}]: {},
  [[]]: [],
  [null]: null,
  [undefined]: undefined,
  [function add() { }]: function add() { }
}
// 除了 symbol 类型的属性不能被遍历出来,其他的所有 key 类型都被转成了 string 类型的 key
// 本质上是被调用了 对应类型的 toString() 方法得到的结果
Object.keys(obj).forEach(key => {
  console.log("key = ", key, " type = ", typeof key);
  //  key =  1                      type =  string
  //  key =  2                      type =  string
  //  key =  true                  type =  string
  //  key =  [object Object]   type =  string
  //  key =                          type =  string
  //  key =  null                   type =  string
  //  key =  undefined          type =  string
  //  key =  function add(){}  type =  string
});
/**
  Map
**/
var map = new Map();
map.set({}, {})
  .set([], [])
  .set(false, false)
  .set(null, null)
  .set(undefined, undefined)
  .set(function add() { }, function add() { })
  .set(Symbol('test'), Symbol('test'))
  .set('string', 'string')
  .set(1, 1);
for (const key of map.keys()) {
  console.log(typeof key);
  // object
  // object
  // boolean
  // object
  // undefined
  // function
  // symbol
  // string
  // number
}
复制代码
  • 顺序和迭代
  • Object 中的 key 是无序的,因此在遍历时不能通过插入时的顺序访问对应的值
  • Map 实例化是可以接收二维数组形式的键值对进行初始化,并且会按照顺序去插入,因此可以通过插入的顺序去访问对应的值
  • Object 默认不能使用 for of 或者展开运算符(...) 遍历,因为其原型上不存在 Symbol.iterator 的迭代函数
  • Map 的原型上有 Symbol.iterator 的迭代函数,因此可以通过 for of 或者展开运算符(...)进行迭代
// 1. map 的 for of 遍历在上边的代码中就已经使用过了,这里不再展示
  typeof Map.prototype[Symbol.iterator] === 'function'
// 2. obj 的 for of 遍历
  typeof Object.prototype[Symbol.iterator] === 'undefined'
 var obj = {
   n1: 1,
   n2: 2,
 };
 for (const iterator of obj) {
   // Uncaught TypeError: obj is not iterable
 }
复制代码

选择使用 Object 还是 Map ?

本质还是要看我们存储键值对的目的和具体场景,对于不同的需要,可以进行灵活使用。下面列举一些常见场景。

  • 内存占用
  • 对给定固定大小的内存,Map 大约可比 Object 多存储 50% 的键/值对
  • 插入性能
  • 往 Object 和 Map 中插入新的键/值对的消耗大致相当,不过插入 Map 在所有浏览器中一般会稍微快一些
  • 查找速度
  • 如果键值对数量本来就多且复杂,那么使用 Object 和 Map 都可以,但一般情况下都推荐使用 Object.
  • 如果只包含少量键/值对,Object 的查找速度要比 Map 更快.
  • 如果把 Object 当成数组使用的情况下(比如使用连续整数作为属性),浏览器引擎可以进行优化,在内存中使用更高效的布局,但对于 Map 来说是不可能的.
  • 删除性能
  • 在使用 Object 时,通常不推荐使用 delete 操作去删除属性,具体可以参考这,因此,通常都是用伪删除的方式,比如将不需要用到的属性设置为 null 或者 undefined
  • 而对大多数浏览器引擎来说,Map 的 delete()操作都比插入和查找更快。如果代码涉及大量删除操作,那么毫无疑问应该选择 Map.

WeakMap

ES6 新增的“弱映射”(WeakMap)是一种新的集合类型,为 JS 带来了增强的键/值对存储机制

  • 弱映射中的键只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置键会抛出TypeError.
  • WeakMap 和 Map 的值类型都没有限制,包括 WeakMap Api 也是 Map 的子集.弱键 Weak
  • 弱键不属于正式的引用,不会阻止垃圾回收,只要存在,键/值对就会存在于映射中,并被当作对值的引用,因此就不会被当作垃圾回收.
// 1. 初始化时,由于没有 {} 的其他引用指向,当这段代码执行完成时,这个对象键就会被当作垃圾回收,于是对应的值也会被处理掉
// 最终这个键/值对就从弱映射中消失了,成为了一个空映射
const wm = new WeakMap();
wm.set({}, "val");
// 2. 对象维护着一个对弱映射键的引用,因此这个对象键不会成为垃圾回收的目标。
//    当 setTimeout 中清除掉会摧毁键对象的最后一个引用,垃圾回收程序就可以把这个键/值对清理掉
const weakKey = {
  name: {}
};
const wm = new WeakMap();
wm.set(weakKey.name, "val");
console.log(wm.has(weakKey.name)); // true
setTimeout(()=>{
  weakKey.name = null;
  console.log(wm.has(weakKey.name)); // false
});
复制代码

不可迭代键

  • 由于 WeakMap 中的键/值对任何时候都可能被销毁,所以没必要提供迭代其键/值对的能力,包括像 clear() 这样一次性销毁所有键/值的方法
  • image.png

image.png

使用弱映射

  • 存储 DOM 节点元数据,WeakMap 实例不会妨碍垃圾回收,所以非常适合保存关联元数据,
  • PS:关于元数据的概念可以参考阮一峰老师的网络日志——元数据
// 1. 使用常规 Map
  const m = new Map();
  const loginButton = document.querySelector('#login');
  // 给这个节点关联一些元数据
  m.set(loginButton, {disabled: true});
 // 假设 loginButton 节点在页面上被删除了
 // 由于映射中还保存着按钮的引用,所以对应的 DOM 节点仍然会逗留在内存中
 // 除非明确将其从映射中删除或者等到映射本身被销毁
// 2. 使用 WeaMap
  const wm = new WeakMap();
  const loginButton = document.querySelector('#login');
  // 给这个节点关联一些元数据
  wm.set(loginButton, {disabled: true});
  // 当节点从 DOM 树中被删除后,垃圾回收程序就可以立即释放其内存(前提是没有其他地方引用这个对象)
复制代码

Set

ES6 新增的 Set 是一种新集合类型,为 JS 带来集合数据结构。Set 在很多方面都像是加强的 Map,这是因为它们的大多数 API 和行为都是共有的

image.png

顺序与迭代

  • Set 会维护值插入时的顺序,因此支持按顺序迭代.
  • values() === [Symbol.iterator]() ,且 values() 是默认迭代器,所以可以直接对集合实例使用扩展操作,把集合转换为数组.
const s = new Set(["val1", "val2", "val3"]);
console.log([...s]); // ["val1", "val2", "val3"]
复制代码
  • 集合的 entries() 方法返回一个迭代器,可以按照插入顺序产生包含两个元素的数组,这两个元素是集合中每个值的重复出现
const s = new Set(["val1", "val2", "val3"]);
for (let pair of s.entries()) {
   console.log(pair);
}
// ["val1", "val1"]
// ["val2", "val2"]
// ["val3", "val3"]
复制代码
  • 与 Map 类似,Set 可以包含任何 JavaScript 数据类型作为值。集合也使用SameValueZero 操作(ECMAScript 内部定义,无法在语言中使用),基本上相当于使用严格对象相等的标准来检查值的匹配性。

WeakSet

ES6 新增的“弱集合”(WeakSet)是一种新的集合类型(值的集合),为这门语言带来了集合数据结构,WeakSet 是 Set 的“兄弟”类型,其 API 也是 Set 的子集

  • WeakSet 中的“weak”(弱),描述的是 JavaScript 垃圾回收程序对待“弱集合”中值的方式
  • 弱集合中的值只能是 Object 或者继承自 Object 的类型,尝试使用非对象设置值会抛出 TypeError,这一点和 WeakMao 中的键类型是一致的
const set = new Set();
set.add(123);
console.log(set); // Set(1) {123}
const weakSet = new WeakSet();
weakSet.add(123); // TypeError: Invalid value used in weak set
console.log(weakSet);
复制代码

弱值 Weak

  • 弱值不属于正式的引用,不会阻止垃圾回收
// 由于没有指向 {} 这个对象的其他引用,所以当这行代码执行完成后,这个对象值就会被当作垃圾回收。
// 这个值就从弱集合中消失了,使其成为一个空集合
const ws = new WeakSet();
ws.add({});
// container 对象维护着一个对弱集合值的引用,因此这个对象值不会成为垃圾回收的目标
// 当 setTimeout 中清除掉引用,就会摧毁值对象的最后一个引用,垃圾回收程序就可以把这个值清理掉
const ws = new WeakSet();
const container = {
   val: {}
};
ws.add(container.val);
setTimeout(()=>{
  container.val = null;
});
复制代码

不可迭代值

  • WeakSet 中的值任何时候都可能被销毁,所以没必要提供迭代其值的能力,也包括不提供像 clear() 这样一次性销毁所有值的方法

使用弱集合

  • 相比于 WeakMap 实例,WeakSet 实例的用处没有那么大,但弱集合在给对象打标签时还是有价值的
// 1. 使用常规 Set
// 通过查询元素在不在 disabledElements 中,就可以知道它是不是被禁用了。
// 假如 DOM 树中被删除了,它的引用却仍然保存在 Set 中,因此垃圾回收程序也不能回收它
const disabledElements = new Set();
const loginButton = document.querySelector('#login');
// 通过加入对应集合,给这个节点打上“禁用”标签
disabledElements.add(loginButton);
// 2. 使用 WeakSet
// 只要 WeakSet 中任何元素从 DOM 树中被删除,垃圾回收程序就可以忽略其存在,而立即释放其内存(假设没有其他地方引用这个对象)
const disabledElements = new WeakSet();
const loginButton = document.querySelector('#login');
// 通过加入对应集合,给这个节点打上“禁用”标签
disabledElements.add(loginButton);
复制代码

END


目录
相关文章
|
1月前
|
存储 JavaScript Java
(Python基础)新时代语言!一起学习Python吧!(四):dict字典和set类型;切片类型、列表生成式;map和reduce迭代器;filter过滤函数、sorted排序函数;lambda函数
dict字典 Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。 我们可以通过声明JS对象一样的方式声明dict
164 1
|
4月前
|
存储 缓存 JavaScript
Set和Map有什么区别?
Set和Map有什么区别?
395 1
|
1月前
|
存储 算法 容器
set_map的实现+set/map加持秒杀高频算法题锻炼算法思维
`set`基于红黑树实现,支持有序存储、自动去重,增删查效率为O(logN)。通过仿函数可自定义排序规则,配合空间配置器灵活管理内存。不支持修改元素值,迭代器失效需注意。`multiset`允许重复元素。常用于去重、排序及查找场景。
|
5月前
|
存储 JavaScript 前端开发
for...of循环在遍历Set和Map时的注意事项有哪些?
for...of循环在遍历Set和Map时的注意事项有哪些?
314 121
|
8月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
241 2
|
5月前
|
存储 C++ 容器
unordered_set、unordered_multiset、unordered_map、unordered_multimap的介绍及使用
unordered_set是不按特定顺序存储键值的关联式容器,其允许通过键值快速的索引到对应的元素。在unordered_set中,元素的值同时也是唯一地标识它的key。在内部,unordered_set中的元素没有按照任何特定的顺序排序,为了能在常数范围内找到指定的key,unordered_set将相同哈希值的键值放在相同的桶中。unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。它的迭代器至少是前向迭代器。前向迭代器的特性。
261 0
|
5月前
|
编译器 C++ 容器
用一棵红黑树同时封装出map和set
再完成上面的代码后,我们的底层代码已经完成了,这时候已经是一个底层STL的红黑树了,已经已符合库里面的要求了,这时候我们是需要给他穿上对应的“衣服”,比如穿上set的“衣服”,那么这个穿上set的“衣服”,那么他就符合库里面set的要求了,同样map一样,这时候我们就需要实现set与map了。因此,上层容器map需要向底层红黑树提供一个仿函数,用于获取T当中的键值Key,这样一来,当底层红黑树当中需要比较两个结点的键值时,就可以通过这个仿函数来获取T当中的键值了。我们就可以使用仿函数了。
74 0
|
5月前
|
存储 编译器 容器
set、map、multiset、multimap的介绍及使用以及区别,注意事项
set是按照一定次序存储元素的容器,使用set的迭代器遍历set中的元素,可以得到有序序列。set当中存储元素的value都是唯一的,不可以重复,因此可以使用set进行去重。set默认是升序的,但是其内部默认不是按照大于比较,而是按照小于比较。set中的元素不能被修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
246 0
|
9月前
|
编译器 容器
哈希表模拟封装unordered_map和unordered_set
哈希表模拟封装unordered_map和unordered_set