前言
前文再续,书接上一回。上周一个偶然的机会写的一篇this指向,超出了我的意外,获得了许多的赞和保藏,所以我决议从现在开始,坚持每周用心写一篇有关于前端的技术文章(ES6和难理解的常识点)。一边用来稳固自己以前学习的常识,一边分享给咱们,让咱们也能有所收成。别的今天是中秋节,祝咱们中秋节高兴,中秋节要吃月饼。
1. 什么是迭代器?
概念(维基百科): 迭代器(iterator),是确使用户可在容器目标(container,例如链表或数组)上遍访的目标[1][2][3],设计人员使用此接口无需关心容器目标的内存分配的完结细节。
JS中的迭代器
- 其实质便是一个目标,契合迭代器协议(iterator protocol)
- 迭代器协议
- 其目标回来一个next函数
- 调用next函数回来一个目标,其目标中包含两个特点
-
done
(完结),它的值为布尔类型,也便是true/false
。- 假设这个迭代器没有迭代完结即回来
{done:false}
- 当这个迭代器完结了即回来
{done:true}
- 假设这个迭代器没有迭代完结即回来
-
value
(值),它能够回来js中的任何值,TS中表示可为:value:any
类型
-
1.1 迭代器的根本完结
考虑以下代码:
let index = 0
const bears = ['ice', 'panda', 'grizzly']
let iterator = {
next() {
if (index < bears.length) {
return { done: false, value: bears[index++] }
}
return { done: true, value: undefined }
}
}
console.log(iterator.next()) //{ done: false, value: 'ice' }
console.log(iterator.next()) //{ done: false, value: 'panda' }
console.log(iterator.next()) //{ done: false, value: 'grizzly' }
console.log(iterator.next()) //{ done: true, value: undefined }
- 是一个目标,完结了
next
办法,next
办法回来了一个目标,有done
特点和value
特点,且key
的值类型也为boolean
或any
,契合迭代器协议,是一个妥妥的迭代器没跑了。 - 坏处
- 违背了高内聚思维,明明
index
和iterator
目标是属于一个整体,我却使用了全局变量,从V8引擎的GC,可达性(也便是标记铲除)来看,假设bears = null
,不手动设置为null很有或许会形成内存走漏,而且内聚性低。 - 假设我要创建一百个迭代器目标呢? 那我就自己界说一百遍吗?肯定过错的,咱们要把它封装起来,这样内聚性又高,又能进行复用,一箭双雕,一举两得,真的是
very beautiful
,very 高雅。
- 违背了高内聚思维,明明
1.2 迭代器的封装完结
考虑一下代码:
const bears = ['ice', 'panda', 'grizzly']
function createArrIterator(arr) {
let index = 0
let _iterator = {
next() {
if (index < arr.length) {
return { done: false, value: arr[index++] }
}
return { done: true, value: undefined }
}
}
return _iterator
}
let iter = createArrIterator(bears)
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
- 内聚性非常高,尽最大或许进行了复用,削减冗余代码
2. 什么是可迭代目标
迭代器目标和可迭代目标是一个不同的东西,尽管它们存在关联,而且面试的时分经常面这些概念,废话不多说,咱们直接进入主题。
- 首先便是一个目标,且契合可迭代目标协议(iterable protocol)
- 可迭代目标协议
- 完结了[Symbol.iterator]为key的办法,且这个办法回来了一个迭代器目标
- 绕了一大圈总算把概念搞明白了,那可迭代目标有什么优点呢? 有什么应用场景呢?
- for of 的时分,其实质便是调用的这个函数,也便是[Symbol.iterator]为key的办法
2.1 原生可迭代目标(JS内置)
- String
- Array
- Set
- NodeList 类数组目标
- Arguments 类数组目标
- Map
2.1.1 部分for of 演示
let str = 'The Three Bears'
const bears = ['ice', 'panda', 'grizzly']
for( let text of str) {
console.log(text) //字符串每个遍历打印
}
for( let bear of bears) {
console.log(bear)
}
//ice panda grizzly
2.1.2 查看内置的[Symbol.iterator]办法
- 上面给咱们举例了许多可迭代目标,那它们必定是契合可迭代目标协议的,考虑以下代码
const bears = ['ice', 'panda', 'grizzly']
//数组的Symbol.iterator办法
const iter = bears[Symbol.iterator]()
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
const nickName = 'ice'
//字符串的Symbol.iterator办法
const strIter = nickName[Symbol.iterator]()
console.log(strIter.next())
console.log(strIter.next())
console.log(strIter.next())
console.log(strIter.next())
2.2 可迭代目标的完结
let info = {
bears: ['ice', 'panda', 'grizzly'],
[Symbol.iterator]: function() {
let index = 0
let _iterator = {
//这儿一定要箭头函数,或许手动保存上层效果域的this
next: () => {
if (index < this.bears.length) {
return { done: false, value: this.bears[index++] }
}
return { done: true, value: undefined }
}
}
return _iterator
}
}
let iter = info[Symbol.iterator]()
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
//契合可迭代目标协议 就能够使用 for of 遍历
for (let bear of info) {
console.log(bear)
}
//ice panda grizzly
- 契合可迭代目标协议,是一个目标,有
[Symbol.iterator]
办法,而且这个办法回来了一个迭代器目标。 - 当我使用for of 遍历,就会自动的调用这个办法。
2.3 可迭代目标的应用
- for of
- 打开语法
- 解构语法
- promise.all(iterable)
- promise.race(iterable)
- Array.from(iterable)
- …
2.4 自界说类迭代完结
class myInfo {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
[Symbol.iterator]() {
let index = 0
let _iterator = {
next: () => {
const friends = this.friends
if (index < friends.length) {
return {done: false, value: friends[index++]}
}
return {done: true, value: undefined}
}
}
return _iterator
}
}
const info = new myInfo('ice', 22, ['panda','grizzly'])
for (let bear of info) {
console.log(bear)
}
//panda
//grizzly
- 此事例仅仅简略的对
friends
进行了迭代,你也能够迭代你想要的全部东西… - 记住此事例,后续咱们会对这个事例进行重构,高雅的会让你不能用言语来形容。
3. 生成器函数
生成器是ES6新增的一种能够对函数操控的方案,能灵敏的操控函数的暂停履行,持续履行等。
生成器函数和普通函数的不同
- 界说: 普通函数
function
界说,生成器函数function*
,要在后面加*
- 生成器函数能够经过
yield
来操控函数的履行 - 生成器函数回来一个生成器(generator),生成器是一个特别的迭代器
3.1 生成器函数根本完结
function* bar() {
console.log('fn run')
}
bar()
- 咱们会发现,这个函数竟然没有履行。咱们前面说过,它是一个生成器函数,它的回来值是一个生成器,同时也是一个特别的迭代器,所以跟普通函数比较,好像暂停了,那怎么让他履行呢?接下来咱们进一步探讨。
3.2 生成器函数单次履行
function* bar() {
console.log('fn run')
}
const generator = bar()
console.log(generator.next())
//fn run
//{ value: undefined, done: true }
- 回来了一个生成器,咱们调用next办法就能够让函数履行,而且next办法是有回来值的,咱们上面讲迭代器的时分有探讨过,而value没有回来值那便是undefined。那上面说的yield关键字在哪,到底是怎么操控函数的呢?是怎么用的呢?
3.3 生成器函数屡次履行
function* bar() {
console.log('fn run start')
yield 100
console.log('fn run...')
yield 200
console.log('fn run end')
return 300
}
const generator = bar()
//1. 履行到第一个yield,暂停之后,而且把yield的回来值 传入到value中
console.log(generator.next())
//2. 履行到第一个yield,暂停之后,而且把yield的回来值 传入到value中
console.log(generator.next())
//3. 履行剩下代码
console.log(generator.next())
//打印结果:
//fn run start
//{done:false, value: 100}
//fn run...
//{done:false, value: 200}
//fn run end
//{done:true, value: 300}
- 现在咱们恍然大悟,每当调用next办法的时分,代码就会开始履行,履行到
yield x
,后就会暂停,等候下一次调用next持续往下履行,周而复始,没有了yield
关键字,进行最终一次next调用回来done:true
。
3.4 生成器函数的分段传参
我有一个需求,已然生成器能操控函数分段履行,我要你完结一个分段传参。
考虑以下代码:
function* bar(nickName) {
const str1 = yield nickName
const str2 = yield str1 + nickName
return str2 + str1 + nickName
}
const generator = bar('ice')
console.log(generator.next())
console.log(generator.next('panda '))
console.log(generator.next('grizzly '))
console.log(generator.next())
// { value: 'ice', done: false }
// { value: 'panda ice', done: false }
// { value: 'grizzly panda ice', done: true }
// { value: undefined, done: true }
- 假设没有接触过这样的代码会比较古怪
- 当我调用next函数的时分,yield的左边是能够承受参数的,也并不是一切的next办法的实参都能传递到生成器函数内部
- yield左边接纳的,是第2次调用next传入的实参,那第一次传入的就没有yield关键字接纳,一切只有当我调用bar函数的时分传入。
- 最终一次next调用,传入的参数我也调用不了,因为没有yield关键字能够接纳了。
- 许多开发者会疑惑,这样写有什么用呢? 可读性还差,但是在处理异步数据的时分就非常有用了,后续会在promise中文章中介绍。
3.5 生成器替代迭代器
前面咱们讲到,生成器是一个特别的迭代器,那生成器必定是能够替代迭代器目标的,考虑以下代码。
let bears = ['ice','panda','grizzly']
function* createArrIterator(bears) {
for (let bear of bears) {
yield bear
}
}
const generator = createArrIterator(bears)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
其实这儿还有一种语法糖的写法yield*
- yield* 顺次迭代这个可迭代目标,相当于遍历拿出每一项 yield item(伪代码)
考虑以下代码:
let bears = ['ice','panda','grizzly']
function* createArrIterator(bears) {
yield* bears
}
const generator = createArrIterator(bears)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
- 顺次迭代这个可迭代目标,回来每个item值
4. 可迭代目标的终极封装
class myInfo {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
*[Symbol.iterator]() {
yield* this.friends
}
}
const info = new myInfo('ice', 22, ['panda','grizzly'])
for (let bear of info) {
console.log(bear)
}
//panda
//grizzly
- 回忆以下可迭代目标协议
- 是一个目标而且有[Symbol.iterator]办法
- 这个办法回来一个迭代器目标 生成器函数回来一个生成器,是一个特别的迭代器
5. 总结
5.1 迭代器目标
- 实质便是一个目标,要契合迭代器协议
- 有自己对应的next办法,next办规律回来一组数据
{done:boolean, value:any}
5.2 可迭代目标
- 实质便是目标,要契合可迭代目标协议
- 有
[Symbol.iterator]
办法,而且调用这个办法回来一个迭代器
5.3 生成器函数
- 能够操控函数的暂停履行和持续履行
- 经过
function* bar() {}
这种方式界说 - 不会立马履行,而是回来一个生成器,生成器是一个特别的迭代器目标
-
yield
关键字能够操控函数分段履行 - 调用回来生成器的next办法进行履行
6. 结语
- 坚持自律简略二字,却贯穿了我的前半生,我希望我能坚持做一件事,每天都能有所进步。