认识迭代器与生成器
迭代器
迭代器是帮助我们对某个数据结构进行遍历的对象
在JS中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(irerator protocol)
- 迭代器协议定义了一系列值(无论是优先还是无限个)的标准方式
- 在
js中这个标准就是一个特定的next方法
next方法有以下要求:
- 一个无参函数或一个参数,返回一个应当拥有一些两个属性对象(
MDN文档) done(boolean)- 如果迭代器可以产生序列中的下一个值,则为
false(这等价于没有指定done这个属性) - 如果迭代器已将序列迭代完毕,则为
true,这种情况下,value是可选的,即为迭代结束后的默认返回值
- 如果迭代器可以产生序列中的下一个值,则为
value- 迭代器返回的任何
JS值,done为true时可以省略
- 迭代器返回的任何
例子
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 = ['abc', 'cba', 'nba']
const namesIterator = createArrayIterator(names)
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
可迭代对象
可迭代对象和迭代器是不同的概念,当一个对象实现了iterable protocol协议时,他就是一个可迭代对象,这个对象的要求是必须实现迭代器的方法(iterator),在代码中我们可以使用Symbol.iterator访问该对象
那么可迭代对象用在哪里呢?
JS中的语法:for of、展开语法(spread syntax)、yield*、解构赋值(Destructuring_assignment)- 创建一些对象:
new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable])- 一些方法的调用:
Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable)认识for of
for of遍历的必须是可迭代对象,而且它是根据可迭代对象里面的迭代器中的done来决定是否停止的,如果done为true则停止遍历
const iterableObj = {
names: ['abc', 'cba', 'nba'],
[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 }
}
}
}
}
}
// iterableObj对象就是一个可迭代对象
// console.log(iterableObj[Symbol.iterator]);
const iterator = iterableObj[Symbol.iterator]()
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());
// console.log(iterator.next());
// for of遍历的必须是可迭代对象,所以下面的obj是不能使用for of遍历的
// const obj = {
// name: 'zs',
// age: 18
// }
for (const item of iterableObj) {
console.log(item);
}
内置可迭代对象
事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的,如:
String、Array、Map、Set、arguments对象、NodeList集合、展开运算符、解构等
注意:
对象并不是可迭代对象,所以在使用展开运算符展开对象时也不是可迭代对象,而是在
ES9(2018)中新增的一个特性,解构对象的时候也是ES9新增的
如下例子:
1.数组其实是通过new Array()的类来创建出来的可迭代对象,我们可以打印来看一下
const names = ['abc', 'cba', 'nba']
console.log(names[Symbol.iterator]); // [Function: values]
console.log(Object.getOwnPropertyDescriptors(Array))
const iterator1 = names[Symbol.iterator]()
console.log(iterator1.next());
console.log(iterator1.next());
console.log(iterator1.next());
console.log(iterator1.next());
for (const item of names) {
console.log(item);
}
2.Map和Set都是可迭代对象
const set = new Set()
set.add(10)
set.add(20)
set.add(30)
console.log(set[Symbol.iterator]);
for (const item of set) {
console.log(item);
}
自定义可迭代类
在类中创建一个迭代器([Symbol.iterator])即可,在迭代器中定义return函数可以监听迭代器什么时候停掉了
class Classroom {
constructor(address, name, students) {
this.address = address
this.name = name
this.students = students
}
entry(newStudent) {
this.students.push(newStudent)
}
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.students.length) {
return { done: false, value: this.students[index++] }
} else {
return { done: true, value: undefined }
}
},
return: () => {
console.log('迭代器提前停止了');
// return函数也是需要return的
return { done: true, value: undefined }
}
}
}
}
const classroom = new Classroom('1栋101', '计算机教室', ['zs', 'ls'])
classroom.entry('ww')
for (const stu of classroom) {
console.log(stu);
// 可使用return来监听迭代器什么时候停掉了
if (stu === 'ls') break
}
如果是一个函数,我们怎么把它变成可迭代函数呢?
function Person() {
}
Person.prototype[Symbol.iterator] = function () { }
生成器
生成器是ES6中新增的一直函数控制、使用的方案,它可以让我们更加灵活地控制函数什么时候继续执行、暂停执行等
平时我们的函数终止的条件通常是返回值或者发生了异常
要了解生成器,我们需要先了解生成器函数,生成器函数与普通函数的区别:
- 生成器函数需要在
function的后面添加一个符号* - 生成器函数可以通过
yield关键字来控制函数的执行流程 - 生成器函数的返回值是一个
Generator(生成器)- 生成器事实上是一种特殊的迭代器(
MDN)
- 生成器事实上是一种特殊的迭代器(
例子
// 当生成器函数遇到yield的时候暂停函数执行,遇到return的时候停止函数执行
function* foo() {
console.log('函数开始执行');
const value1 = 100
console.log('第一段代码:', value1);
yield value1
// return value1
console.log('第二段代码:', value2);
yield value2 = 200
console.log('函数执行结束');
return '123'
}
// 生成器函数返回一个生成器对象generator foo函数里面的代码不会执行
const generator = foo()
console.log(generator); // Object [Generator] {}
// 如果我们想要执行生成器函数里面的代码 必须需要使用next
generator.next()
generator.next()
// generator.next()是由返回值的
console.log('返回值1:', generator.next()); // 返回值1: { value: 100, done: false }
console.log('返回值2:', generator.next()); // 返回值2: { value: undefined, done: false }
console.log('返回值3:', generator.next()); // 返回值3: { value: undefined, done: true } 后面有return 返回值3: { value: '123', done: true }
传递参数
next中的参数传递给yield,生成器函数的参数传递给函数全局
function* foo(num) {
console.log('函数开始执行');
const value1 = 100 * num
console.log('第一段代码:', value1);
const n = yield value1
const value2 = 200 * n
console.log('第二段代码:', value2);
const count = yield value2
const value3 = 300 * count
console.log('第三段代码:', value3);
yield value3
console.log('函数执行结束');
return '123'
}
// 参数传递给第一段代码
const generator = foo(5)
console.log('返回值1:', generator.next());
// 参数传递给第一个yield作为返回值,与yield后面的value1没有关系,yield value只是让{ value: undefined, done: false }里的value有值而已
console.log('返回值2:', generator.next(10));
// 参数传递给第二个yield作为返回值
console.log('返回值3:', generator.next(25));
return
generator.return(n)相当于在第一个yield后面添加了return n
function* foo(num) {
console.log('函数开始执行');
const value1 = 100 * num
console.log('第一段代码:', value1);
const n = yield value1
const value2 = 200 * n
console.log('第二段代码:', value2);
const count = yield value2
const value3 = 300 * count
console.log('第三段代码:', value3);
yield value3
console.log('函数执行结束');
return '123'
}
// 参数传递给第一段代码
const generator = foo(5)
console.log('返回值1:', generator.next());
// { value: 15, done: true } 相当于在第一个yield后面添加了return n
console.log('返回值2:', generator.return(15));
console.log('返回值3:', generator.next()); // 返回值3: { value: undefined, done: true }
throw
function* foo(num) {
console.log('函数开始执行');
const value1 = 100 * num
console.log('第一段代码:', value1);
// 如果不捕获异常 会直接崩掉
try {
yield value1
} catch (err) {
console.log('捕获到异常:', err);
// 如果在catch的时候没有yield回去,就会继续往下执行,直到下一个yield的值来当作generator.throw的返回值
yield 'aaa'
}
const value2 = 200
console.log('第二段代码:', value2);
const count = yield value2
console.log('函数执行结束');
return '123'
}
// 参数传递给第一段代码
const generator = foo(5)
const result = generator.next()
if (result.value !== 200) {
console.log(generator.throw('err message')); // 捕获到异常: err message
}
生成器替代迭代器
原本的迭代器写法代码比较多,我们可以使用生成器来替代迭代器,如下例子:
例一:
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 }
// }
// }
// }
// 第一种生成器的写法
// yield arr[index++] // { value: 'abc', done: false }
// yield arr[index++] // { value: 'nba', done: false }
// yield arr[index++] // { value: 'cba', done: false }
// 第二种生成器的写法
// for (const item of arr) {
// yield item
// }
// 第三种写法 yield* + 可迭代对象
// 事实上就是第二种写法的语法糖
yield* arr
}
const names = ['abc', 'nba', 'cba']
const namesIterator = createArrayIterator(names)
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
console.log(namesIterator.next());
例二
// 创建一个可迭代一个范围内数字的函数
function* createRangeIterator(start, end) {
let index = start
// return {
// next: function () {
// if (index < end) {
// return { done: false, value: index++ }
// } else {
// return { done: true, value: undefined }
// }
// }
// }
while (index < end) {
yield index++
}
}
const rangeIterator = createRangeIterator(10, 20)
console.log(rangeIterator.next());
console.log(rangeIterator.next());
console.log(rangeIterator.next());
例三:使用生成器重构之前的自定义可迭代类
class Classroom {
constructor(address, name, students) {
this.address = address
this.name = name
this.students = students
}
entry(newStudent) {
this.students.push(newStudent)
}
// [Symbol.iterator] = function* () {
// yield* this.students
// }
*[Symbol.iterator]() {
yield* this.students
}
}
const classroom = new Classroom('1栋101', '计算机教室', ['zs', 'ls'])
for (const stu of classroom) {
console.log(stu);
}
异步代码的处理方案
需求
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
}, 2000)
})
}
// 需求:
// 1.zs -> res: zs
// 2.res + 'aaa' -> res: zsaaa
// 1.res + 'bbb' -> res: zsaaabbb
第一种方案
requestData('zs').then(res => {
requestData(res + 'aaa').then(res => {
requestData(res + 'bbb').then(res => {
console.log(res);
})
})
})
第二种方案
requestData('zs').then(res => {
return res + 'aaa'
}).then(res => {
return res + 'bbb'
}).then(res => {
console.log(res);
})
第三种方案:Promise + generator 实现
function* getData() {
const res1 = yield requestData('zs')
const res2 = yield requestData(res1 + 'aaa')
const res3 = yield requestData(res2 + 'bbb')
console.log(res3);
}
// 手动执行生成器函数
// const generator = getData()
// generator.next().value.then(res => {
// generator.next(res).value.then(res => {
// generator.next(res).value.then(res => {
// generator.next(res)
// })
// })
// })
// genFn是一个生成器函数
function execGenerator(genFn) {
const generator = genFn()
function exec(res) {
const result = generator.next(res)
if (result.done) {
return result.value
}
result.value.then(res => {
exec(res)
})
}
exec()
}
execGenerator(getData)
第四种方案:使用co第三方包,其实就是第三种方案
npm i co
function* getData() {
const res1 = yield requestData('zs')
const res2 = yield requestData(res1 + 'aaa')
const res3 = yield requestData(res2 + 'bbb')
console.log(res3);
}
const co = require('co')
co(getData)
第五种方案: 使用await、async,这种方案的本质就是第三种方案,所以async在这里类似于function*
async function getData() {
const res1 = await requestData('zs')
const res2 = await requestData(res1 + 'aaa')
const res3 = await requestData(res2 + 'aaa')
console.log(res3);
}
getData()
异步函数async
async关键字用于声明一个异步函数,返回一个Promise对象:
async是asynchronous单词的缩写,异步、非同步sync是synchronous单词的缩写,同步、同时
基本使用,平常使用的执行顺序与普通函数是一样的
默认返回
undefined,Promise.resolve(undefine)
async function foo() {
console.log('start');
console.log('中间代码');
console.log('end');
// 1.默认返回undefined,也可以自定义 返回的值会作为resolve的参数
// return 'aaa'
// 2.返回thenable
// return {
// then: function (resolve, reject) {
// resolve('bbb')
// }
// }
// 3.返回一个新的promise
return new Promise((resolve, reject) => {
resolve('ccc')
})
}
const promise = foo()
promise.then(res => {
console.log('exec:', res);
})
抛出异常,在async异步函数中,抛出异常后,还会执行后续代码,再抛出异常,而且抛出的异常会被作为异步函数返回到Promise的reject值
async function foo() {
console.log('start');
console.log('中间代码');
throw new Error('err message')
}
foo().catch(err => {
console.log('err:', err);
})
console.log('后续代码');
await关键字
await特点:
- 通常使用
await是后面会跟上一个表达式,这个表达式会返回一个Promise - 那么
await会等到Promise的状态变成fulfilled状态后,再继续执行异步函数
await基本使用
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
}, 2000)
})
}
async function foo() {
// 里面类似于是通过then来获取到requestData的参数,再到then里是吗使用generator.next(参数)传递给yeild返回给res1
const res1 = await requestData('zs')
console.log('res1:', res1);
const res2 = await requestData('aaa')
console.log('res2:', res2);
}
foo()
await跟上其他的值 await 后面的代码相当于放到了then中执行
async function foo() {
// 1.普通值
// const res1 = await 123
// await 后面的代码相当于放到了then中执行
console.log('res1')
// 2.thenable
// const res2 = await {
// then: function (resolve, reject) {
// resolve('aaa')
// }
// }
// 3.new Promise
const res3 = await new Promise((resolve, reject) => {
resolve('bbb')
})
console.log(res3);
}
foo()
await等待reject,reject会作为异步函数(如下async foo)返回的Promsie的reject来使用
function requestData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(url)
}, 2000)
})
}
async function foo() {
const res1 = await requestData('zs')
console.log('res1:', res1);
}
foo().catch(err => {
console.log('err:', err);
})