概述
为什么要将迭代器、生成器和异步放在一块来讲述呢?因为他们存在肯定的分割。WTF,这不是废话吗?对的,但,是也不是。接下来咱们一个个来看吧
迭代器
什么是迭代器呢?迭代器是一种可能在容器对象上(例如数组或链表等)遍历数据汇合的对象。迭代器对象提供了一种对立的形式来拜访数据汇合中的元素,而无需裸露其底层示意。通过迭代器,能够一次拜访汇合中的每一个元素,而不须要晓得其中元素的个数或其底层存储形式。迭代器通常应用 next() 办法来拜访汇合中的下一个元素,并返回一个蕴含元素值和是否还有更多元素的对象。
next办法有如下要求:
一个无参数或者一个参数的函数,返回一个该当领有以下两个属性的对象:
done(boolean)
- 如果迭代器能够产生下一个值,则为false。(这等价于没有指定done这个属性。)
- 如果迭代器已将序列迭代结束,则为true。这种状况下,value是可选的,如果他仍然存在,即为迭代过后的默认返回值(undefined)
- value
迭代器返回的任何JS值,done为true时可省略。
怎么样,到这里的话,咱们是不是对迭代器曾经有了大抵的理解。但这些实践还是有些让人头大对不对?没关系,让咱们一起看一个例子就能有更加粗浅的了解了。看代码吧
// 封装一个生成迭代器对象的函数function createArrayIterator(arr) { let indexNum = 0 // 1.首先,依照定义,迭代器是一个对象 const arrayIterator = { // 2. 其次,这个对象必须实现一个next办法(接管0或1个参数) next: function() { // 3. 而后,这个办法必须返回一个对象 // 这个对象蕴含两个参数: // done --> boolean // value --> 具体的值或undefined if(indexNum < arr.length) return { done: false, value: arr[indexNum++] } else // 此时value可省略 return { done: true, value: undefined } } } return arrayIterator }const nums = [3, 2, 9]const numsIterator = createArrayIterator(nums)console.log(numsIterator.next()) // { done: false, value: 3}console.log(numsIterator.next()) // { done: false, value: 2}console.log(numsIterator.next()) // { done: false, value: 9}console.log(numsIterator.next()) // { done: true, value: undefined}
到这里应该齐全了解了吧!
接下来,咱们一起来看一下可迭代对象
可迭代对象
什么是可迭代对象呢?它和迭代器是不同的概念,不过也与其有分割。当一个对象实现了iterable protocol时,它就是一个可迭代对象。具体就是这个对象必须实现@@iterator办法,在代码中应用Symbol.iterator属性来拜访,该属性返回一个迭代器对象。
这样说是不是还是有些形象,老规矩,看看代码就懂了
// 将infos变成一个可迭代对象// 1.首先,依照定义,可迭代对象必须是一个对象。WTF???哈哈,没错,废话文学也要用起来const info = { friends: ['Kobe', 'Messi', 'Taylor'], // 2.其次,这个对象必须领有一个Symbol.iterator属性。 // 3.再者,这个属性对应一个办法,这个办法就叫作@@iterator办法 [Symbol.iterator]: function() { let index = 0 // 4.最初,这个办法必须返回一个迭代器对象 return { // 5.接下来就是迭代器对象那一番操作了 next: () => { if(index < this.friends.length) return { done: false, value: this.friends[index++] } else return { done: true } } } } }const infoIterator = info[Symbol.iterator]()console.log(infoIterator.next()) // { done: false, value: 'Kobe' }console.log(infoIterator.next()) // { done: false, value: 'Messi' }console.log(infoIterator.next()) // { done: false, value: 'Taylor' }console.log(infoIterator.next()) // { done: true }
怎么样,迭代器与可迭代对象是不是都很简略。那么接下来一起来看看生成器吧
生成器
在理解生成器之前,让咱们先来理解一下生成器函数
那什么是生成器函数呢?生成器函数也是一个函数,然而它和一般的函数存在以下区别:
- 首先,生成器函数须要在function的前面加一个符号:
*
- 其次,生成器函数能够通过yield关键字来管制函数的执行流程
最初,生成器函数的返回值是一个Generator(生成器)
- 事实上,生成器是一种非凡的迭代器
- 要想执行生成器函数外部的代码,须要生成器对象调用他的next办法
- 当遇到yield时,就会中断执行
生成器函数和生成器对象的根本应用
function* countNumbers() { let i = 0; while (i < 3) { yield i i++ }}const generatorObj = countNumbers()console.log(generatorObj.next()) // { value: 0, done: false }console.log(generatorObj.next()) // { value: 1, done: false }console.log(generatorObj.next()) // { value: 2, done: false }console.log(generatorObj.next()) // { value: undefined, done: true }
生成器函数返回值和参数以及生成器提前结束
// 1.定义了一个生成器函数function* foo(name1) { console.log("执行外部代码:1111", name1) console.log("执行外部代码:2222", name1) const name2 = yield "aaaa" console.log("执行外部代码:3333", name2) console.log("执行外部代码:4444", name2) const name3 = yield "bbbb" // return "bbbb" console.log("执行外部代码:5555", name3) console.log("执行外部代码:6666", name3) yield "cccc" return undefined}// 2.调用生成器函数, 返回一个 生成器对象const generator = foo("next1")// 调用next办法// console.log(generator.next()) // { done: false, value: "aaaa" }// console.log(generator.next()) // { done: false, value: "bbbb" }// console.log(generator.next()) // { done: false, value: "cccc" }// console.log(generator.next()) // {done: true, value: undefined}// 3.在两头地位间接return, 后果// console.log(generator.next()) // { done: false, value: "aaaa" }// console.log(generator.next()) // { done: true, value: "bbbb" }// console.log(generator.next()) // { done: true, value: undefined }// console.log(generator.next()) // { done: true, value: undefined }// console.log(generator.next()) // { done: true, value: undefined }// console.log(generator.next()) // { done: true, value: undefined }// 4.给函数每次执行的时候, 传入参数// 执行外部代码:1111 next1// 执行外部代码:2222 next1// {value: 'aaaa', done: false}console.log(generator.next())// 执行外部代码:3333 next2// 执行外部代码:4444 next2// {value: 'bbbb', done: false}console.log(generator.next("next2"))// 执行外部代码:5555 next3// 执行外部代码:6666 next3// {value: 'cccc', done: false}console.log(generator.next("next3"))// {value: undefined, done: true}console.log(generator.next())
生成器抛出异样 -- throw函数
生成器函数能够通过throw语句抛出异样,该语句能够将异样对象传递给生成器函数,生成器函数会在yield表达式处暂停并且将控制权交给调用方。在调用方中,能够应用try...catch语句捕捉这个异样,也能够让异样持续向上传递。
tips:
- 抛出异样后能够在生成器函数中捕捉异样
然而在catch语句中不能再yield新的值了,然而能够在catch语句外应用yield持续中断函数的执行
function* myGenerator() { try { yield 1 yield 2 throw new Error("Oops! Something went wrong.") yield 3 } catch (e) { console.log(e.message) yield 4 } yield 5}const g = myGenerator()console.log(g.next().value) // 1console.log(g.next().value) // 2// Exception thrown by caller. { value: 4, done: false }console.log(g.throw(new Error("Exception thrown by caller.")))console.log(g.next().value) // 5console.log(g.next()) // { value: undefined, done: true }
yield* 生产一个可迭代对象
能够用
yield*
生产一个可迭代对象,这个时候相当于是一种yield的语法糖,只不过会顺次迭代这个可迭代对象,每次迭代其中的一个值。
function* createArrayIterator(arr) { yield* arr}const arr = [3, 1, 2]const gen = createArrayIterator(arr)console.log(gen.next()) // { value: 3, done: false }console.log(gen.next()) // { value: 1, done: false }console.log(gen.next()) // { value: 2, done: false }console.log(gen.next()) // { value: undefined, done: true }
生成器代替迭代器的利用场景
对迭代器代码进行重构
function* createArrayIterator(arr) { for(let i = 0; i < arr.length; i++) { yield arr[i] }}const nums = [2,3,4]const gen = createArrayIterator(nums)console.log(gen.next()) // { value: 2, done: false }console.log(gen.next()) // { value: 3, done: false }console.log(gen.next()) // { value: 4, done: false }console.log(gen.next()) // { value: undefined, done: true }
生成某个范畴内的值
function* createRangeGenerator(start, end) { for(let i = start; i < end; i++) { yield i }}const rangeGen = createRangeGenerator(6, 9)console.log(rangeGen.next()) // { value: 6, done: false }console.log(rangeGen.next()) // { value: 7, done: false }console.log(rangeGen.next()) // { value: 8, done: false }console.log(rangeGen.next()) // { value: undefined, done: true }
异步函数
异步
首先,什么是异步呢?异步是指一种非阻塞的编程形式,让代码在执行事件或网络操作时,不会阻塞主线程的其余工作。通常在JS中,网络申请和事件处理都是异步的。常见的异步编程形式包含回调函数、Promise、async/await等。
异步函数
那什么是异步函数呢?JS中,用关键字async申明的函数就称之为异步函数。 异步函数能够有多种形式,但外围是必须应用关键字async进行申明,须要留神的是,异步函数内的代码仍是同步的,也就是从上至下一次执行。如果遇到其余异步函数就要具体问题具体分析了,可能会波及到微工作、宏工作、事件队列、事件循环等,这个当前咱们联合Promise,async/await等常识再独自开一期唠唠。
async foo1() {}const foo2 = async function() {}const foo3 = async () => {}class Foo { async foo() {}}
异步函数的返回值
异步函数内的代码会同步执行,这点和一般函数是统一的。
但异步函数有返回值时,和一般函数会有区别:
- 异步函数的返回值相当于被包裹到Promise.resolve()中
- 如果异步函数的返回值是Promise,状态将会由新的Promise决定
- 如果异步函数的返回值是一个对象,并且实现了thenable,那么状态就会由对象的then办法来决定
// 返回值的区别// 1.一般函数// function foo1() {// return 123// }// foo1() // 123// 2.异步函数async function foo2() { // 1.返回一个一般的值 // -> Promise.resolve(321) // then中失去 321 return ["abc", "cba", "nba"] // 2.返回一个Promise // return new Promise((resolve, reject) => { // setTimeout(() => { // resolve("aaa") // then中失去 "aaa" // }, 3000) // }) // 3.返回一个thenable对象 // return { // then: function(resolve, reject) { // resolve("bbb") // then中失去 "bbb" // } // }}foo2().then(res => { console.log("res:", res) // res: (3) ['abc', 'cba', 'nba']})
异步函数的异样
还有一点须要留神的是,在async中抛出了异样,程序并不会像一般函数一样报错,而是会作为Promise的reject来传递。
// 如果异步函数中有抛出异样(产生了谬误), 这个异样不会被浏览器立刻解决// 会进行如下解决: Promise.reject(error)async function foo() { console.log("---------1"); // 1 console.log("---------2"); // 2 // "abc".filter(); throw new Error("async function error"); // 以下的代码都不再执行了 console.log("---------3"); // return new Promise((resolve, reject) => { // reject("err rejected") // }) return 123;}// promise -> pending -> fulfilled/rejectedfoo() .then((res) => { console.log("res:", res); }) .catch((err) => { console.log("err:", err); // err: Error: async function error console.log("继续执行其余的逻辑代码"); })
async与await联合应用
异步函数的另一个非凡之处在于能够在它的外部应用await关键字,而普通函数中是不能够的。
那await关键字有什么特点呢?
- 通常await前面会跟上一个表达式,这个表达式会返回一个Promise
- await会等到Promise的状态变成fulfilled之后继续执行异步函数
tip:await应用条件: 必须
在异步函数中应用,在同步函数中应用会报错,终止程序执行。
function bar() { console.log("bar function") return new Promise(resolve => { setTimeout(() => { resolve(123) }, 3000) })}async function foo() { console.log("-------") // await后续返回一个Promise // 会期待Promise有后果之后, 才继续执行后续的代码 const res1 = await bar() console.log("await前面的代码:", res1) const res2 = await bar() console.log("await前面的代码:", res2) console.log("+++++++")}// -------// bar function// 3s后输入// await前面的代码: 123// bar function// 6s后再输入// await前面的代码: 123// +++++++foo()
异步解决计划优化
说到这里,不晓得你有没有一些纳闷?生成器与async/await与咱们要说的异步解决计划的优化有什么关系呢?
别着急,让咱们先来看一下传统的异步解决计划和基于Promise的解决计划,而后再看一下基于生成器的解决计划和基于async/await的解决计划,咱们就能晓得他们的优缺点了。
// 封装申请的办法: url -> promise(result)function requestData(url) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(url) }, 2000) })} /* 需要: 1.向服务器发送三次网络申请获取数据 2.第二次申请依赖第一次申请的后果 3.第三次申请依赖第二次申请的后果 4.第三次申请完结后能力失去最终想要的后果*/
1. 传统的异步解决计划:导致回调天堂
// 形式一: 层层嵌套(回调天堂 callback hell)function getData() { // 1.第一次申请 requestData("aaa").then(res1 => { console.log("第一次后果:", res1) // 2.第二次申请 requestData(res1 + "bbb").then(res2 => { console.log("第二次后果:", res2) // 3.第三次申请 requestData(res2 + "ccc").then(res3 => { console.log("第三次后果:", res3) }) }) })}
2. 基于Promise的解决计划
// 形式二: 应用Promise进行重构(解决回调天堂)// 链式调用function getData() { requestData("aaa").then(res1 => { console.log("第一次后果:", res1) return requestData(res1 + "bbb") }).then(res2 => { console.log("第二次后果:", res2) return requestData(res2 + "ccc") }).then(res3 => { console.log("第三次后果:", res3) })}
3. 基于生成器的解决计划
function* getData() { const res1 = yield requestData("aaa") console.log("res1:", res1) const res2 = yield requestData(res1 + "bbb") console.log("res2:", res2) const res3 = yield requestData(res2 + "ccc") console.log("res3:", res3)}const generator = getData()generator.next().value.then(res1 => { generator.next(res1).value.then(res2 => { generator.next(res2).value.then(res3 => { generator.next(res3) }) })})
4. 基于async/await的解决计划(最优)
async function getData() { const res1 = await requestData("aaa") console.log("res1:", res1) const res2 = await requestData(res1 + "bbb") console.log("res2:", res2) const res3 = await requestData(res2 + "ccc") console.log("res3:", res3)}// 2s后输入:res1: aaa// 4s后输入:res2: aaabbb// 6s后输入:res3: aaabbbcccconst generator = getData()
通过下面几种计划的比拟咱们能够得出结论:
- 传统的异步解决计划会导致回调天堂,这还只是三个申请,如果是十个甚至更多申请的话,想想有多恐怖!
- 基于生成器的计划尽管写法上较为简洁,然而调用时仿佛又回到了回调天堂上了。
- 基于Promise的优化计划,看起来足够简洁,采纳链式调用的形式应用起来也十分不便,然而还是不够优雅,对,你没听错,就是优雅! 咱们也要和雷布斯学,写出诗一样的代码嘛!
- 基于async/await的解决计划,岂但逻辑清晰,看起来足够简洁,应用起来也足够不便。其实async/await就是基于Promise进行封装的,想想咱们下面所说的异步函数的返回值就能分明,为什么返回一个一般的值也是用Promise进行包裹起来的呢,对吧。 async关键字将函数转换成返回Promise对象的函数,await关键字用于期待Promise对象的实现。 async/await的劣势在于可能使异步代码看起来更像同步代码,这使得代码更容易了解和保护。