- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
相信很多人对迭代器和生成器都不陌生,当提到async和await的原理时,大部分人可能都知道async、await是Promise+生成器的语法糖,其原理具体是怎么做的呢?下面通过这篇文章带你详细了解一下迭代器和生成器,以及带你从生成器一步步推导到async和await。
迭代器是确使用户在容器对象(链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。
迭代器的定义可能比较抽象,简单来说迭代器就是一个对象,可用于帮助我们对某个数据结构(链表、数组)进行遍历;
在JavaScript中,迭代器也是一个具体的对象,并且这个对象必须符合迭代器协议(iterator protocol);
什么是迭代器协议?
在JavaScript中就是指这个对象必须实现一个特定的next方法,并且next方法有如下要求;
next方法可接收0个或者1个参数(在生成器中next可以接收1个参数),并且需返回一个对象,对象包含以下两个属性:
done:值为Boolean,如果迭代器可以迭代产生下一个值,就为false,如果已经迭代完毕,就为true;
value:迭代器返回的值,如果done为true,value一般为undefined;
编写一个最简单的迭代器:
const iterator = {
next: function() {
return { done: false, value: 123 }
}
}
明白了迭代器的基本定义,下面就来实现一下符合迭代器协议的对象吧,并且看看其它的一些基本用法。比如,需要通过迭代器访问一个数组:
(1)创建一个迭代器对象
const names = ['curry', 'kobe', 'klay']
let index = 0 // 通过一个index来记录当前访问的位置
const iterator = {
next() {
if (index < names.length) {
return { done: false, value: names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
console.log(iterator.next()) // { done: false, value: 'curry' }
console.log(iterator.next()) // { done: false, value: 'kobe' }
console.log(iterator.next()) // { done: false, value: 'klay' }
console.log(iterator.next()) // { done: true, value: undefined }
(2)实现生成迭代器的函数
function createIterator(arr) {
let index = 0
return {
next() {
if (index < arr.length) {
return { done: false, value: arr[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
const names = ['curry', 'kobe', 'klay']
// 调用createIterator函数,生成一个访问names数组的迭代器
const namesIterator = createIterator(names)
console.log(namesIterator.next()) // { done: false, value: 'curry' }
console.log(namesIterator.next()) // { done: false, value: 'kobe' }
console.log(namesIterator.next()) // { done: false, value: 'klay' }
console.log(namesIterator.next()) // { done: true, value: undefined }
上面提到了迭代器是一个对象,并且符合迭代器协议,那么什么是可迭代对象呢?它与迭代器又有什么区别?
Symbol.iterator
方法,并且该方法返回一个迭代器对象;如下,iteratorObj
就是一个可迭代对象:
const iteratorObj = {
names: ['curry', 'kobe', 'klay'],
[Symbol.iterator]: function() {
let index = 0
return {
// 注意:这里的next需要使用箭头函数,否则this访问不到iteratorObj
next: () => {
if (index < this.names.length) {
return { done: false, value: this.names[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
// 调用iteratorObj中的Symbol.iterator得到一个迭代器
const iterator = iteratorObj[Symbol.iterator]()
console.log(iterator.next()) // { done: false, value: 'curry' }
console.log(iterator.next()) // { done: false, value: 'kobe' }
console.log(iterator.next()) // { done: false, value: 'klay' }
console.log(iterator.next()) // { done: true, value: undefined }
上面的可迭代对象都是由自己实现的,其实在JavaScript中为我们提供了很多可迭代对象,如:String、Array、Map、Set、arguments对象、NodeList(DOM集合)等。
// 1.String
const str = 'abc'
const strIterator = str[Symbol.iterator]()
console.log(strIterator.next()) // { value: 'a', done: false }
console.log(strIterator.next()) // { value: 'b', done: false }
console.log(strIterator.next()) // { value: 'c', done: false }
console.log(strIterator.next()) // { value: undefined, done: true }
// 2.Array
const names = ['curry', 'kobe', 'klay']
console.log(names[Symbol.iterator])
const namesIterator = names[Symbol.iterator]()
console.log(namesIterator.next()) // { value: 'curry', done: false }
console.log(namesIterator.next()) // { value: 'kobe', done: false }
console.log(namesIterator.next()) // { value: 'klay', done: false }
console.log(namesIterator.next()) // { value: undefined, done: true }
// 3.Map/Set
const set = new Set
set.add(10)
set.add(20)
set.add(30)
const setIterator = set[Symbol.iterator]()
console.log(setIterator.next()) // { value: 10, done: false }
console.log(setIterator.next()) // { value: 20, done: false }
console.log(setIterator.next()) // { value: 30, done: false }
console.log(setIterator.next()) // { value: undefined, done: true }
可迭代对象在实际应用中特别常见,像一些语法的使用、创建一些对象和方法调用都用到了可迭代对象。
JS中的语法:for...of、展开语法、解构等。
for...of可用于遍历一个可迭代对象,其原理就是利用迭代器的next函数,如果done为false,就从返回的对象中拿到value返回给我们,而对象不是一个可迭代对象,所以对象不能使用for...of遍历;
const num = [1, 2, 3]
for (const item of num) {
console.log(item) // 1 2 3
}
// 遍历上面自己定义的可迭代对象iteratorObj也是可以的
for (const item of iteratorObj) {
console.log(item) // curry kobe klay
}
const obj = { name: 'curry', name: 30 }
for (const key of obj) {
console.log(key)
}
...
对数组进行展开时,也是通过迭代器的next去获取数组的每一项值,然后存放到新数组中;const names = ['james', 'green']
// 将数组和iteratorObj通过扩展进行合并
const newNames = [...names, ...iteratorObj]
console.log(newNames) // [ 'james', 'green', 'curry', 'kobe', 'klay' ]
const str = 'abc'
const nums = [1, 2, 3]
const [str1, str2, str3] = str
console.log(str1, str2, str3) // a b c
const [num1, num2, num3] = nums
console.log(num1, num2, num3) // 1 2 3
const [name1, name2, name3] = iteratorObj
console.log(name1, name2, name3) // curry kobe klay
// 1.Set
const set = new Set(iteratorObj)
console.log(set) // Set(3) { 'curry', 'kobe', 'klay' }
// 2.Array.from
const names = Array.from(iteratorObj)
console.log(names) // [ 'curry', 'kobe', 'klay' ]
// 传入的可迭代对象中的每个值,会使用Promise.resolve进行包裹
Promise.all(iteratorObj).then(res => {
console.log(res) // [ 'curry', 'kobe', 'klay' ]
})
扩展:现在我们都知道了for...of可用于遍历一个可迭代对象,如果在遍历过程中终端了呢?因为使用break、continue、return、throw
都是可以中断遍历的,既然for...of遍历的原理是基于迭代器的,那么在for...of中进行中断操作,一定是可以被迭代器监听到的,上面说了,在迭代器中有一个next方法,其实还可以指定一个return方法,如果遍历过程中断了,就会去调用return方法,注意return方法也要返回和next方法一样的对象。这种情况就称之为迭代器的中断。
const iteratorObj = {
names: ['curry', 'kobe', 'klay'],
[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 }
}
},
return() {
console.log('哎呀,我被中断了!')
return { done: true, value: undefined }
}
}
}
}
for (const item of iteratorObj) {
console.log(item)
if (item === 'kobe') break
}
上面提到了对象不是一个可迭代对象,所以对象不能使用for...of遍历,如果我们想要实现通过for...of遍历对象呢?那么可以自己实现一个类,这个类的实例化对象是可迭代对象。
Symbol.iterator
方法用于返回一个迭代器;p
中包含一个friends数组,通过for...of遍历p
对象时,可以将friends数组的每一项遍历出来;class Person {
constructor(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < this.friends.length) {
return { done: false, value: this.friends[index++] }
} else {
return { done: true, value: undefined }
}
}
}
}
}
简单看一下效果:
const p = new Person('curry', 30, ['kobe', 'klay', 'green'])
for (const name of p) {
console.log(name) // kobe klay green
}
生成器是ES6中新增的一种控制函数执行的方案,它可以帮助我们控制函数的暂停和执行。生成器是一种特殊的迭代器,所以生成器也是一个对象,并且可以调用next方法。那么怎么创建一个生成器对象呢?
创建生成器对象需要使用生成器函数,生成器函数和普通函数不一样,主要有以下特点:
*
;yield
关键字来分割函数体代码,控制函数的执行;实现一个生成器函数,该函数的执行可以通过返回的生成器对象进行控制。
function* generatorFn() {
console.log('函数开始执行~')
console.log('函数第一段代码执行...')
yield
console.log('函数第二段代码执行...')
yield
console.log('函数第三段代码执行...')
yield
console.log('函数第四段代码执行...')
console.log('函数执行结束~')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
generator.next()
console.log('------------------------')
generator.next()
console.log('------------------------')
generator.next()
console.log('------------------------')
generator.next()
上面说到了生成器是一种特殊的迭代器,那么调用next方法肯定也是有返回值的,并且返回值是一个包含done、value属性的对象。
function* generatorFn() {
console.log('函数第一段代码执行...')
yield
console.log('函数第二段代码执行...')
yield
console.log('函数第三段代码执行...')
yield
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
从打印结果可以看出来,next返回的对象中value是没有值的,当执行到最后一段代码后,done的值就为true了:
如果需要指定next返回值中的value,那么可以通过在yield
后面跟上一个值或者表达式,就可以将对应的值传递到next返回对象value中了。
function* generatorFn() {
console.log('函数第一段代码执行...')
yield 10
console.log('函数第二段代码执行...')
yield 20
console.log('函数第三段代码执行...')
yield 30
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
观察以上打印结果,在执行完第四段代码后,调用的next返回值为{ value: undefined, done: true }
,原因是后面已经没有yield
了,而且当函数没有指定返回值时,最后会默认执行return undefined
。
在前面介绍迭代器定义时,提到迭代器的next可以传递0个或1个参数,而可以传递1个参数的情况就是生成器的next可以传递一个参数,而给每一段代码传递过去的参数可以通过yield来接收。
function* generatorFn(value) {
console.log('函数第一段代码执行...', value)
const value1 = yield 10
console.log('函数第二段代码执行...', value1)
const value2 = yield 20
console.log('函数第三段代码执行...', value2)
const value3 = yield 30
console.log('函数第四段代码执行...', value3)
}
// 调用generatorFn获取生成器
const generator = generatorFn('参数0')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next('参数1'))
console.log('------------------------')
console.log(generator.next('参数2'))
console.log('------------------------')
console.log(generator.next('参数3'))
next参数传递解释:
yield
接收的,这样可以方便下面代码使用这个参数,所以给next传递参数,需要从第二个next开始传递;return方法也可以给生成器函数传递参数,但是调用return后,生成器函数就会中断,之后再调用next就不会再继续生成值了。
function* generatorFn() {
console.log('函数第一段代码执行...')
yield
console.log('函数第二段代码执行...')
yield
console.log('函数第三段代码执行...')
yield
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.return(123))
console.log('------------------------')
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
上面的执行return方法,相当于函数内部执行了return:
function* generatorFn() {
console.log('函数第一段代码执行...')
yield
console.log('函数第二段代码执行...')
const value = yield
return value
console.log('函数第三段代码执行...')
yield
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.next(123))
console.log('------------------------')
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
throw方法可以给生成器函数内部抛出异常。
try catch
捕获异常后,后续的代码还是可以正常执行的;function* generatorFn() {
console.log('函数第一段代码执行...')
yield 10
console.log('函数第二段代码执行...')
try {
yield 20
} catch (err) {
console.log(err)
}
console.log('函数第三段代码执行...')
yield 30
console.log('函数第四段代码执行...')
}
// 调用generatorFn获取生成器
const generator = generatorFn()
console.log(generator.next())
console.log('------------------------')
console.log(generator.next())
console.log('------------------------')
console.log(generator.throw('err message'))
console.log('------------------------')
console.log(generator.next())
在前面实现了一个生成迭代器的函数,实现过程还需要进行判断,并自己返回对应的对象,下面就用生成器来实现一个生成迭代器的函数。
function* createIterator(arr) {
let index = 0
yield arr[index++]
yield arr[index++]
yield arr[index++]
}
function* createIterator(arr) {
for (const item of arr) {
yield item
}
}
yield*
,后面可以跟上一个可迭代对象,它会依次迭代其中每一个值;function* createIterator(arr) {
yield* arr
}
测试一下以上三种方法,执行结果都是一样的:
const names = ['curry', 'kobe', 'klay']
const iterator = createIterator(names)
console.log(iterator.next()) // { value: 'curry', done: false }
console.log(iterator.next()) // { value: 'kobe', done: false }
console.log(iterator.next()) // { value: 'klay', done: false }
console.log(iterator.next()) // { value: undefined, done: true }
在进行异步请求时,如果出现了这样一个需求,下一次的请求需要拿到上一次请求的结果。如下是使用Promise封装的一个request方法。
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(url)
}, 300)
})
}
实现上面的需求可以怎么做呢?
request('/aaa').then(res => {
request(res + '/bbb').then(res => {
request(res + '/ccc').then(res => {
console.log(res) // /aaa/bbb/ccc
})
})
})
request('/aaa').then(res => {
return request(res + '/bbb')
}).then(res => {
return request(res + '/ccc')
}).then(res => {
console.log(res) // /aaa/bbb/ccc
})
function* getRequestData() {
const res1 = yield request('/aaa')
const res2 = yield request(res1 + '/bbb')
const res3 = yield request(res2 + '/ccc')
console.log(res3)
}
const generator = getRequestData()
generator.next().value.then(res => {
generator.next(res).value.then(res => {
generator.next(res).value.then(res => {
generator.next(res) // /aaa/bbb/ccc
})
})
})
function autoGenerator(generatorFn) {
const generator = generatorFn()
function recursion(res) {
const result = generator.next(res)
// 如果done值为true,说明结束了
if (result.done) return result.value
// 没有结束,继续调用Promise的then
result.value.then(res => {
recursion(res)
})
}
recursion()
}
autoGenerator(getRequestData) // /aaa/bbb/ccc
co
;const co = require('co')
co(getRequestData) // /aaa/bbb/ccc
async和await是我们解决异步回调的最终解决方案,它可以让我们异步的代码,看上去是同步执行的。
async function getRequestData() {
const res1 = await request('/aaa')
const res2 = await request(res1 + '/bbb')
const res3 = await request(res2 + '/ccc')
console.log(res3)
}
getRequestData() // /aaa/bbb/ccc
相信从上面讲述的四个异步请求的处理方案中,就可以看出来async、await和生成器的关系了。
*
换成async
;yield
换成await
;总结:
我有一个 html 格式的表单: 我需要得到 JavaScript在value input 字段执行,但只能通过表单的 submit .原因是页面是一个模板所以我不控制它(不能有
我管理的论坛是托管软件,因此我无法访问源代码,我只能向页面添加 JavaScript 来实现我需要完成的任务。 我正在尝试用超链接替换所有页面上某些文本关键字的第一个实例。我还根据国家/地区代码对这些
我正在使用 JS 打开新页面并将 HTML 代码写入其中,但是当我尝试使用 document.write() 在新页面中编写 JS 时功能不起作用。显然,一旦看到 ,主 JS 就会关闭。用于即将打开的
提问不是为了解决问题,提问是为了更好地理解系统 专家!我知道每当你将 javascript 代码输入 javascript 引擎时,它会立即由 javascript 引擎执行。由于没有看过Engi
我在一个文件夹中有两个 javascript 文件。我想将一个变量的 javascript 文件传递到另一个。我应该使用什么程序? 最佳答案 window.postMessage用于跨文档消息。使
我有一个练习,我需要输入两个输入并检查它们是否都等于一个。 如果是 console.log 正则 console.log false 我试过这样的事情: function isPositive(fir
我正在做一个Web应用程序,计划允许其他网站(客户端)在其页面上嵌入以下javascript: 我的网络应用程序位于 http://example.org 。 我不能假设客户端网站的页面有 JQue
目前我正在使用三个外部 JS 文件。 我喜欢将所有三个 JS 文件合而为一。 尽一切可能。我创建 aio.js 并在 aio.js 中 src="https://code.jquery.com/
我有例如像这样的数组: var myArray = []; var item1 = { start: '08:00', end: '09:30' } var item2 = {
所以我正在制作一个 Chrome 扩展,它使用我制作的一些 TamperMonkey 脚本。我想要一个“主”javascript 文件,您可以在其中包含并执行其他脚本。我很擅长使用以下行将其他 jav
我有 A、B html 和 A、B javascript 文件。 并且,如何将 A JavaScript 中使用的全局变量直接移动到 B JavaScript 中? 示例 JavaScript) va
我需要将以下整个代码放入名为 activate.js 的 JavaScript 中。你能告诉我怎么做吗? var int = new int({ seconds: 30, mark
我已经为我的 .net Web 应用程序创建了母版页 EXAMPLE1.Master。他们的 I 将值存储在 JavaScript 变量中。我想在另一个 JS 文件中检索该变量。 示例1.大师:-
是否有任何库可以用来转换这样的代码: function () { var a = 1; } 像这样的代码: function () { var a = 1; } 在我的浏览器中。因为我在 Gi
我收到语法缺失 ) 错误 $(document).ready(function changeText() { var p = document.getElementById('bidp
我正在制作进度条。它有一个标签。我想调整某个脚本完成的标签。在找到可能的解决方案的一些答案后,我想出了以下脚本。第一个启动并按预期工作。然而,第二个却没有。它出什么问题了?代码如下: HTML:
这里有一个很简单的问题,我简单的头脑无法回答:为什么我在外部库中加载时,下面的匿名和onload函数没有运行?我错过了一些非常非常基本的东西。 Library.js 只有一行:console.log(
我知道 javascript 是一种客户端语言,但如果实际代码中嵌入的 javascript 代码以某种方式与在控制台上运行的代码不同,我会尝试找到答案。让我用一个例子来解释它: 我想创建一个像 Mi
我如何将这个内联 javascript 更改为 Unobtrusive JavaScript? 谢谢! 感谢您的回答,但它不起作用。我的代码是: PHP js文件 document.getElem
我正在寻找将简单的 JavaScript 对象“转储”到动态生成的 JavaScript 源代码中的最优雅的方法。 目的:假设我们有 node.js 服务器生成 HTML。我们在服务器端有一个对象x。
我是一名优秀的程序员,十分优秀!