迭代器
(iterator
),可以把它当做是一个接口
,用户可以使用该接口来遍历数据而不需关心数据内部的实现细节
在JavaScript
中,迭代器是
一个具体的对象
这个对象
必须含有一个next
方法
拥有迭代器
的数据可以被用于for...of
,展开运算符
,解构赋值
,创建对象
,调用特定方法
等等地方
next
方法是一个无参数或者只有一个参数
的函数,应当返回一个拥有done和value两个属性的对象
done
属性是一个布尔值
,它代表了迭代器是否将数据遍历完成
,未完成的话值为false
,完成或遍历终止的话值为true
value
为迭代器每次在对数据遍历时取得的值
,done
为true
时可以省略,值为undefined 我们可以实现一个数组的迭代器,通过这个迭代器来遍历数组
var arr = [1, 2, 3, 4, 5]
var arrIndex = 0
var arrIterator = {
next: function () {
if (arrIndex >= arr.length) {
return { done: true, value: undefined }
} else {
return { done: false, value: `arr第${arrIndex}元素是${arr[arrIndex++]}` }
}
}
}
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())
console.log(arrIterator.next())
控制台结果如下
我们知道在JavaScript
中对象
本身是不可迭代
的
如果一个对象实现了迭代器
时那么这个对象就变成了一个可迭代对象
迭代器的名称必须为@@iterator
,在代码中我们可以使用Symbol.iterator
访问
var obj = {
arr: [1, 2, 3, 4, 5],
[Symbol.iterator]() {
var index = 0
return {
next: () => {
if (index >= this.arr.length) {
return { done: true, value: undefined }
} else {
return { done: false, value: this.arr[index++] }
}
}
}
}
}
var objIterator = obj[Symbol.iterator]()
console.log(objIterator.next())
console.log(objIterator.next())
console.log(objIterator.next())
console.log(objIterator.next())
console.log(objIterator.next())
console.log(objIterator.next())
for (var item of obj) {
console.log(item)
}
通过对对象实现迭代器
的形式成功让对象可以迭代
我们还可以创建一个类,所有通过这个类new
出来的对象都是可迭代对象
class Person {
constructor() {
this.name = "张三"
this.age = "18"
}
[Symbol.iterator]() {
var index = 0;
var items = Object.entries(this)
return {
next: () => {
if (index >= items.length) {
return { done: true, value: undefined }
} else {
return { done: false, value: items[index++] }
}
}
}
}
}
var p1 = new Person()
for (var item of p1) {
console.log(item)
}
控制台结果如下
在某些情况下遍历可能会中断
如在遍历的过程中使用了break
,return
,throw
等等
我们想要监听中断的话可以添加return
方法
class Person {
constructor() {
this.name = "张三"
this.age = "18"
}
[Symbol.iterator]() {
var index = 0;
var items = Object.entries(this)
return {
next: () => {
if (index >= items.length) {
return { done: true, value: undefined }
} else {
return { done: false, value: items[index++] }
}
},
return: () => {
console.log("监听到了中断操作")
return { done: true, value: undefined }
}
}
}
}
var p1 = new Person()
for (var item of p1) {
console.log(item)
break
}
生成器
(Generator
)是一种新的函数控制解决方案,它能够更加灵活的控制函数什么时候执行,什么时候暂停
生成器
本质上是一个特殊的迭代器
生成器函数
是一个特殊的函数,生成器函数需要在Function
标识符的后面加一个*
生成器函数能通过yield控制函数执行流程
生成器函数
会返回一个生成器对象
function* foo() {
console.log("aaa")
yield
console.log("bbb")
yield
console.log("ccc")
yield
console.log("ddd")
}
var generator = foo()
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
生成器同样有返回值与传递参数
function* foo(next1) {
console.log("aaa", next1)
var next2 = yield "return1"
console.log("bbb", next2)
var next3 = yield "return2"
console.log("ccc", next3)
var next4 = yield "return3"
console.log("ddd", next4)
}
var generator = foo("next1")
console.log(generator.next())
console.log(generator.next("next2"))
console.log(generator.next("next3"))
console.log(generator.next("next4"))
console.log(generator.next("next5"))
控制台结果如下
可以看到在next
中传入参数时会被放入yield
前接收的变量中
值得注意的是第一次
调用next
时是不用传递参数
的,因为没有yield
前面的变量接收参数
如果想要在第一次
调用next
就传递参数的话需要再foo
中传递
每个yield
后面都是返回值
,这些返回值
被存放在value
中
当生成器函数
执行完毕后默认的返回值
是undefined
如果希望生成器
提前结束的话可以调用return
方法和throw
方法
function* foo(next1) {
console.log("aaa", next1)
var next2 = yield "return1"
console.log("bbb", next2)
var next3 = yield "return2"
console.log("ccc", next3)
var next4 = yield "return3"
console.log("ddd", next4)
}
var generator = foo("next1")
console.log(generator.next())
console.log(generator.next("next2"))
console.log(generator.return("next3"))
console.log("-------------------------------")
var generator2 = foo("next1")
console.log(generator2.next())
console.log(generator2.next("next2"))
console.log(generator2.throw(new Error("生成器抛出异常")))
return
方法调用后这个生成器函数就不会再执行了
而throw
方法则会向生成器函数内部抛出一个异常
生成器函数
内部可以使用try catch
捕获异常
如果不捕获异常
的话生成器函数会停止运行
使用try catch
捕获的话在catch
内部无法继续yield
,在catch
外部可以继续函数的执行
如果我们想用生成器来遍历数组的话可以这么写
var arr = [1, 2, 3, 4, 5]
function* foo(arr) {
yield* arr
}
var generator = foo(arr)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
控制台结果如下
上面这种代码本质上是下面这种代码的语法糖
写法
var arr = [1, 2, 3, 4, 5]
function* foo(arr) {
for (var i = 0; i < arr.length; i++) {
yield arr[i]
}
}
var generator = foo(arr)
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
class Person {
constructor() {
this.name = "张三"
this.age = "18"
}
*[Symbol.iterator]() {
yield* Object.entries(this)
}
}
var p1 = new Person()
for (var item of p1) {
console.log(item)
}
控制台结果如下
值得注意的是,如果想在类中实现生成器函数
,可以在[Symbol.iterator]
前加上一个*
async用于声明一个异步函数
当一个函数前面添加了async
关键字时,这个函数就变成了异步函数
异步函数
中的代码默认同步执行
异步函数
同样有返回值
普通值
Promise.resolve()
中Promise
Promise
则状态由Promise
决定thenable
thenable
则状态由then
方法决定异步函数
中抛出异常
,会作为reject
来处理异步函数
中最大的特点就是可以使用await
关键字await
关键字通常会跟一个表达式
,表达式返回一个Promise
await
会等待Promise
状态变为fulfilled
之后再继续执行代码
await
返回的值跟表达式有关
await
后面是一个普通的值
,那么会直接返回
这个值await
后面是一个thenable
的对象,那么会根据then
方法调用来决定后续的值await
后面的表达式返回的Promise
的状态是reject
,那么会将这个reject
结果直接作为异步函数
的Promise
的reject
值回到我们最开始的一个问题,我们想要发送一个网络请求
并用一个变量接收网络请求的结果
在Promise
中是这种写法
具体关于Promise
可以看我这篇文章
Promise
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve("success:" + url)
}, 1000)
})
}
function getRequests() {
var datas = []
request("http://yes.com").then((res) => {
datas.push(res)
return request("http://no.com")
}).then((res) => {
datas.push(res)
return request("http://null.com")
}).then((res) => {
datas.push(res)
return request("http://foo.com")
}).then((res) => {
datas.push(res)
})
console.log(datas)
}
getRequests()
使用生成器
和迭代器
的写法
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve("success:" + url)
}, 1000)
})
}
function* getRequests() {
var datas = []
var temp
temp = yield request("http://yes.com")
datas.push(temp)
temp = yield request("http://no.com")
datas.push(temp)
temp = yield request("http://null.com")
datas.push(temp)
temp = yield request("http://foo.com")
datas.push(temp)
console.log(datas)
}
var generator = getRequests()
generator.next().value.then((res) => {
generator.next(res).value.then((res) => {
generator.next(res).value.then((res) => {
generator.next(res).value.then((res => {
generator.next(res)
}))
})
})
})
而如果使用async
和await
的话这么写
function request(url) {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve("success:" + url)
}, 1000)
})
}
async function getRequests() {
var datas = []
var temp
temp = await request("http://yes.com")
datas.push(temp)
temp = await request("http://no.com")
datas.push(temp)
temp = await request("http://null.com")
datas.push(temp)
temp = await request("http://foo.com")
datas.push(temp)
console.log(datas)
}
getRequests()
这就是异步的最终解决方案