一、什么是迭代器
迭代器,是确使用户可在容器对象上遍历的对象,使用该接口无需关心对象的内部实现细节。迭代器本身就是一个对象,而这个对象可以帮我们对某个数据结构进行遍历的对象。其本质就是一个对象,符合迭代器协议(iterator protocol),在js中这个标准就是一个特定的next方法。说人话就是必须要有一个next函数。而next函数返回包含done和value两个属性的一个对象。
迭代器协议(iterator protocol):
-
调用next函数返回一个对象,其对象中包含两个属性
-
done(完成):它的值为布尔类型,也就是
true/false
。如果这个迭代器没有迭代完成即返回{done:false}
, 当这个迭代器完成了即返回{done:true}
。 -
value(值):它可以返回js中的任何值。
1、迭代器对象:
const names = ['wbb', 'lbb', 'fbb']
let index = 0
// 迭代器对象
const namesIterator = {
next: function() {
if(index < names.length) {
return {done: false, value: names[index++]}
} else {
return {done: true, value: undefined}
}
}
}
console.log(namesIterator.next()) // { done: false, value: 'wbb' }
console.log(namesIterator.next()) // { done: false, value: 'lbb' }
console.log(namesIterator.next()) // { done: false, value: 'fbb' }
console.log(namesIterator.next()) // { done: true, value: undefined }
以上代码在对象中存在next函数,且返回了done和value两个属性,因为是一个迭代器对象。但是我们可以考虑一下,如果我们需要有多个迭代器对象呢?所以我们就需要封装一个生成迭代器对象的函数,减少冗余。
function createArrayIterator(arr) {
let index = 0
return {
next: function() {
if(index < arr.length) {
return {done: false, value: arr[index++]}
} else {
return {done: true, value: undefined}
}
}
}
}
const names = ['wbb', 'lbb', 'fbb']
const nameIterator = createArrayIterator(names)
console.log(namesIterator.next()) // { done: false, value: 'wbb' }
console.log(namesIterator.next()) // { done: false, value: 'lbb' }
console.log(namesIterator.next()) // { done: false, value: 'fbb' }
console.log(namesIterator.next()) // { done: true, value: undefined }
总结:
- 迭代器身是一个容器对象,可以帮助我们对某个数据结构进行遍历;
- 而这个对象需要符合迭代器协议, 在js中这个标准就是一个特定的next方法;
- next是 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象;
- done(boolean),为false时,value有值,可继续遍历,为true时,value可省略,代表遍历结束。
2、可迭代对象
首先就是一个对象,且符合可迭代对象协议(iterable protocol)
实现了[Symbol.iterator]
为key的方法,且这个方法返回了一个迭代器对象。
const iterableObj = {
names: ['abc', 'zxc', 'qwe'],
[Symbol.iterator]: function() {
let index = 0
return {
next: () => {
if(index < this.names.length) {
return {done: false, value: this.names[index++]}
} else {
return {done: true, value: undefined}
}
}
}
}
}
那可迭代对象有什么好处呢? 有什么应用场景呢?
首先for...of可以遍历的东西必须是一个可迭代对象,for...of本质就是使用的迭代器。
对象不是一个可迭代对象
const obj = {
name: 'wbb',
age: 18,
sex: 'female'
}
// 报错了
for(let value of obj) {
console.log(value) // Uncaught TypeError: obj is not iterable
}
数组本身就是一个可迭代对象
const names = ['wbb', 'lbb', 'fbb']
for(let value of names) {
console.log(value) // 'wbb' 'lbb' 'fbb'
}
const animals = ['spider', 'panda', 'bear']
//可迭代对象上有Symbol.iterator方法,并且返回一个迭代器对象
const iter = animals[Symbol.iterator]()
// 迭代器对象上有next方法
console.log(iter.next()) // { value: 'spider', done: false }
console.log(iter.next()) // { value: 'panda', done: false }
console.log(iter.next()) // { value: 'bear', done: false }
console.log(iter.next()) // { value: undefined, done: true }
3、原生可迭代对象(JS内置):
String
Array
Set
NodeList 类数组对象
Arguments 类数组对象
Map
4、为对象添加可迭代方法
const obj = {
name: 'wbb',
age: 18,
sex: 'female'
}
const obj2 = {
name: 'fbb',
age: 32,
sex: 'male'
}
// 1、在对象原型添加[Symbol.iterator]的方法
Object.prototype[Symbol.iterator] = function() {
let keys = []
let index = 0
for(let key in this) {
keys.push(key)
}
// 2、返回一个迭代器对象
return {
// 这里必须要用箭头函数,不然其this的指向是不明确的。
// (ps: 若有兴趣,可一键三连,后面再写一篇关于this指向问题的文章)
next: () => {
if(index > keys.length) {
return { done: true, value: undefined }
} else {
let result = { done: false, value: this[keys[index]] }
index ++
return result
}
}
}
}
for(let key of obj) {
console.log(key)
}
// 输出
// wbb、18、female、undefined
for(let key of obj2) {
console.log(key)
}
// 输出
// fbb、32、male、undefined
二、什么是生成器
它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。
function foo() {
const value1 = 100
console.log(value1)
const value2 = 200
console.log(value2)
const value3 = 300
console.log(value3)
}
foo()
// 比如以上我想执行到200就暂停呢,这时候就可以使用生成器函数,对函数进行精准的控制
生成器函数也是一个函数,但是和普通的函数有一些区别:
- 生成器函数需要在function的后面加一个符号:*
- 生成器函数可以通过yield关键字来控制函数的执行流程
- 生成器函数的返回值是一个Generator(生成器)
生成器事实上是一种特殊的迭代器: MDN:Instead, they return a special type of iterator, called a Generator
1、生成器的使用
function* foo() {
// 第一段代码:
console.log('函数开始执行')
const value1 = 100
console.log(value1)
yield
// 第二段代码:
const value2 = 200
console.log(value2)
// return "123" // 如果有return,执行第二个next就停止结束了, {done: true, value: 123}
yield value2
// 第三段代码:
console.log('函数执行结束')
}
// foo() //一行代码都不会执行
// 调用生成器函数时, 会给我们返回一个生成器对象,是一个特殊的迭代器
const generator = foo()
// 开始执行第一段代码
generator.next()
// 输出:
// '函数开始执行',
// '100'
// 开始执行第二段代码
console.log("第二段代码:", generator.next()) // 有返回值,是一个对象。输出:'第二段代码: { value: 200, done: false }',由此可见生成器是一个特殊的迭代器
// 输出:
// 200
// 开始执行第三段代码
console.log("第三段代码:", generator.next()) // 第三段代码: { value: undefined, done: true }, 结束后默认返回一个状态为true,value值为undefined的迭代器对象
// 输出:
// 函数执行结束
generator.next() // 什么都没输出
// 技巧: 一个yield对应一个next
2、给生成器传值
赋值是给前一个yield的左边赋值
function* foo(num) {
console.log("函数开始执行~")
const value1 = 100 * num // 第一个一般很少传
const n = yield value1 // 这个n是4
const value2 = 200 * n
const count = yield value2 // 这个count是5
const value3 = 300 * count
const count1 = yield value3 // 这个num是6
console.log("函数执行结束~", count1) // 输出'函数执行结束~,num'
return "123"
}
const generator = foo(2) // 在这里可以传第一个
console.log(generator.next(3)) // 这里传是没用的,因为没有上一个yield了,拿不到的, generator.next()的返回值是{value: 200, done: false}
console.log(generator.next(4)) // generator.next()的返回值是{value: 800, done: false}
console.log(generator.next(5)) // generator.next()的返回值是{value: 1500, done: false}
console.log(generator.next(6)) // generator.next()的返回值是{value: '123', done: true}
console.log(generator.next()) // generator.next()的返回值是{value: undefined, done: true}
3、生成器替代迭代器
function* createArrayIterator(arr) {
// 第一种写法
// yield 'abc'
// yield 'wbb'
// yield 'fbb'
// 第二种写法
// for(const item of arr) {
// yield item
// }
// 第三种写法 yield* 后面 跟的是可迭代对象
yield* arr
}
const names = ['abc', 'wbb', 'fbb']
const namesIterator = createArrayIterator(names)
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
// 依次输出:
// { value: 'abc', done: false }
// { value: 'wbb', done: false }
// { value: 'fbb', done: false }
// { value: undefined, done: true }
总结:
- 可以控制函数的暂停执行和继续执行
- 通过function* bar() {} 这种形式定义
- 不会立马执行,而是返回一个生成器,生成器是一个特殊的迭代器对象
- yield 关键字可以控制函数分段执行
- 调用返回生成器的next方法进行执行