js迭代器与生成器

目录

  • 迭代器
    • next
    • 可迭代对象
    • 自定义类的迭代
    • 迭代器的中断
  • 生成器
    • 生成器函数
    • 返回值与传递参数
    • 生成器的提前结束
    • 生成器的语法糖
    • 使用生成器实现自定义类迭代
  • async和await
    • await

迭代器

迭代器iterator),可以把它当做是一个接口,用户可以使用该接口来遍历数据而不需关心数据内部的实现细节
JavaScript中,迭代器是一个具体的对象
这个对象必须含有一个next方法
拥有迭代器的数据可以被用于for...of展开运算符解构赋值创建对象调用特定方法等等地方

next

next方法是一个无参数或者只有一个参数的函数,应当返回一个拥有done和value两个属性的对象
done属性是一个布尔值,它代表了迭代器是否将数据遍历完成,未完成的话值为false,完成或遍历终止的话值为true
value为迭代器每次在对数据遍历时取得的值donetrue时可以省略,值为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())

控制台结果如下

js迭代器与生成器_第1张图片

可迭代对象

我们知道在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)
}

控制台结果如下

结果

迭代器的中断

在某些情况下遍历可能会中断
如在遍历的过程中使用了breakreturnthrow等等
我们想要监听中断的话可以添加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())

控制台结果如下
js迭代器与生成器_第2张图片

返回值与传递参数

生成器同样有返回值与传递参数

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"))

控制台结果如下

js迭代器与生成器_第3张图片
可以看到在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("生成器抛出异常")))

控制台结果如下
js迭代器与生成器_第4张图片

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())

控制台结果如下
js迭代器与生成器_第5张图片
上面这种代码本质上是下面这种代码的语法糖写法

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和await

async用于声明一个异步函数
当一个函数前面添加了async关键字时,这个函数就变成了异步函数
异步函数中的代码默认同步执行
异步函数同样有返回值

  1. 返回一个普通值
    这个值会被包裹到Promise.resolve()
  2. 返回一个Promise
    返回一个Promise则状态由Promise决定
  3. 返回一个thenable
    返回一个thenable则状态由then方法决定
    如果在异步函数抛出异常,会作为reject来处理
    异步函数中最大的特点就是可以使用await关键字

await

await关键字通常会跟一个表达式,表达式返回一个Promise
await会等待Promise状态变为fulfilled之后再继续执行代码
await返回的值跟表达式有关

  1. 如果await后面是一个普通的值,那么会直接返回这个值
  2. 如果await后面是一个thenable的对象,那么会根据then方法调用来决定后续的值
  3. 如果await后面的表达式返回的Promise的状态是reject,那么会将这个reject结果直接作为异步函数Promisereject

回到我们最开始的一个问题,我们想要发送一个网络请求并用一个变量接收网络请求的结果
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)
            }))
        })
    })
})

而如果使用asyncawait的话这么写

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()

这就是异步的最终解决方案

你可能感兴趣的:(网页,javascript,开发语言,ecmascript)