Skip to content

Commit

Permalink
ES6-Set
Browse files Browse the repository at this point in the history
  • Loading branch information
wb_zhouling03 committed Nov 30, 2023
1 parent 82c33b4 commit bd935ad
Show file tree
Hide file tree
Showing 3 changed files with 360 additions and 1 deletion.
352 changes: 352 additions & 0 deletions docs/frontend/js/es6/01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
---
title: ES6
prev:
text: 目录
link: ./index.md

next:
text: 目录
link: ./index.md
---

## Set 和 Map 数据结构

### Set

`new Set()`

Set 类似于数组,但是成员的值都是唯一的,没有重复的值。

属性:size

操作方法:add、delete、has、clear

遍历方法:keys、values、entries、forEach

#### Set 的应用场景

1. 数组去重

```js
const arr = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
console.log("通过扩展运算符将 Set 转为数组:", [...new Set(arr)]);
console.log("通过 Array.from 将 Set 转为数组:", Array.from(new Set(arr)));
```

2. 字符串去重

```js
console.log([...new Set("ababbc")].join(""));
```

3. 实现并集、交集、差集

```js
const a = new Set([1, 2, 3]);
const b = new Set([4, 3, 2]);

// 并集
const union = new Set([...a, ...b]);

// 交集
const intersect = new Set([...a].filter((x) => b.has(x)));

// 差集
const difference = new Set([...a].filter((x) => !b.has(x)));
```

### WeakSet

`new WeakSet()`

WeakSet 与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别:

1. WeakSet 的成员只能是对象(除 null),而不能是其他类型的值
2. WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用(故 WeakSet 适合临时存放一组对象,以及存放跟对象绑定的信息)
3. WeakSet 不可遍历,即不具有 size 属性,和 clear 方法,以及 Set 的遍历方法

操作方法:add、delete、has

#### WeakSet 的应用场景

1. 储存 DOM 节点,不用担心节点从文档移除时,引发内存泄漏

2. 限制原型上的方法只能通过实例调用

```js
const foos = new WeakSet();

class Foo {
constructor() {
foos.add(this);
}

method() {
if (!foos.has(this)) {
throw new TypeError("Foo.prototype.method 只能在 Foo 的实例上调用!");
}
}
}
```

### Map

`new Map()`

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键

属性:size

操作方法:set、get、has、delete、clear

遍历方法:keys、values、entries(默认迭代接口)、forEach

#### Map 与其他数据结构的互相转换

1. Map 与数组

```js
// 数组转 Map
const map = new Map([
["name", "张三"],
["title", "Author"]
]);

// Map 转数组
console.log([...map]);
```

2. Map 与对象

如果 Map 有非字符串的键名,则这个键名会被转为字符串再作为对象的键名

```js
const strMapToObj = (strMap) => {
const obj = Object.create(null);
for (let [k, v] of strMap) {
obj[k] = v;
}
return obj;
};
```

对象转 Map

```js
// 使用 Object.entries() 方法
const obj = { name: "张三", title: "Author" };
const map = new Map(Object.entries(obj));

// 自己转换
const objToStrMap = (obj) => {
const strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
};
```

3. Map 与 JSON

Map 转 JSON

```js
// Map 的键名都是字符串时,可以选择转为对象 JSON
const strMapToJson = (strMap) => {
return JSON.stringify(strMapToObj(strMap));
};

// Map 的键名有非字符串时,可以选择转为数组 JSON
const mapToArrayJson = (map) => {
return JSON.stringify([...map]);
};
```

JSON 转 Map

```js
// JSON 转为 Map,正常情况下,所有键名都是字符串
const jsonToStrMap = (jsonStr) => {
return objToStrMap(JSON.parse(jsonStr));
};

// 有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组
const jsonToMap = (jsonStr) => {
return new Map(JSON.parse(jsonStr));
};
```

### WeakMap

`new WeakMap()`

WeakMap 与 Map 的区别有两点:

1. WeakMap 只接受对象作为键名(null 除外),不接受其他类型的值作为键名
2. WeakMap 的键名所指向的对象,不计入垃圾回收机制。WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用

操作方法:set、get、has、delete

#### WeakMap 的应用场景

1. DOM 节点作为键名

```js
let myWeakmap = new WeakMap();

myWeakmap.set(document.getElementById("logo"), { timesClicked: 0 });

document.getElementById("logo").addEventListener(
"click",
function () {
let logoData = myWeakmap.get(document.getElementById("logo"));
logoData.timesClicked++;
},
false
);
```

2. 部署私有属性

```js
// 使用了 WeakMap 来存储私有属性 _counter 和 _action
const _counter = new WeakMap();
const _action = new WeakMap();

class Countdown {
/**
* 构造函数
* @param {number} counter - 计数器初始值
* @param {function} action - 计数器为0时执行的回调函数
*/
constructor(counter, action) {
_counter.set(this, counter); // 使用 WeakMap 存储私有属性 _counter
_action.set(this, action); // 使用 WeakMap 存储私有属性 _action
}

/**
* 计数器减1
*/
dec() {
let counter = _counter.get(this); // 获取私有属性 _counter 的值
if (counter < 1) return;
counter--;
_counter.set(this, counter); // 更新私有属性 _counter 的值
if (counter === 0) {
_action.get(this)(); // 执行私有属性 _action 中存储的回调函数
}
}
}
```

### WeakRef

WeakRef 对象,用于直接创建对象的弱引用

> 标准规定,一旦使用 WeakRef()创建了原始对象的弱引用,那么在本轮事件循环(event loop),原始对象肯定不会被清除,只会在后面的事件循环才会被清除
```js
const target = { name: "张三" };
const ref = new WeakRef(target);
```

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

#### WeakRef 的应用场景

1. 缓存,未被清除时可以从缓存取值,一旦清除缓存就自动失效

```js
const makeWeakCached(f) {
const cache = new Map();
return key => {
const ref = cache.get(key);
if (ref) {
const cached = ref.deref();
if (cached !== undefined) return cached;
}

const fresh = f(key);
cache.set(key, new WeakRef(fresh));
return fresh;
}
}
```

### FinalizationRegistry

清理器注册表功能 FinalizationRegistry,用来指定目标对象被垃圾回收机制清除以后,所要执行的回调函数

```js
// 1. 新建注册表实例
const registry = new FinalizationRegistry((heldValue) => {
// 回调函数的参数heldValue可以是任意类型的值,字符串、数值、布尔值、对象,甚至可以是undefined
console.log("目标对象被清除了");
});

const target = {};
// 2. 注册表实例的register()方法,用来注册所要观察的目标对象
registry.register(target, "some value", target);

// 3. 如果还想取消已经注册的回调函数,则要向register()传入第三个参数,作为标记值。这个标记值必须是对象,一般都用原始对象
registry.unregister(target);
```

> 注册表不对目标对象构成强引用,属于弱引用。因为强引用的话,原始对象就不会被垃圾回收机制清除,这就失去使用注册表的意义了
#### FinalizationRegistry 的应用场景

1. 对上述的缓存函数进行增强

```js
function makeWeakCached(f) {
const cache = new Map();
const cleanup = new FinalizationRegistry((key) => {
const ref = cache.get(key);
if (ref && !ref.deref()) cache.delete(key);
});

return (key) => {
const ref = cache.get(key);
if (ref) {
const cached = ref.deref();
if (cached !== undefined) return cached;
}

const fresh = f(key);
cache.set(key, new WeakRef(fresh));
cleanup.register(fresh, key);
return fresh;
};
}
```

2. 如果由于某种原因,Thingy 类的实例对象没有调用 release()方法,就被垃圾回收机制清除了,那么清理器就会调用回调函数#cleanup(),输出一条错误信息

```js
class Thingy {
#file;
#cleanup = (file) => {
console.error(
`The \`release\` method was never called for the \`Thingy\` for the file "${file.name}"`
);
};
#registry = new FinalizationRegistry(this.#cleanup);

constructor(filename) {
this.#file = File.open(filename);
this.#registry.register(this, this.#file, this.#file);
}

release() {
if (this.#file) {
this.#registry.unregister(this.#file);
File.close(this.#file);
this.#file = null;
}
}
}
```

> 由于无法知道清理器何时会执行,所以最好避免使用它。另外,如果浏览器窗口关闭或者进程意外退出,清理器则不会运行
7 changes: 7 additions & 0 deletions docs/frontend/js/es6/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
sidebar: false
---

## ES6

[第 1 章 Set 和 Map 数据结构](./01.md)
2 changes: 1 addition & 1 deletion docs/frontend/js/red-book/01.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: 红宝书
prev:
text: 目录
link: ./README.md
link: ./index.md

next:
text: HTML 中的 JavaScript
Expand Down

0 comments on commit bd935ad

Please sign in to comment.