Iterator迭代器和Generator生成器

Iterator迭代器和for/of循环原理

Iterator迭代器规范

在这里插入图片描述

自己创建一个Iterator类,来实现ES6中的迭代器规范:

class Iterator {
    constructor(assemble) {
        // assemble:需要迭代的数据结构
        this.assemble = assemble
        // index:记录迭代的次数(或者索引)
        this.index = -1
    }
    // 必须具备next方法
    next() {
        this.index++
        let { assemble, index } = this
        if (index >= assemble.length) {
            // 迭代完毕
            return {
                done: true,
                value: undefined
            }
        }
        return {
            done: false,
            value: assemble[index]
        }
    }
}

创建一个实例对象,其应该符合迭代器规范的要求:

  • itor.next() 具备next方法,执行这个方法可以依次遍历数据结构中的每一个成员
  • 每一次遍历返回的结果是一个对象 {done:false,value:xxx}
    • done: 是否迭代完毕
    • value: 当前获取的成员值

符合以上两个特点的对象,我们称之为符合迭代器规范的对象

let arr = [10, 20, 30, 40],
    itor = new Iterator(arr)
console.log(itor.next()) // {done: false, value: 10}
console.log(itor.next()) // {done: false, value: 20}
console.log(itor.next()) // {done: false, value: 30}
console.log(itor.next()) // {done: false, value: 40}
console.log(itor.next()) // {done: true, value: undefined}

在JS中,有很多数据结构,天生具备迭代器规范,例如:
        我们主要看数据结构(对象)是否具备 Symbol.iterator 这个属性;有这个属性就具备迭代器规范,没有就不具备;具备这个规范,就可以使用 for/of 循环来迭代数据中的每一项值了。

  • 数组 Array.prototype[Symbol(Symbol.iterator)]=function…
  • 部分类数组:
    • arguments[Symbol(Symbol.iterator)]
    • NodeList.prototype[Symbol(Symbol.iterator)] document.querySelectorAll('*')
    • HTMLCollection.prototype[Symbol(Symbol.iterator)] document.getElementsByTagName('*')
  • 字符串 String.prototype[Symbol(Symbol.iterator)]
  • Set/Map

但是对于纯粹对象「或者自己构建的类数组对象(以数字作为索引,索引从0开始逐级递增,有length属性表示长度)」等来讲,默认是不具备 Symbol.iterator 这个属性的,所以他们不具备迭代器规范「不能直接使用 for/of 循环」

for/of循环原理

重写数组迭代器规范(数组有内置的迭代器规范)来说明原理:

// 数组迭代的方式 for、while、forEach/map、for/in、for/of...
let array = [10, 20, 30, 40]
array[Symbol.iterator] = function () {
    console.log('for/of Start');
    let self = this,// this->array
        index = -1
    // 返回具备迭代器规范的对象->itorr
    return {
        next() {
            index += 2
            if (index >= self.length) {
                return {
                    done: true,
                    value: undefined
                }
            }
            return {
                done: false,
                value: self[index]
            }
        }
    }
}
// let itorr = array[Symbol.iterator]()
for (let val of array) {
    console.log(val);
}

在这里插入图片描述

// for/of循环主要用于获取数据结构中每一项的‘值’
for (let val of array) {
    console.log(val);
}

原理:
1.迭代执行,先执行数组的 Symbol.iterator 这个方法,获取一个具备迭代器规范的对象 -> itor
2.开始迭代:每一次迭代都是把 itor.next 方法执行

  • 把获取对象中的value属性值,赋值给val这个变量
  • 再看对象中done这个属性的值,如果是false,则继续迭代;如果是true,则结束迭代

普通对象是不具备迭代器规范的:

let obj = {
    name: '52lkk',
    age: 24,
    0: 100,
    [Symbol('AA')]: 200
}
for (let val of obj) {
    console.log(val);
}

在这里插入图片描述

为普通对象设置Symbol.iterator使之具备迭代器规范,从而可以通过for/of进行迭代:

// 迭代对象的方式:for/in;获取所有的keys,然后迭代keys;也可以使用for/of(但是需要自己为其设置Symbol.iterator)
let obj = {
    name: '52lkk',
    age: 24,
    0: 100,
    [Symbol('AA')]: 200
}
Object.prototype[Symbol.iterator] = function iterator() {
    let self = this,// 迭代的对象
        index = -1,
        keys = Reflect.ownKeys(self)
    return {
        next() {
            index++
            let key = keys[index]
            // 这个必须要设置,否则就是死循环
            if (index >= keys.length) {
                return {
                    done: true,
                    value: undefined
                }
            }
            return {
                done: false,
                value: self[key]
            }
        }
    }
}
for (let val of obj) {
    console.log(val);
}

在这里插入图片描述

类数组也是不具备迭代器规范的:

// 类数组
let objj = {
    0: 10,
    1: 20,
    2: 30,
    length: 3
}
for (let val of objj) {
    console.log(val);
}

在这里插入图片描述

类数组和数组结构一样,可以直接复用数组原型上的Symbol.iterator方法:jQuery源码就这么写

// 类数组
let objj = {
    0: 10,
    1: 20,
    2: 30,
    length: 3
}
objj[Symbol.iterator] = Array.prototype[Symbol.iterator]
for (let val of objj) {
    console.log(val);
}

在这里插入图片描述


各种循环方式的性能测试:

// 数组迭代的方式 for、while、forEach/map、for/in、for/of...
let arr = new Array(9999999).fill(null)

console.time('for')
for (let i = 0; i < arr.length; i++) {

}
console.timeEnd('for')

console.time('while')
let i = 0
while (i < arr.length) {

    i++
}
console.timeEnd('while')

console.time('FOR-EACH');
arr.forEach(item => {

});
console.timeEnd('FOR-EACH');

console.time('FOR-OF');
for (let val of arr) {

}
console.timeEnd('FOR-OF');

console.time('FOR-IN');
for (let key in arr) {

}
console.timeEnd('FOR-IN');

在这里插入图片描述
for/while: 普通迭代-命令式编程
for…each/for…of: 顺序迭代处理数组中的每一项,相当于普通迭代的封装-函数式编程
for…in: 性能很慢,公有私有属性都迭代、无法获取Symbol类型属性等-杜绝使用

Generator生成器和await原理

如何创建一个generator生成器函数?
把创建函数的function后面加一个*即可(箭头函数是没有办法变成生成器函数的)

每一个生成器函数,都是GeneratorFunction这个类的实例
fn.__proto __ —> GeneratorFunction.prototype —> Function.prototype
多了一个私有属性 [[IsGenerator]]:true
普通函数的原型链
fn.__proto __ —> Function.prototype

当生成器函数执行:

  • 首先并不会立即把函数体中的代码执行,而是返回一个具备迭代器规范的对象【itor】
    itor.__proto __
    • next()
    • throw
    • return
    • Symbol[Symbol.iterator]:function…
      • 当itor.next()执行的时候
        • 把函数体中的代码开始执行
        • 返回一个对象
          • done:记录代码是否执行完毕
          • value:记录本次处理的结果
const fn = function* fn() {
    console.log('代码运行中', 10);
    return 100
}
console.log('代码运行结束', fn());
let itor = fn()
console.log(itor.next());//->{value: 100, done: true}
console.log(itor.next());//->{value: undefined, done: true}

// 对于ES6语法生成的对象
let obj = {
    // sum: function () { }// 对象中创建普通函数
    * sum() {              // 对象中创建生成器函数

    }
}
console.log(obj.sum());

在这里插入图片描述
在这里插入图片描述


Generator生成器函数的作用:
可以基于返回的itor【迭代器对象】,通过其next方法,控制函数体中的代码一步步地去执行

  • 每一次执行next,控制函数体中的代码开始执行【或者从上一次暂停的位置继续执行】,直到遇到yield则暂停。
    • done: false
    • value: yield后面的值
  • 当遇到函数体中的return,或者已经执行到函数最末尾的位置了
    • done: true
    • value: 函数的返回值或undefined
const fn = function* fn() {
    console.log('A');
    yield 100
    console.log('B');
    yield 200
    console.log('C');
    yield 300
    console.log('D');
    return 400
}
let itor = fn()
console.log(itor.next());//->{value: 100, done: false}
console.log(itor.next());//->{value: 200, done: false}
console.log(itor.next());//->{value: 300, done: false}
console.log(itor.next());//->{value: 400, done: true}

在这里插入图片描述
itor.return('xxx'):相当于在函数体中执行遇到了return,结束整个函数的继续执行「done:true」,传递的值相当于return的值

console.log(itor.next());//->{value: 100, done: false}
console.log(itor.return('52lkk'));//->{value: '52lkk', done: true}
console.log(itor.next());//->{value: undefined, done: true}

在这里插入图片描述
itor.throw('xxx'):手动抛出异常「控制台报红」;生成器函数中的代码接下来都不会再执行了,传递的值相当于抛红报异常的值

console.log(itor.next());//->{value: 100, done: false}
console.log(itor.throw('52lkkk'));
console.log(itor.next());//抛出异常后,它下面的代码也不会执行了

在这里插入图片描述


params:生成器函数接收的实参值,它是生成器函数执行时传递的值
fn(10,20,30)—>params:[10,20,30]

itor.next(N):每一次执行next方法,传递的值会作为上一个yield的返回值「所以第一次执行next方法传递的值是没有用的,因为在它之前没有yield」

const fn = function* fn(...params) {
    let x = yield 100
    console.log(x);//->second
    let y = yield 200
    console.log(y);//->third
}
let itor = fn(10, 20, 30)
console.log(itor.next('first'));//->{value: 100, done: false}
console.log(itor.next('second'));//->{value: 200, done: false}
console.log(itor.next('third'));//->{value: undefined, done: true}

在这里插入图片描述


yield*:支持让我们进入另外一个生成器函数中去一步步执行

let sum = function* sum() {
    yield 300
    yield 400
}
const fn = function* fn() {
    yield 100
    yield* sum()// yield*:支持让我们进入另外一个生成器函数中去一步步执行
    yield 200
}
let itor = fn()
// console.log(itor.next());//->{value: 100, done: false}
// console.log(itor.next());//->{value: sum, done: false}
// console.log(itor.next());//->{value: 200, done: false}
console.log(itor.next());//->{value: 100, done: false}
console.log(itor.next());//->{value: 300, done: false}
console.log(itor.next());//->{value: 400, done: false}
console.log(itor.next());//->{value: 200, done: false}
console.log(itor.next());//->{value: undefined, done: true}

在这里插入图片描述


设置延迟函数:

const delay = (interval = 1000) => {
    return new Promise(resolve => {
        {
            setTimeout(() => {
                resolve(`@@${interval}`)
            }, interval)
        }
    })
}

需求:有三个请求,请求需要的时间分别是1000ms/2000ms/3000ms,实现串行请求
方法一:.then()期约链

delay(1000)
    .then(value => {
        console.log('第一个请求成功', value);
        return delay(2000)
    })
    .then(value => {
        console.log('第二个请求成功', value);
        return delay(3000)
    })
    .then(value => {
        console.log('第三个请求成功', value);
    })
    .catch(reason => {
        console.log('任何请求失败都会走到这里', reason);
    })

方法二:async/await

(async () => {
    try {
        let value = await delay(1000)
        console.log('第一个请求成功', value);
        value = await delay(2000)
        console.log('第二个请求成功', value);
        value = await delay(3000)
        console.log('第三个请求成功', value);
    } catch (reason) {
        console.log('任何请求失败都会走到这里', reason);
    }
})()

方法三:基于generator函数,模拟await的语法,实现请求的串行

const handle = function* handle() {
    let value = yield delay(1000)
    console.log('第一个请求成功', value);
    value = yield delay(2000)
    console.log('第二个请求成功', value);
    value = yield delay(3000)
    console.log('第三个请求成功', value);
}
let itor = handle()
// done:是否执行完毕  value:每一次yield后面的值【Promise实例】
let { done, value } = itor.next()
value.then(x => {
    // x:第一个请求成功的结果 @@1000
    let { done, value } = itor.next(x)
    value.then(x => {
        // x:第二个请求成功的结果 @@2000
        let { done, value } = itor.next(x)
        value.then(x => {
            // x:第三个请求成功的结果 @@3000
            let { done, value } = itor.next(x)
        })
    })
})

对方法三递归优化:编写通知Generator中代码逐一执行的方法

ES8(ECMAScript2017)中,提供了 aysnc/await 语法:用来简化promise的操作,它是Promise + Generator的语法糖,我们自己实现的AsyncFunction和Generator函数就是async/await的原理

const AsyncFunction = function AsyncFunction(generator, ...params) {
    let itor = generator(...params)
    const next = x => {
        let { done, value } = itor.next(x)
        if (done) return
        if (!(value instanceof Promise)) value = Promise.resolve(value)
        // value.then(x => {
        //     next(x)
        // })
        value.then(next)
    }
    next()
}
AsyncFunction(handle)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
迭代器生成器JavaScript 中非常重要的概念,常在面试中涉及。迭代器Iterator)是一种对象,它提供了一种顺序访问集合中各个元素的方法,而不暴露集合的内部结构。生成器Generator)是一种特殊类型的函数,可以暂停执行并返回中间结果。 1. 迭代器Iterator): - 迭代器是一个拥有 `next()` 方法的对象。 - `next()` 方法返回一个包含 `value` 和 `done` 属性的对象。 - `value` 表示当前迭代到的元素的值。 - `done` 表示迭代是否已经结束。 以下是一个简单的示例,展示如何使用迭代器遍历数组: ```javascript const arr = [1, 2, 3]; const iterator = arr[Symbol.iterator](); console.log(iterator.next()); // { value: 1, done: false } console.log(iterator.next()); // { value: 2, done: false } console.log(iterator.next()); // { value: 3, done: false } console.log(iterator.next()); // { value: undefined, done: true } ``` 2. 生成器Generator): - 生成器是一种特殊类型的函数,使用 `function*` 声明,内部可以使用 `yield` 关键字。 - `yield` 关键字用于暂停生成器函数的执行,并返回一个中间结果。 - 每次调用生成器函数时,会返回一个迭代器对象,通过调用 `next()` 方法可以继续执行生成器函数。 以下是一个简单的示例,展示如何使用生成器函数生成斐波那契数列: ```javascript function* fibonacci() { let a = 0; let b = 1; while (true) { yield a; [a, b] = [b, a + b]; } } const fib = fibonacci(); console.log(fib.next().value); // 0 console.log(fib.next().value); // 1 console.log(fib.next().value); // 1 console.log(fib.next().value); // 2 ``` 希望以上概念和示例能够帮助你在面试中更好地理解和回答相关问题!如果你有更多疑问,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值