【ES6】Set和Map数据结构

Set

WeakSet

Map

WeakMap

WeakRef

Set

Set 本身就是一个构造函数,用来生成 Set 数据结构。类似数组,值是唯一的,没有重复。

const s = new Set();
s.add("a"); // 添加
s.add("b");

console.log(s, s.size); // Set { 'a', 'b' } 2

向 Set 加入值时,不会发生类型转换,即 5 和 "5" 是不一样的值,这类似于严格相等运算符(===)。与 === 不同的是,向 Set 加入 NaN 时,会认为 NaN 等于自身,当重复加入 NaN 会去重;而严格相等运算符则认为 NaN 不等于自身。

两个对象总是不相等的。

Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。

const set = new Set([1, 2, 3, 4]);
console.log(set); // Set { 1, 2, 3, 4 }

const s = new Set([[1, 2], [3, 4]]);
console.log(s); // Set { [ 1, 2 ], [ 3, 4 ] }

数组去重

  • [...new Set(array)] 解构

  • Array.from() 将 Set 结构转为数组

// 数组去重
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log([...items]); // [1, 2, 3, 4, 5]

// 去除字符串中的重复字符
console.log([...new Set("abbbc")].join("")); // "abc"(将字符串转为 Set 结构,再解构为数组,将其转为字符串)

Set 实例的属性和方法

属性:

  • constructor 构造函数,默认就是 Set 函数

  • size 返回 Set 实例的成员总数

方法:

  • add(value) 添加值,返回 Set 结构自身。可以链式书写。

  • delete(value) 删除值,返回 true / false , 表示是否删除成功。

  • has(value) 查找值,返回 true / false , 表示是否存在该值。

  • clear() 清除所有成员,没有返回值。

遍历

  • keys() 返回键名遍历器对象。

  • values() 返回键值遍历器对象。Set 结构的默认遍历器接口就是 values() 。

  • entries() 返回键值对遍历器对象。

  • forEach() 没有返回值。

Set的遍历顺序就是插入顺序。

Set 结构没有键名,只有键值(或者说键名和键值是同一个值)。

Set 结构的实例默认可遍历,它的默认遍历器生成函数就是它的 values 方法。这意味着,可以省略 values 方法,直接用for...of循环遍历 Set 。

let set = new Set(['red', 'green', 'blue']);

for (let x of set) {
  console.log(x);
}
// red
// green
// blue

数组的 map 和 filter 方法也可以间接用于 Set 了。

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

WeakSet

与 Set 类似,也是不重复的值的集合。与 Set 不同的是:

  • WeakSet 的成员只能是对象,而不能是其他类型的值。

  • WeakSet 中的对象都是弱引用。即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

  • WeakSet 不可遍历。成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,有可能刚遍历完,成员就没了。

用处:可存储 DOM 节点,不用担心这些节点从文档移除时,会引发内存泄漏。

// new WeakSet()
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a); // WeakSet {[1, 2], [3, 4]}
typeof a; // Object 


const b = [1, 2, 3];
const weakSet = new WeakSet(b); // 报错
typeof b; // Object

// new Set()
const c = [2, 3];
const set = new Set(c); // Set { 2, 3 }

数组是类似于列表的高阶对象。 new Set(arr) 和 new WeakSet(arr) 都是将数组成员转为 Set / WeakSet 的成员存入的。第二个例子报错,是因为数组 b 的成员不是对象, WeakSet 的成员只能是对象。

三个方法:

  • add(value) 添加成员。

  • delete(value) 删除成员。返回 true / false 。

  • has(value) 表示是否存在某个值,返回 true / false 。

WeakSet 没有 size 属性,没有办法遍历它的成员。

Map

键值对集合。与对象不同的是,键的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

作为构造函数, Map 也可以接受一个数组作为参数,该数组成员是一个个表示键值对的数组。其实,任何具有 Iterator 接口、且每个成员都是一个双元素的数组(表示 key-value)的数据结构都可以作为 Map 构造函数的参数。

new Map(params) 参数只要满足:1. 具有 Iterator 接口; 2. 其成员是 ["key", "value"] 的形式(双元素的数组)。

const arr = [
  ['title',"这是标题"],
  ['content',"这是内容"]
]
const map = new Map(arr);
console.log(map); // Map { 'title' => '这是标题', 'content' => '这是内容' }

console.log(map.has("title")); // true
console.log(map.get("title")); // 这是标题

map.set("author","作者XXX");

console.log(map.size); // 3

只有对同一对象的引用,Map 才将其视为同一个键。相同值的两个实例。在 Map 中被视为两个键。

const map = new Map();

// 相同值的两个实例
const a1 = ['a'];
const a2 =['a'];
map.set(a1, '1').set(a2, '2');
console.log(map); // Map { [ 'a' ] => '1', [ 'a' ] => '2' }

map.clear();

// 同一个实例
const b = ['b'];
map.set(b, 'bbbb');
console.log(map); // Map { [ 'b' ] => 'bbbb' }

map.set(b, 'bccc');
console.log(map); // Map { [ 'b' ] => 'bccc' }

Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,如 0 和 -0 是同一个键,true 和 "true" 是不同的键。

虽然 NaN 不严格相等于自身,但 Map 将其视为同一个键。

属性和方法

属性:

  • size 返回成员总数。

方法:

  • set(key,value) 设置键-值,返回 Map 。可以采用链式写法。

  • get(key) 获取 key 对应的值 value 。找不到 key , 返回 undefined 。

  • has(key) 表示是否存在 key , 返回 true / false 。

  • delete(key) 删除键 key 。返回 true / false 。

  • clear() 清空所有成员。没有返回值。

遍历

  • keys() 返回键名的遍历器。

  • values() 返回键值的遍历器。

  • entries() 返回所有成员的遍历器。Map 的默认遍历器接口就是 entries() 。

  • forEach()

Map 的遍历顺序就是插入顺序。

const arr = [
  ['title',"这是标题"],
  ['content',"这是内容"]
]
const map = new Map(arr);

// 扩展运算符,将 Map 结构转为数组结构
console.log([...map]); // [ [ 'title', '这是标题' ], [ 'content', '这是内容' ] ]

// entries()
for(let item of map.entries()) {
  console.log(item); // [ 'title', '这是标题' ] , [ 'content', '这是内容' ]
}

// 以上 for...of 代码等同于:
for(let item of map) {
  console.log(item); // [ 'title', '这是标题' ] , [ 'content', '这是内容' ]
}

// forEach()
map.forEach((value, key) => {
  console.log(`${key}-${value}`); // title-这是标题 , content-这是内容
});

Map 转为对象:如果所有 Map 的键都是字符串,它可以无损地转为对象。如果有非字符串的键名,name这个键名会被转为字符串,再作为对象的键名。

对象转为 Map :

  • 方法一( Map 构造函数):通过 Object.entries() 将对象的每一个键值对转为一个数组,返回一个二维数组,再通过 new Map(arr) 转为 Map 结构。

  • 方法二( Map 的 set 方法):通过 Object.keys() 获取对象的属性名,作为 Map 结构的 key , 将对象的属性值作为 Map 结构的 value 。

const obj = {
  "title":"标题",
  "content":"内容"
}

console.log(Object.entries(obj)); // [ [ 'title', '标题' ], [ 'content', '内容' ] ]

// 对象转为 Map 
// 方法一:Map 构造函数
const map = new Map(Object.entries(obj));
console.log(map); // Map { 'title' => '标题', 'content' => '内容' }


// 方法二:Map 的 set 方法 添加成员
const m = new Map();
for(const k of Object.keys(obj)) {
  m.set(k, obj[k]);
}
console.log(m); // Map { 'title' => '标题', 'content' => '内容' }

Map 转为 JSON :

  • key 都是字符串的情况:将 Map 先转为对象,再通过 JSON.stringify(obj) 转为 对象 JSON 。

  • key 有非字符串的情况:将 Map 先转为数组,再通过 JSON.stringify(arr) 转为 数组 JSON 。

JSON 转为 Map :

  • key 都是字符串的情况:通过 JSON.parse(jsonStr) 转为对象,再将对象转为 Map 。

  • key 有非字符串的情况:通过 JSON.parse(jsonStr) 转为数组,再将数组转为 Map 。

WeakMap

与 Map 类似,也是用于生成键值对的集合。只接受对象作为键名。

特点同 WeakSet 。

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};

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

注意,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。所以,即使在 WeakMap 外部消除了 obj 的引用,WeakMap 内部的引用依然存在。

WeakRef

用于直接创建对象的弱引用。

let target = {};
let wr = new WeakRef(target); // 创建了一个机遇 target 的新对象 wr

上面代码中, wr 就是一个 WeakRef 的实例,属于对 target 的弱引用。

WeakRef 实例对象有一个 deref() 方法,如果原始对象存在,该方法返回原始对象。如果原始对象已经被垃圾回收机制清除,该方法返回 undefined 。

弱引用对象的一大用处,就是作为缓存,未被清除时可以从缓存取值,一旦清除缓存就自动失效。

一旦使用 WeakRef() 创建了原始对象的弱引用,那么在本轮时间循环,原始对象肯定不会被清除,只会在后面的事件循环才会被清除。

FinalizationRegistry

posted @ 2021-11-05 16:14  等等啦  阅读(123)  评论(0)    收藏  举报