重学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*
目录
相关文章
|
1天前
|
前端开发 JavaScript 程序员
Javascript:forEach、map、filter、reduce、reduceRight
Javascript:forEach、map、filter、reduce、reduceRight
|
2天前
|
JavaScript 前端开发 Java
ES6 逐点突破系列 -- Set Map,工作感悟,完美收官
ES6 逐点突破系列 -- Set Map,工作感悟,完美收官
|
2天前
|
存储 缓存 JavaScript
JavaScript中的Set和Map:理解与使用
JavaScript中的Set和Map:理解与使用
|
2天前
|
JavaScript 前端开发
JavaScript中的map和foreach:理解与使用
JavaScript中的map和foreach:理解与使用
|
3天前
|
存储 JavaScript
ES6+新特性-Symbol与Set/Map数据结构
ES6 引入了三种新的数据结构:Symbol、Set和Map。Symbol是唯一且不可变的值,常用于定义对象的独特属性;Set存储不重复值,适合数组去重;Map则是键值对集合,键可为任意类型,提供了更灵活的存储方式。这些新数据结构提供了更高效的操作手段,分别解决了属性命名冲突、数据去重和复杂键值对存储的问题。示例展示了如何使用Symbol、Set和Map进行基本操作。
|
3天前
|
JavaScript 前端开发
JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式
【5月更文挑战第11天】JavaScript 的数组方法 map()、filter() 和 reduce() 提供了函数式编程处理元素的方式。map() 用于创建新数组,其中元素是原数组元素经过指定函数转换后的结果;filter() 则筛选出通过特定条件的元素生成新数组;reduce() 将数组元素累计为单一值。这三个方法使代码更简洁易读,例如:map() 可用于数组元素乘以 2,filter() 用于选取偶数,reduce() 计算数组元素之和。
11 2
|
3天前
|
存储 编译器 C++
C++:map&set 对红黑树的封装
C++:map&set 对红黑树的封装
11 1
|
3天前
|
存储
Map与Set的经典OJ题
Map与Set的经典OJ题
13 3
|
3天前
|
存储 自然语言处理 容器
Map与Set
Map与Set
12 3
|
3天前
|
存储 C++ 容器
C++:STL - set & map
C++:STL - set & map
16 4