一、迭代器
迭代: 迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代
迭代器: 迭代器是对某一个数据结构进行遍历的对象,符合迭代器协议(可以产生一系列值).迭代器协议有一个next方法,是一个无参或一个参数的函数,每一次迭代都会返回{done: false, value: 'xxx'}, 当所有数据迭代结束最后默认会返回{done: true, value: undefined}
let iterator = {
next: function() {
return { done: false, value: 123 }
}
}
console.log(iterator.next()) // { done: false, value: 123 }
const arr = [1, 3, 5, 6, 21]
let index = 0
let iterator1 = {
next: function() {
if(index < arr[index]) {
return { done: false, value: arr[index++] }
} else {
return { done: true, value: undefined }
}
}
}
console.log(iterator1.next()) // { done: false, value: 1 }
console.log(iterator1.next()) // { done: false, value: 3 }
console.log(iterator1.next()) // { done: false, value: 5 }
console.log(iterator1.next()) // { done: false, value: 6 }
console.log(iterator1.next()) // { done: false, value: 21 }
console.log(iterator1.next()) // { done: true, value: undefined }
console.log(iterator1.next()) // { done: true, value: undefined }
每次调用next方法,都会返回一个对象,这个对象有两个属性(done: 是否迭代结束, value: 迭代返回的结果);一旦迭代结束,再次调用next,结果默认都为{ done: true, value: undefined }
二、可迭代对象
可迭代对象: 可迭代对象也是一个对象,符合可迭代协议.可迭代协议有一个[Symbol.iterator]方法,这个方法会返回一个迭代器对象
let obj = {
name: ['小红', '小刚', '小强', '小王'],
[Symbol.iterator]: function() {
let index = 0
return {
next: () => {
if(index < this.name.length) {
return { done: false, value: this.name[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
const iterator = obj[Symbol.iterator]()
console.log(iterator.next()) // { done: false, value: '小红' }
console.log(iterator.next()) // { done: false, value: '小刚' }
console.log(iterator.next()) // { done: false, value: '小强' }
console.log(iterator.next()) // { done: false, value: '小王' }
console.log(iterator.next()) // { done: true, value: undefined }
console.log(iterator.next()) // { done: true, value: undefined }
const iterator1 = obj[Symbol.iterator]()
console.log(iterator1.next()) // { done: false, value: '小红' }
console.log(iterator1.next()) // { done: false, value: '小刚' }
1.如何判断某个数据是否为可迭代对象
function isIterable(obj) {
return typeof obj[Symbol.iterator] === 'function'
}
console.log(isIterable([1, 3, 5])) // true
console.log(isIterable('hello')) // true
console.log(isIterable(new Map())) // true
console.log(isIterable(new Set())) // true
console.log(isIterable(123)) // false
console.log(isIterable({name: 'ywq'})) // false
可迭代对象符合可迭代协议(有Symbol.iterator方法),因此我们可以通过判断对象是否有Symbol.iterator方法来确定他是否是一个可迭代对象...
我们将上面的数组、字符串更换成其他不同数据的数组、字符串,发现它仍然是一个可迭代对象. 这是为什么呢? 嗯,原来是它内部已经帮我们实现了,那我们来看一下都有哪些是官方已经帮我们实现了吧
2.常见的内置可迭代对象
// 数组
const nums = [21, 18, 7, 3]
console.log(nums[Symbol.iterator]) // [Function: values]
// 字符串
const str = '你好呀'
console.log(str[Symbol.iterator]) // [Function: [Symbol.iterator]]
// map
const map = new Map()
console.log(map[Symbol.iterator]) // [Function: entries]
// set
const set = new Set()
console.log(set[Symbol.iterator]) // [Function: values]
// arguments参数
function foo() {
console.log(arguments[Symbol.iterator]) // [Function: values]
}
foo(10, 20, 30)
我们发现 数组、字符串、map、set、arguments参数 都是内置的可迭代对象,但是这些可迭代对象有什么用呢,有什么应用场景?
3.可迭代对象的应用场景
- 3.1 场景一: for...of
const nums = [10, 3, 62, 97]
for(const item of nums) {
console.log(item) // 依次打印 10 3 62 97
}
- 3.2 场景二: 展开语法
const arr = ['aaa', 'bbb', 'ccc']
const myIterable = {
data: 'ywq',
[Symbol.iterator]: function() {
let index = 0
return {
next: () => {
if(index < this.data.length) {
return { done: false, value: this.data[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
const newArr = [...arr, ...myIterable]
console.log(newArr) // [ 'aaa', 'bbb', 'ccc', 'y', 'w', 'q' ]
- 3.3 场景三: 解构语法
let arr = ['小张', '小杨', '小王']
let [ name1, name2 ] = arr
console.log(name1, name2) // 小张 小杨
- 3.4 场景四: 创建其他的数据 Map/Set接收可迭代对象
let arr = [1, 3, 9, 12]
let obj = { name: 'ywq', age: 18 }
const map = new Map(arr) // Map(3) { 'ywq' => 18, 'wqy' => 12, 'qwy' => 21 }
const map = new Map(obj) // TypeError: object is not iterable
const set = new Set(arr) // Set(4) { 1, 3, 9, 12 }
const set = new Set(obj) // TypeError: object is not iterable
- 3.5 场景五: Promise.all/Promsie.allSettle .....
const myIterable = {
data: 'ywq',
[Symbol.iterator]: function() {
let index = 0
return {
next: () => {
if(index < this.data.length) {
return { done: false, value: this.data[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
Promise.all(myIterable).then(res => { console.log(res) }) // [ 'y', 'w', 'q' ]
上面是可迭代对象的应用场景,你可能会产生以下疑问...
-
为什么for...of可以遍历可迭代对象
可迭代对象实现了[Symbol.iterator]方法. 数据结构具有Symbol.iterator属性,就可以进行遍历. 对象的Symbol.iterator属性指向其默认遍历器方法
使用for...of时,会自动调用[Symbol.iterator]方法,返回给我们一个遍历器对象,然后将遍历器对象的value返回给我们
-
为什么普通的对象也可以有展开和解构语法
eg: let obj = { name: 'ywq', age: 18 } const newObj = {...obj} const { name, age } = obj
我们通过上面可以判断obj并不是一个可迭代对象.但是为什么obj也可以进行展开和解构. 这是因为对象的展开和解构使用的并不是可迭代操作,具体是怎么实现的,可以找度娘
三、生成器
生成器: 可以控制函数什么时候继续执行、什么时候暂停执行
- 1.生成器是一个对象,是一个特殊的迭代器
- 2.生成器函数的返回值是生成器(Generator)
- 3.生成器函数的定义: function* 函数名称() {}; 通过yield控制函数的暂停和执行
下面我们来写一段简单的生成器函数吧
function* test() {
let num1 = 2
console.log('num1:', num1) // // num1: 2
yield
let num2 = 3
console.log('num2:', num2) // num2: 3
yield
console.log(num1 + num2) // 5
}
const generator = test()
console.log(generator.next()) // { value: undefined, done: false }
console.log(generator.next()) // { value: undefined, done: false }
console.log(generator.next()) // { value: undefined, done: true }
console.log(generator.next()) // { value: undefined, done: true }
生成器函数和普通函数的区别是:声明时多了一个*.
当调用test函数时,函数内部的代码并不会执行,而是会返回给我们一个生成器.
通过next调用这个生成器,第一个yield前面的代码会被执行;再次使用next,第一个yield和第二个yield之间的代码会被执行;第三次调用next,第二个yield和第三个yield之间的代码会被执行...依此类推
我们发现调用next方法打印的结果和前面的迭代器是一样的.因此可以得出生成器也是一个特殊的迭代器.
当第三次调用next方法时,迭代已经结束,done为true;但是前面两次的迭代结果都为undefined,我们怎么使其有值呢?
1.迭代器对象的结果
function* test() {
let num1 = 2
console.log('num1:', num1)
// return '哈哈哈' // ②
yield // ③
// yield '哈哈哈2' // ④
let num2 = 3
console.log('num2:', num2)
yield
console.log(num1 + num2)
return undefined
// return '嘿嘿嘿' // ①
}
const generator = test()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
一个函数默认的返回值为undefined,我们猜想最后一次迭代结束的结果为undefined,我们将①放开,发现结果正如我们所想.
return的结果就是我们迭代对象返回的结果,那我们将②放开.发现done为true了,后面再调用next方法done仍然为true.
我们得出了一个结论:
1. yield 是特殊的 return
2. yield 会暂停生成器的执行; return 会终止生成器的执行
2.生成器的next传递参数 -> generator.next(arg)
function* foo(num) {
let v1 = 3 * num
let aaa = yield
let v2 = v1 * aaa
let bbb = yield v2
let v3 = v2 + bbb
return v3
}
const generator = foo(6)
console.log(generator.next()) // { value: undefined, done: false }
console.log(generator.next(2)) // { value: 36, done: false }
console.log(generator.next(5)) // { value: 41, done: true }
普通的函数调用可以传递参数,next当然也可以咯
当在next方法中传入参数时,参数会被显示在上一个yield的返回值中(第二次调用next, 参数是aaa;第三次调用next, 参数是bbb;依此类推);
第一次调用next方法,和我们普通函数的传参一样.通常第一次调用next是不传递参数的.
3.生成器的return终止执行 -> generator.return(msg)
function* demo() {
let str = '你好'
const name = yield str
let str1 = `我是${name}`
const age = yield str1
console.log(111)
let str2 = `我今年${age}了`
const address = yield str2
let str3 = `我住在${address}`
yield str3
return '今天我很开心'
}
const generator = demo()
const i1 = generator.next()
console.log(i1) // { value: '你好', done: false }
const i2 = generator.next('ywq')
console.log(i2) // { value: '我是ywq', done: false }
const f = generator.return('终止了')
console.log(f) // { value: '终止了', done: true }
console.log(generator.next()) // { value: undefined, done: true }
console.log(generator.next()) // { value: undefined, done: true }
console.log(generator.next()) // { value: undefined, done: true }
上面代码我们发现,当调用return方法时,生成器被终止了,done为true. 后面再次调用done仍为true.
代码中的console甚至不会执行,相当于在前后yield之间直接return了
4.生成器的throw抛出异常 -> generator.throw(errMsg)
function* fn(num) {
let value1 = 21 + num
const p1 = yield value1
let value2 = value1 + p1
// const p2 = yield value2
try {
const p2 = yield value2
} catch(err) {
console.log('err:', err)
}
console.log('执行后续代码')
}
const generator = fn(6)
const v1 = generator.next()
console.log(v1)
const v2 = generator.next(10)
console.log(v2)
// const v3 = generator.next(3) // ①
const v3 = generator.throw(3) // ②
console.log(v3)
throw通常用于条件语句中,条件不符合就执行throw,符合的话,就按照正常逻辑执行
5.替代迭代器
function IteratorFn(arr) {
let index = 0
return {
next: function() {
if(index < arr.length) return { done: false, value: arr[index++]}
return { done: true, value: undefined }
}
}
}
let arr = [1, 21, 3, 72, 6]
const iterator = IteratorFn(arr)
将上面代码替换为生成器
方法一: yield 值
function* IteratorFn(arr) {
yield 1
yield 21
yield 3
yield 72
yield 6
}
let arr = [1, 21, 3, 72, 6]
const generator = IteratorFn(arr)
方法二: for...of
function* IteratorFn(arr) {
for(const item of arr) {
yield item
}
}
let arr = [1, 21, 3, 72, 6]
const generator = IteratorFn(arr)
方法三: yield* 可迭代对象
function* IteratorFn(arr) {
yield* arr
}
let arr = [1, 21, 3, 72, 6]
const generator = IteratorFn(arr)
6.异步代码解决方案
场景: 获取某省某市某区某校某年级某班某人的考试成绩
// 公共代码
let commonUrl = 'http://hnstykscg.com/getScore?'
function getScore(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
}, 2000)
})
}
getScore(commonUrl + 'provinceID=p41').then(res => {
if(有该省份) {
getScore(commonUrl + 'provinceID=p41&&cityID=ct03').then(res => {
if(有该城市) {
getScore(commonUrl + 'provinceID=p41&&cityID=ct03&&areaID=a06').then(res => {
if(有该区) {
getScore(commonUrl + 'provinceID=p41&&cityID=ct03&&areaID=a06&&schoolID=s001').then(res => {
// ...
})
}
})
}
})
}
})
上面代码是假设的一个场景,我们多次调用了同一个函数,造成了'回调地狱'...当然上面场景还是有限的.总之,就是各种不舒服.那既然我们学习了生成器,就可以对其进行优化了
function* foo() {
let res = yield getScore(commonUrl + 'provinceID=p41')
if(有该省) {
let res1 = yield getScore(commonUrl + 'provinceID=p41&&cityID=ct03')
if(有该市) {
let res2 = yield getScore(commonUrl + 'provinceID=p41&&cityID=ct03&&areaID=a06')
// ...
}
}
}
function autoExec(fn) {
const generator = fn()
function innerFn(res) {
const result = generator.next(res)
if(result.done) return result.value
result.value.then(res => {
innerFn(res)
})
}
innerFn()
}
autoExec(foo)
先获取生成器函数的生成器,然后调用next获取结果,如果迭代完成返回结果,如果没有,将结果作为参数传入next中...
async function foo() {
let res = await getScore(commonUrl + 'provinceID=p41')
if(有该省) {
let res1 = await getScore(commonUrl + 'provinceID=p41&&cityID=ct03')
if(有该市) {
let res2 = await getScore(commonUrl + 'provinceID=p41&&cityID=ct03&&areaID=a06')
// ...
}
}
}
async/await实际上是方法二的语法糖,只是将*替换为了async并修改了位置,将yield替换为await.
四、async/await异步函数
1.什么是异步函数
async function demo() {
console.log('我被执行了1')
console.log('我被执行了2')
console.log('我被执行了3')
}
demo()
在普通函数的前面加上async,那么这个函数就可以称之为异步函数.和普通函数一样进行调用,代码会依次执行.
2.异步函数的执行过程
console.log('aaaaaa')
async function demo() {
console.log('我被执行了1')
console.log('我被执行了2')
console.log('我被执行了3')
}
console.log('bbbbbbb')
demo()
console.log('ccccc')
如果异步函数没有其他特殊的代码,和普通的函数执行顺序是没有什么区别的.不要认为只要是函数前面有async,它就会延迟执行.
3.异步函数和普通函数的区别
额,写法的区别就不说了
3.1 返回值是一个Promise
// 异步函数
async function foo() {
return 333 // ① res: 333
return '哈哈哈' // ② res: 哈哈哈
return {
then: function(resolve, reject) {
resolve('mmm')
}
} // ③ res: mmm
return new Promise((resolve, reject) => {
setTimeout(() => { resolve('嘿嘿嘿') }, 300)
}) // ④ res: 嘿嘿嘿
}
const p = foo()
p.then(res => {
console.log('res:', res)
}).catch(err => {
console.log('err:', err)
})
- 异步函数的返回值是一个Promise
- 函数return时,then方法就会被执行.并且return的结果就是Promise的结果
3.2 抛出异常不影响后续代码执行
async function foo() {
console.log('aaaa')
throw new Error('未知错误')
console.log('bbb')
console.log('ccc')
}
foo().catch(err => {
console.log('err1:', err)
})
console.log('哈哈哈')
console.log('嘿嘿嘿')
- 代码中抛出异常,会作为Promise的reject结果
- 后续代码仍然可以运行,普通函数则会终止执行
3.3 await关键字
async function foo() {
console.log('aaa')
const res = await 321 // ① bbb 321
const res = await {
then: function(resolve, reject) {
resolve('mmm')
}
} // ② bbb mmm
const res = await new Promise((resolve, reject) => {
setTimeout(() => { resolve('嘿嘿嘿') }, 300) // ③ bbb 嘿嘿嘿
setTimeout(() => { reject('哼哼哼') }, 300) // ④ err: 嘿嘿嘿
})
console.log('bbb:', res)
}
foo().catch(err => {
console.log('err:', err)
})
- await只能和async搭配使用
- await后面可跟普通值,也可跟thenable、Promise、表达式
- 一旦reject时,reject的结果会作为异步函数返回的Promise的结果