-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
220 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |