1.迭代器
1.1 什么是迭代器?
迭代器:本质上是一个对象,符合迭代器协议。
迭代器协议:
1.其对象要返回一个next函数
2.调用next函数返回一个对象,这个对象包含两个属性
2.1 done(完成),值为Boolean,返回true/false
2.1.1 如果这个迭代器没有迭代完成,返回{done:false}
2.1.2 如果这个迭代器迭代完成,返回{done:true}
2.2 val(当前值),可以返回js中的任意值
1.2 迭代器的基本实现
根据迭代器的定义,我们需要实现next函数并根据需要返回{done:xxx,val:xxx}
// 定义索引
let index = 0
// 定义原始迭代对象
const arr = ['Alice', 'Bob', 'John']
// 定义一个迭代器对象
let iterator = {
// next函数
next() {
// - 没有迭代完成,返回done:false,继续迭代
if (index < arr.length) {
return { done: false, value: arr[index++] }
} else {
// -- 迭代完成,done:true
return { done: true, value: undefined }
}
}
}
console.log(iterator.next())//{ done: false, value: 'Alice' }
console.log(iterator.next())//{ done: false, value: 'Bob' }
console.log(iterator.next())//{ done: false, value: 'John' }
console.log(iterator.next())//{ done: true, value: undefined }
说明1:迭代器是一个对象,实现next方法,next方法返回一个新的对象,对象中done用于观察迭代是否完成,对象中的值用于表示迭代当前值。既有next又有done和val,符合迭代器协议。
说明2:此时定义的一些变量如index、arr都暴露到了全局,违背了高内聚的开发思想,导致当前迭代器效率特别低,综上对目标迭代器进行封装。
1.3 迭代器的封装实现
function createIterator(iterator) {
let index = 0
let _iterator = {
next() {
if (index < iterator.length) {
return { done: false, value: iterator[index++] }
} else {
return { done: true, value: undefined }
}
}
}
return _iterator
}
let iter = createIterator(arr)
console.log(iter.next())//{ done: false, value: 'Alice' }
console.log(iter.next())//{ done: false, value: 'Bob' }
console.log(iter.next())//{ done: false, value: 'John' }
console.log(iter.next())//{ done: true, value: undefined }
2.可迭代对象
2.1 什么是可迭代对象
首先需要明确,迭代器对象和可迭代对象是完全不同的东西,尽管他们之间会存在联系,但是还是不要将这二者进行混淆。
可迭代对象:
1.是一个对象,符合可迭代协议
2.可迭代协议是什么?
2.1 实现了[Symbol.iterator]作为key的方法,且这个方法返回一个迭代器对象
3.for...of...本质上就是调用了这个[Symbol.iterator]为key的方法
2.2 原生可迭代对象(JS内置)
- String
- Array
- Set
- Map
- NodeList类数组对象
- Arguments类数组对象
2.2.1 部分可迭代对象for...of...展示
let str = 'Alice'
let arr = [1, 2, 3, 4, 5]
let map = new Map([['name', 'Alice'], ['age', 18]])
for (const element of str) {
console.log(element);//会依次打印
}
for (const element of arr) {
console.log(element);
}
for (const element of map) {
console.log(element);
}
2.2.2 查看内置的[Symbol.iterator]方法
由2.2.1我们可以迭代这些对象,既然可以迭代,那必然是符合可迭代对象协议
const arr = ['Alice', 'Bob', 'John']
// 数组的[Symbol.iterator]方法
let arrIterator = arr[Symbol.iterator]()
console.log(arrIterator.next())//{ value: 'Alice', done: false }
console.log(arrIterator.next())//{ value: 'Bob', done: false }
console.log(arrIterator.next())//{ value: 'John', done: false }
console.log(arrIterator.next())//{ value: undefined, done: true }
const str = 'Alice'
// 字符串的[Symbol.iterator]方法
let strIterator = str[Symbol.iterator]()
console.log(strIterator.next())//{ value: 'A', done: false }
console.log(strIterator.next())//{ value: 'l', done: false }
console.log(strIterator.next())//{ value: 'i', done: false }
console.log(strIterator.next())//{ value: 'c', done: false }
console.log(strIterator.next())//{ value: 'e', done: false }
console.log(strIterator.next())//{ value: undefined, done: true }
2.3 可迭代对象的实现
既然我们需要对对象可迭代,那么必须实现[Symbol.iterator]方法,并且这个方法返回一个迭代器对象。
// 定义一个可迭代对象
let iterableObject = {
arr: ['Alice', 'Bob', 'John'],
[Symbol.iterator]: function () {
let index = 0
console.log(this)
/**
{
arr: [ 'Alice', 'Bob', 'John' ],
[Symbol(Symbol.iterator)]: [Function: [Symbol.iterator]]
}
*/
let _iterator = {
next: () => {
if (this.arr.length > index) {
return { done: false, value: this.arr[index++] }
} else {
return { done: true, value: undefined }
}
}
}
return _iterator
}
}
let arrIter = iterableObject[Symbol.iterator]()
console.log(arrIter.next())//{ done: false, value: 'Alice' }
console.log(arrIter.next())//{ done: false, value: 'Bob' }
console.log(arrIter.next())//{ done: false, value: 'John' }
console.log(arrIter.next())//{ done: true, value: undefined }
当我们使用for...of...对目标对象进行迭代的时候,它会自动执行[Symbol.iterator]方法进行迭代。
2.4 可迭代对象的使用场景
- for...of...
- 展开语法
- 结构语法
- promise.all(iterable)
- promise.race(iterable)
- Array.from(iterable)
- ...
2.5 自定义可迭代类实现
由上面几节,我们实现了字面量可迭代对象的实现,接下来我们来设计一个可迭代类的实现。
class MyInfo {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
[Symbol.iterator]() {
let index = 0
let _iterator = {
next: () => {
if (this.friends.length > index) {
return { done: false, value: this.friends[index++] }
} else {
return { done: true, value: undefined }
}
}
}
return _iterator
}
}
const myInfo = new MyInfo('Alice', 18, ['Amy', 'Lihua'])
for (const element of myInfo) {
console.log(element)//Amy Lihua
}
这个MyInfo类只是简单的实现了对friends的迭代,当然你也可以迭代所有你想要迭代的对象
3.生成器
生成器是ES6新增的一种可以对函数进行控制的方案,它可以控制函数的暂停执行和继续执行。
生成器函数和普通函数的不同:
- 普通函数是function定义,生成器是function*定义
- 生成器函数可以通过yield来控制函数的执行
- 生成器函数返回一个生成器(Generator),生成器是一种特殊的迭代器
3.1 生成器函数的基本实现
function* test() {
console.log('xxx');
}
// 为什么调用的函数的执行,却没有出现打印结果?
test()
console.log(test())//Object [Generator] {}
首先我们会发现,函数突然暂停了,没有项普通函数那样正常的执行打印。
我们前面也说过,他是生成器函数,执行的结果会返回一个生成器,同时也是一个特殊的迭代器。
所以和普通函数相比,他的执行好像被强行暂停了,那怎么让它继续执行呢?我们慢慢来探讨。
3.2 生成器函数的单次执行
function* test() {
console.log('xxx');
}
// 为什么调用的函数的执行,却没有出现打印结果?
test()
console.log(test())//Object [Generator] {}
console.log(test().next())//xxx { value: undefined, done: true }
我们根据上面函数的执行结果,发现调用test()函数我们得到了一个生成器,生成器有个next方法,又能让函数继续执行从而打印了xxx,并且还有一段很熟悉的东西: { value: undefined, done: true } 。
这个对象我们是不是在哪见过?
没错,就是迭代器执行next函数的结果,现在就能体会为什么说生成器是个特殊的迭代器了。
这个对象表示这次迭代是最后一次,后面没值可继续迭代了
那话说回来,yield关键字去哪了?我们马上就来探讨。
3.3 生成器的多次执行
既然单次调用就是执行一次迭代器,那我们用yield看看是不是真的控制生成器的执行。
function* generator() {
console.log('开始了');
yield 1
console.log('进行中');
console.log('多执行一次');
yield 2
console.log('结束了');
yield 3
}
generator() //还是依然无事发生
let genera = generator()
// 执行了第一个yield,并且将yield后的值当做第一个迭代器的值,并且告知后面还能继续迭代
console.log(genera.next());//开始了 { value: 1, done: false }
// 有点意思,第二次执行了yield 1 和yield 2之间的所有语句 不是仅仅执行一行
console.log(genera.next());//进行中 多执行一次 { value: 2, done: false }
// 那不出意外 这里就是获得 结束了 以及 value为3的迭代器值
console.log(genera.next());// 结束了 { value: 3, done: false }
// 果然 和我们预料的一致,但是这个done 还是false 后面还有?呢我们在继续执行一次
console.log(genera.next());//{ value: undefined, done: true }
// 这次再没有了
到这,我们发现了,这那叫生成器,这不就是迭代器么?
没错,这就是迭代器,只不过迭代的进行 通过函数内的yield关键字来当做开关
yield x 这个x会被当做此次迭代器的值,有没有yield会被当做done的结果 告诉我们后面 能不能继续迭代
既然叫做生成器函数,呢么这个函数的参数是如何传递的?
3.4 生成器函数的传参
我们先按照函数的传参去尝试一下
function* name(name1, name2) {
let firstName = yield name1
let lastName = yield name2
console.log(firstName, lastName);
return firstName + lastName
}
console.log(name('Kaselin', 'Alice'));//Object [Generator] {}
// 还是一样的,是个生成器 函数的执行被暂停了
let getName = name('Keselin', 'Alice')
console.log(getName.next());//{ value: 'Kaselin', done: false }
// 第一次执行,获取到了name1的值
console.log(getName.next());//{ value: 'Alice', done: false }
// 第二层执行,获取到了name2的值
console.log(getName.next());//{ value: NaN, done: true }
// 喂 怎么返回了NaN?按理说不应该是字符串的拼接?
// 我们加上log函数,去尝试打印firstName和lastName,得到的都是undefined,
// 这NaN原来是undefined+undefined得来的
// 由此我们得出生成器函数的参数是分段传递的
function* name(name1, name2) {
let firstName = yield name1
let lastName = yield name2
return firstName + lastName + 1
}
console.log(name('Kaselin', 'Alice'));//Object [Generator] {}
// 还是一样的,是个生成器 函数的执行被暂停了
let getName = name('Keselin', 'Alice')
console.log(getName.next());//{ value: 'Keselin', done: false }
console.log(getName.next('Amy'));//{ value: 'Alice', done: false }
console.log(getName.next('Bob'));//{ value: 'AmyBob1', done: true }
console.log(getName.next());//{ value: undefined, done: true }
console.log(getName.next());//{ value: undefined, done: true }
// 第一次next传入的Amy被舍弃,第三次未传参变为undefined
console.log(getName.next('Amy'));//{ value: 'Keselin', done: false }
console.log(getName.next('Bob'));//{ value: 'Alice', done: false}
console.log(getName.next());//{ value: 'Bobundefined1', done: true }
console.log(getName.next());//{ value: undefined, done: true }
console.log(getName.next());//{ value: undefined, done: true }
// 这里的第三次NaN实际上是因为消耗最后一个yield以及yield被消耗完的呢一次没有传参都是undefined导致的
console.log(getName.next());//{ value: 'Keselin', done: false }
console.log(getName.next());//{ value: 'Alice', done: false }
console.log(getName.next());//{ value: NaN, done: true }
console.log(getName.next('Amy'));//{ value: undefined, done: true }
console.log(getName.next('Bob'));//{ value: undefined, done: true }
// 由此我们可以得出这个传值的一般规律:
// 1.有yield接受的情况下,传入的任何参数都不会被接收
// 2.本次执行next函数消耗最后一个yield的时候,当前next传入的参数会赋值给第一个变量
// 3.yield被消耗完,还会再接受一次next传入的参数
// 4.错过这个传参窗口,后续所有参数都会舍弃
这里的生成器函数传参需要多尝试理解一下,是一个很让人费解的机制。
-
第一次next()调用的参数被忽略(历史原因)
-
后续next()调用的参数成为上一个yield表达式的返回值
- 每个next()调用推进到下一个yield或return语句
- 生成器完成后,所有next()调用返回
{value: undefined, done: true}
3.5 生成器代替迭代器
我们一直在说,生成器是一种特殊的迭代器,那生成器必定替代迭代器对象的。
let friends = ['Alice', 'Bob', 'Amy']
function* createFriendsIterator(friendsArr) {
for (const friend of friendsArr) {
yield friend
}
}
// let create = createFriendsIterator(friends)
// 还是个生成器
// console.log(create) // Object [Generator] {}
// console.log(create.next()) // { value: 'Alice', done: false }
// console.log(create.next()) // { value: 'Bob', done: false }
// console.log(create.next()) // { value: 'Amy', done: false }
// console.log(create.next()) // { value: undefined, done: true }
// 熟悉的结果,熟悉的配方,都是符合我们的预期的
// 能不能写的更优雅一点?还真有 yield*
// yield* 这个写法会自动为可迭代对象的结果追加yield
function* createFriendsIterator(friendsArr) {
yield* friendsArr
}
let create = createFriendsIterator(friends)
console.log(create) // Object [Generator] {}
console.log(create.next()) // { value: 'Alice', done: false }
console.log(create.next()) // { value: 'Bob', done: false }
console.log(create.next()) // { value: 'Amy', done: false }
console.log(create.next()) // { value: undefined, done: true }
// 有没有发现结果和上面一样?
既然我们学会了这么优雅的写法来获取迭代器,那我们可以不可以对最开始的那个MyInfo类进行改造?
4.生成器改造可迭代对象
class MyInfo {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
*[Symbol.iterator]() {
yield* this.friends
}
}
let myInfo = new MyInfo('Alice', 18, ['Bob', 'John', 'Amy'])
for (const info of myInfo) {
console.log(info);
// Bob
// John
// Amy
}
-------------------------------------------------------------
class MyInfo {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
[Symbol.iterator]() {
let index = 0
let _iterator = {
next: () => {
if (this.friends.length > index) {
return { done: false, value: this.friends[index++] }
} else {
return { done: true, value: undefined }
}
}
}
return _iterator
}
}
const myInfo = new MyInfo('Alice', 18, ['Amy', 'Lihua'])
for (const element of myInfo) {
console.log(element)//Amy Lihua
}
上下一对比,简直了。
5.结论
- 迭代器需要满足迭代器协议,拥有自己独特的next方法,返回{done:Boolean,value:any}
- 可迭代对象需要符合可迭代协议,即拥有[Symbol.iterator]方法,执行这个方法后返回一个迭代器
- 生成器通过function*(){ }声明,yield关键字控制执行,并且返回迭代器
- 生成器是一种特殊的迭代器
6.结语
坚持是一件特别有趣的事,当你日复一日年复一年的积累,时间的复利就会在你的身上发生。
如有问题欢迎大家一起讨论学习,共勉。(^▽^)