重学JavaScript【Map和Set】

简介: 重学JavaScript 篇的目的是回顾基础,方便学习框架和源码的时候可以快速定位知识点,查漏补缺,所有文章都同步在 公众号(道道里的前端栈) 和 github 上。

网络异常,图片无法展示
|

重学JavaScript 篇的目的是回顾基础,方便学习框架和源码的时候可以快速定位知识点,查漏补缺,所有文章都同步在 公众号(道道里的前端栈)github 上。


Map


ECMAScript6之前,在JavaScript中实现“键/值”形式存储可以使用Object开高效完成,也就是用对象的属性作为key,属性的值作为value。但是这种实现并非没有问题,所以TC39委员会专门为“键/值”存储定义了一个规范。

Map 是一种新的集合类型,为JavaScript带来了真正的键值存储机制。Map里面的大多数特性都可以通过Object来实现,但是二者还是会存在一些细微的差异。


API

Map构造函数可以哦通过 new 操作符创建一个空映射:

const m = new Map();

如果想在创建的同时初始化实例,可以传入一个可迭代对象,需要包含键值对数组:

const m = new Map([
  ["key", "value"]
]);
m.size // 1

初始化过后,可以使用 set() 方法再添加键值对,可以使用 get()has() 进行查询,可以通过 size 属性获取键值对的数量,可以使用 delete()clear() 删除值。

const m = new Map();
m.has("name"); // false
m.get("name"); // undefined
m.set("name", "abc")
 .set("age", 12);
m.has("name"); // true
m.get("name"); // "abc"
m.size // 2
m.delete("name");
a.size // 1
m.clear();
m.size // 0

由于set方法返回的是映射实例,所以可以用 . 来进行链式操作。

在Object中,键只能使用数字,字符串和符号,但是在Map中可以使用任何JavaScript数据类型作为键,其映射的值也是没有限制的。

const m = new Map();
const fnKey = () => {};
const symbolKey = () => {};
const objKey = () => {};
m.set(fnKey, "fnValue")
 .set(symbolKey, "symbolValue")
 .set(objKey, "objValue");
m.get(fnKey); //fnValue
m.get(symbolKey); //symbolValue
m.get(objKey); // objValue

在映射中用作键和值的对象及其他“集合”类型,在自己的内容或属性被修改时仍然保持不变。

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的一个主要差异是,Object中没有插入顺序,而Map会维护键值对的插入顺序,从而可以根据插入顺序执行迭代操作。

映射的实例提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组,可以通过entries() 方法(或者 Symbol.iterator属性,因为它引用了entries)取得这个迭代器:

const m = new Map([
  ["k", "val"]
]);
m.entries === m[Symbol.iterator]; // true
for (let c of m.entries()){
  console.log(c);
};
// ["k", "val"]

由于entries()是默认的迭代器,所以可以直接对映射实例使用扩展操作,把映射转换为数组:

[...m] // [["k", "val"]]

当然也可以使用forEach等方法进行操作。

keys()values() 分别返回以插入顺序生成键值的迭代器:

for (let key of m.keys()) { 
 console.log(key)
}
// k
for (let value of m.values()) { 
 console.log(value)
}
// val

注意:在Map里面,遍历的时候是可以修改键值的,但是在映射内部的引用无法修改

const m = new Map([
  ["k", "val"]
]);
for(let key of m.keys()){
  key = "newK";
  console.log(key, m.get("k"));
};
console.log(m.entries());
// newK val
// MapIterator {"k" => "val"}
for(let key of m.values()){
  key = "newV";
  console.log(key, m.get("k"));
};
console.log(m.entries());
// newV val
// MapIterator {"k" => "val"}


WeakMap


ECMAScript6中新增的“弱映射”(WeakMap)是一种新的集合类型,它是Map的兄弟类型,其API也是Map的子集,WeakMap中的“weak”,描述的就是JavaScript垃圾回收对待“弱映射”中键的方式。


API

使用new关键字实例化一个空的WeakMap:

const wm = new WeakMap();

在弱映射中,键只能是Object或者继承自Object的类型,如果使用其他类型会报TypeError,值的类型没有限制。

const key = {id: 1}
const wm = new WeakMap([
  [key, "value"]
]);
wm.get(key); // "value"

在初始化之后可使用 set() 添加键值对,使用 get()has() 查询,使用 delete() 删除:

const wm = new WeakMap(); 
const key1 = {id: 1}, 
 key2 = {id: 2}; 
wm.has(key1); // false 
wm.get(key1); // undefined 
wm.set(key1, "Matt") 
  .set(key2, "Frisbie"); 
wm.has(key1); // true 
wm.get(key1); // Matt 
wm.delete(key1); // 只删除这一个键/值对
wm.has(key1); // false 
wm.has(key2); // true

WeakMap的键所指向的对象,不计入垃圾回收机制。

那为什么要设计出来一个WeakMap呢?这里引用阮一峰的例子,请看:

const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
  [e1, 'foo 元素'],
  [e2, 'bar 元素'],
];

上面代码中,e1和e2是两个对象,通过arr对这两个对象加了一些说明,此时arr引用了e1和e2,一旦不在需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放e1和e2:

arr[0] = null;
arr[1] = null;

这种写法很容易被我们忘记,从而造成内存泄漏。

WeakMap就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,垃圾回收机制不会考虑它们,只要所引用的对象被清除,垃圾回收机制就会释放该对象所占用的内存,不用我们手动删除引用。

WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失,WeakMap结构有助于防止内存泄漏。

注意:WeapMap弱引用的是键名,键值依然是正常引用。

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};
wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}


选择Object还是Map


对于在乎内存和性能来说,大致有以下几点区别:

  1. 内存占用
    当浏览器给定固定大小的内存时,Map比Object多存储50%的键值对。
  2. 插入性能
    插入场景下,Map会比Object快一点,如果涉及大量插入操作,优先使用Map。
  3. 查找速度
    Object和Map在查找速度条件下,差异不大,如果包含少量的键值对,请使用Object
  4. 删除性能
    一般情况下,delete不是很常用,更多的是赋值为undefined或者null,有大量删除操作的话,Map的delete操作要快很多


Set


Set 可以理解为加强版的Map,它们的大多是API和行为是共有的。

API

Set也是通过new关键字来创建的:

const m = new Set();

初始化传入的参数也是个可迭代对象,其中包括插入到新集合实例中的元素:

const s = new Set(["foo", "bar"]);
s.size // 3

初始化之后,可以使用 add() 来增加值,使用 has() 来查询,使用 size 获得元素数量,使用 delete()clear() 删除元素:

const s = new Set();
s.has("name"); // false
s.size; // 0
s.add("name")
 .add("age");
s.has("name"); // true
s.size; // 2
s.delete("name");
s.has("name"); //false
s.clear();
s.size; // 0

上面的add就像Map中的set方法一样。Set可以包含任何JavaScript数据类型作为值:

const s = new Set(); 
const functionVal = () => {}; 
const symbolVal = Symbol(); 
const objectVal = new Object(); 
s.add(functionVal); 
s.add(symbolVal); 
s.add(objectVal); 
s.has(functionVal); // true 
s.has(symbolVal); // true 
s.has(objectVal); // true 

同样的,用作值的对象和其他“集合”类型在自己的内容或属性被修改时也不会被改变:

const s = new Set(); 
const objVal = {}, 
 arrVal = []; 
s.add(objVal); 
s.add(arrVal); 
objVal.bar = "bar"; 
arrVal.push("bar"); 
s.has(objVal); // true 
s.has(arrVal); // true

Set的delete方法返回的是一个布尔值,表示集合中是否存在要删除的值:

const s = new Set(); 
s.add('foo'); 
s.size; // 1 
s.add('foo'); 
s.size; // 1 
// 集合里有这个值
s.delete('foo'); // true 
// 集合里没有这个值
s.delete('foo'); // false

迭代

Set的迭代和Map很类似,同样它也可以直接对集合实例进行扩展:

const s = new Set(["foo", "bar"]);
[...s] // ["foo", "bar"]

其他的有关迭代器的用法,和Map一样。


WeakSet


WeakSet 就是Set对应的兄弟类型,它是一个弱集合,用法和Set类似,也和WeakMap类似。

它里面的值只能是Object或继承自Object的类型,否则会报TypeError。

const val1 = {id: 1}, 
 val2 = {id: 2}, 
 val3 = {id: 3};
const ws = new WeakSet([val1, val2, val3]);
ws.has(val1); // true
ws.has(val2); // true
ws.has(val3); // true

它也支持add,has,delete,用法和Set一样。

至于垃圾回收方面,它和WeakMap是一样的。


总结


Set和Map主要的应用场景在于 数据重组数据存储

  • Set
  • 内部类似于数组,成员唯一且无序
  • [value, value],键值和键名一样,也可以理解为只有键值,没有键名
  • 可以遍历,有add,delete,has,clear
  • WeakSet
  • 成员都是对象
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存DOM
  • 不能遍历,方法有add,delete,has,clear
  • Map
  • 本质上就是键值对的集合,类似集合,[key,value]
  • 可以遍历,方法有get,set,has,delete,clear
  • WeakMap
  • 只接受对象作为键名(null除外)
  • 键名是弱引用,键值任意,键名指向的对象可以被垃圾回收
  • 不能遍历,方法有get,set,has,delete,clear*
目录
相关文章
|
2天前
|
编译器 容器
哈希表模拟封装unordered_map和unordered_set
哈希表模拟封装unordered_map和unordered_set
|
2天前
|
编译器 测试技术 计算机视觉
红黑树模拟封装map和set
红黑树模拟封装map和set
|
2月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
79 18
你对Collection中Set、List、Map理解?
|
2月前
|
存储 缓存 安全
只会“有序无序”?面试官嫌弃的List、Set、Map回答!
小米,一位热衷于技术分享的程序员,通过与朋友小林的对话,详细解析了Java面试中常见的List、Set、Map三者之间的区别,不仅涵盖了它们的基本特性,还深入探讨了各自的实现原理及应用场景,帮助面试者更好地准备相关问题。
73 20
|
3月前
|
存储 C++ 容器
【C++】map、set基本用法
本文介绍了C++ STL中的`map`和`set`两种关联容器。`map`用于存储键值对,每个键唯一;而`set`存储唯一元素,不包含值。两者均基于红黑树实现,支持高效的查找、插入和删除操作。文中详细列举了它们的构造方法、迭代器、容量检查、元素修改等常用接口,并简要对比了`map`与`set`的主要差异。此外,还介绍了允许重复元素的`multiset`和`multimap`。
60 3
【C++】map、set基本用法
|
3月前
|
存储 算法 C++
【C++】unordered_map(set)
C++中的`unordered`容器(如`std::unordered_set`、`std::unordered_map`)基于哈希表实现,提供高效的查找、插入和删除操作。哈希表通过哈希函数将元素映射到特定的“桶”中,每个桶可存储一个或多个元素,以处理哈希冲突。主要组成部分包括哈希表、哈希函数、冲突处理机制、负载因子和再散列,以及迭代器。哈希函数用于计算元素的哈希值,冲突通过开链法解决,负载因子控制哈希表的扩展。迭代器支持遍历容器中的元素。`unordered_map`和`unordered_set`的插入、查找和删除操作在理想情况下时间复杂度为O(1),但在冲突较多时可能退化为O(n)。
37 5
|
3月前
|
JavaScript 前端开发 Java
除了 JavaScript,还有哪些编程语言支持 Set 类型
【10月更文挑战第30天】这些编程语言中的 `Set` 类型虽然在语法和具体实现细节上有所不同,但都提供了类似的集合操作功能,方便开发者在不同的编程场景中处理集合相关的数据和逻辑。
|
4月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
4月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
57 1
|
5月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
51 5

热门文章

最新文章