Skip to content

Commit

Permalink
262
Browse files Browse the repository at this point in the history
  • Loading branch information
ascoders committed Nov 7, 2022
1 parent 5da581f commit 84ea4fe
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 1 deletion.
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

前端界的好文精读,每周更新!

最新精读:<a href="./前沿技术/261.%E7%B2%BE%E8%AF%BB%E3%80%8ARest%20vs%20Spread%20%E8%AF%AD%E6%B3%95%E3%80%8B.md">261.精读《Rest vs Spread 语法》</a>
最新精读:<a href="./前沿技术/262.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%BF%AD%E4%BB%A3%E5%99%A8%20Iterable%E3%80%8B.md">262.精读《迭代器 Iterable》</a>

素材来源:[周刊参考池](https://github.com/ascoders/weekly/issues/2)

Expand Down Expand Up @@ -200,6 +200,7 @@
- <a href="./前沿技术/259.%E7%B2%BE%E8%AF%BB%E3%80%8AHeadless%20%E7%BB%84%E4%BB%B6%E7%94%A8%E6%B3%95%E4%B8%8E%E5%8E%9F%E7%90%86%E3%80%8B.md">259.精读《Headless 组件用法与原理》</a>
- <a href="./前沿技术/260.%E7%B2%BE%E8%AF%BB%E3%80%8A%E5%A6%82%E4%BD%95%E4%B8%BA%20TS%20%E7%B1%BB%E5%9E%8B%E5%86%99%E5%8D%95%E6%B5%8B%E3%80%8B.md">260.精读《如何为 TS 类型写单测》</a>
- <a href="./前沿技术/261.%E7%B2%BE%E8%AF%BB%E3%80%8ARest%20vs%20Spread%20%E8%AF%AD%E6%B3%95%E3%80%8B.md">261.精读《Rest vs Spread 语法》</a>
- <a href="./前沿技术/262.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%BF%AD%E4%BB%A3%E5%99%A8%20Iterable%E3%80%8B.md">262.精读《迭代器 Iterable》</a>

### TS 类型体操

Expand Down
218 changes: 218 additions & 0 deletions 前沿技术/262.精读《迭代器 Iterable》.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
本周精读的文章是 [Iterables](https://javascript.info/iterable)[Iteration protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols),按照为什么需要迭代器、迭代器是如何设计的,我们还能怎么利用迭代器展开来讲。

## 概述

### 为什么需要迭代器

因为用 `for ... of` 循环数组非常方便,但如果仅数组才支持这个语法就太过于麻烦了,比如我们自然会希望 `for ... of` 可以遍历字符串的每个字符,希望 `new Set([1, 2, 3])` 可以快速初始化一个新的 `Set`

以上提到的能力 JS 都支持,那么为什么 JS 引擎知道字符串该如何遍历?如何知道数组 `[1, 2, 3]``Set` 类型每一个 Key 之间的对应关系?**实现这些功能背后的原理就是迭代器(Iterables)**

因为 `Array``Set` 都是可迭代的,所以他们都可以被 `for ... of` 遍历,JS 引擎也自然知道他们之间相互转换的关系。

### 迭代器是如何设计的

有两种定义迭代器的方法,分别是独立定义与合并在对象里定义。

#### 独立定义

为对象拓展 `[Symbol.iterator]` 属性即可。之所以规范采用 `[Symbol.iterator]` 是为了防止普通的字面量 Key 与对象自身的 OwnProperties 冲突:

```ts
const obj = {}
obj[Symbol.iterator] = function() {
return {
someValue: 1,

next() {
// 可通过 this.someValue 访问与修改该值,可定义任意数量的变量作为迭代过程中的辅助变量
if (...) {
return { done: false, value: this.current++ } // 表示迭代还没完,当前值为 value
}
return { done: true } // 表示迭代完毕
}
};
};
```

`for ... of` 时,只要没有读到 `done: true` 就会一直循环。

#### 合并在对象里定义

简化一点可以将迭代定义在对象里:

```ts
let range = {
from: 1,
to: 5,

[Symbol.iterator]() {
this.current = this.from;
return this;
},

next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
},
};
```

这么定义的缺点是并行迭代对象时可能触发 BUG,因为每个迭代间共享了同一份状态变量。

### 手动控制迭代

迭代器也可以自定义触发,方法如下:

```ts
const myObj = iterable[Symbol.iterator]();
myObj.next(); // { value: 1, done: false }
myObj.next(); // { value: 2, done: false }
myObj.next(); // { value: 3, done: false }
myObj.next(); // { done: true }
```

`done``true` 时你就知道迭代停止了。手动控制迭代的好处是,你可以自由控制 `next()` 触发的时机与频率,甚至提前终止,带来了更大的自由度。

### 可迭代与 ArrayLike 的区别

如果不了解迭代器,可能会以为 `for of` 是通过下标访问的,也就会把一个对象能否用 `obj[index]` 访问与是否可迭代弄混。

读过上面的介绍,你应该理解到可迭代的原因是实现了 `[Symbol.iterator]`,而与对象是否是数组,或者 ArrayLike 没有关系。

```ts
// 该对象可迭代,不是 ArrayLike
const range = {
from: 1,
to: 5,
};

range[Symbol.iterator] = function () {
// ...
};
```

```ts
// 该对象不可迭代,是 ArrayLike
const range = {
"0": "a",
"1": "b",
length: 2,
};
```

```ts
// 该对象可迭代,是 ArrayLike
const range = {
"0": "a",
"1": "b",
length: 2,
};

range[Symbol.iterator] = function () {
// ...
};
```

顺带一提,js 的数组类型就是典型既可迭代,又属于 ArrayLike 的类型。

## 精读

### 可迭代的内置类型

`String``Array``TypedArray``Map``Set` 都支持迭代,其表现为:

```ts
const myString = "abc";
for (let val of myString) {
console.log(val);
} // 'a', 'b', 'c'

const myArr = ["a", "b", "c"];
for (let val of myArr) {
console.log(val);
} // 'a', 'b', 'c'

const myMap = [
["1", "a"],
["2", "b"],
["3", "c"],
];
for (let val of myMap) {
console.log(val);
} // ['1', 'a'], ['2', 'b'], ['3', 'c']

const mySet = new Set(["a", "b", "c"]);
for (let val of mySet) {
console.log(val);
} // 'a', 'b', 'c'
```

### 可迭代对象可以适用哪些 API

可迭代对象首先支持上文提到的 `for ... of``for ... in` 语法。

另外就是许多内置函数的入参支持传入可迭代对象:`Map()` `WeakMap()` `Set()` `WeakSet()` `Promise.all()` `Promise.allSettled()` `Promise.race()` `Promise.any()` `Array.from()`

`Array.from` 语法,可以将可迭代对象变成真正的数组,该数组的下标就是执行 `next()` 的次数,值就是 `next().value`

```ts
Array.from(new Set(["1", "2", "3"])); // ['1', '2', '3']
```

`generator` 也是迭代器的一种,属于异步迭代器,所以你甚至可以将 `yield` 一个 `generator` 函数作为上面这些内置函数的参数:

```ts
new Set(
(function* () {
yield 1;
yield 2;
yield 3;
})()
);
```

最后一种就是上周精读提到的 [精读《Rest vs Spread 语法》](https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/261.%E7%B2%BE%E8%AF%BB%E3%80%8ARest%20vs%20Spread%20%E8%AF%AD%E6%B3%95%E3%80%8B.md),解构本质也是用迭代器进行运算的:

```ts
const range = {
from: 1,
to: 5,

[Symbol.iterator]() {
this.current = this.from;
return this;
},

next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
},
};

[...range]; // [1, 2, 3, 4, 5]
```

## 总结

生活中,我们可以数苹果的数量,数大楼的窗户,数杂乱的衣物有多少个,其实不同的场景这些对象的排列形式都不同,甚至老师在黑板写的 `0~10`,我们按照这 4 个字符也能从 1 数到 10,这背后的原理抽象到程序里就是迭代器。

一个对象黑盒,不论内部怎么实现,如果我们能按照顺序数出内部结构,那么这个对象就是可迭代的,这就是 `[Symbol.iterator]` 定义要解决的问题。

生活中与程序中都有一些默认的迭代器,可以仔细领悟一下它们之间的关系。

> 讨论地址是:[精读《迭代器 Iterable》· Issue #448 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/448)
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**

> 关注 **前端精读微信公众号**
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">

> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)

0 comments on commit 84ea4fe

Please sign in to comment.