JavaScript进阶讲解十七—>迭代器与生成器

137 阅读2分钟

迭代器(iterator)

迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。

迭代器是帮助我们对某个数据结构进行遍历的对象

在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):

  1. 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式
  2. 在js中这个标准就是一个特定的next方法

next方法有如下的要求:

  1. 一个无参数或者一个参数的函数(如生成器就是个特殊的迭代器,他就需要一个参数),返回一个应当拥有以下两个属性的对象

    1.1 done(boolean):如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。),如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值

    1.2 value:迭代器返回的任何 JavaScript 值。done 为 true 时可省略

迭代器的实现

基本格式

const iterator = {
    next: function() {
        return {
            done: true,
            value: 1
        }
    }
}

通过迭代器对象访问数组

const nums = ['1', '2', '3']

let index = 0
const numsIterator = {
    next: function() {
        if (index < nums.length) {
            return { done: false, value: nums[index++] }
        } else {
            return { done: true, value: undefined }
        }
    }
}

console.log(numsIterator.next()); // { done: false, value: '1' }
console.log(numsIterator.next()); // { done: false, value: '2' }
console.log(numsIterator.next()); // { done: false, value: '3' }
console.log(numsIterator.next()); // { done: true, value: undefined }
console.log(numsIterator.next()); // { done: true, value: undefined }

生成迭代器函数

const nums = ['1', '2', '3']

function createArrIterator(arr) {
    let index = 0
    return {
        next() {
            if (index < arr.length) {
                return { done: false, value: arr[index++] }
            } else {
                return { done: true, value: undefined }
            }
        }
    }
}

const numsIterator = createArrIterator(nums)
console.log(numsIterator.next()); // { done: false, value: '1' }
console.log(numsIterator.next()); // { done: false, value: '2' }
console.log(numsIterator.next()); // { done: false, value: '3' }
console.log(numsIterator.next()); // { done: true, value: undefined }
console.log(numsIterator.next()); // { done: true, value: undefined }

可迭代对象

  1. 它和迭代器是不同的概念
  2. 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象
  3. 这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性
  4. 当一个对象变成一个可迭代对象的时候,进行某些迭代操作,比如 for...of 操作时,其实就会调用它的@@iterator 方法
const iteratorObj = {
    nums: ['1', '2', '3'],
    [Symbol.iterator]: function() {
        let index = 0
        return {
            next: () => {
                if (index < this.nums.length) {
                    return { done: false, value: this.nums[index++] }
                } else {
                    return { done: true, value: undefined }
                }
            }
        }
    }
}

const numsIterator = iteratorObj[Symbol.iterator]()
console.log(numsIterator.next()); // { done: false, value: '1' }
console.log(numsIterator.next()); // { done: false, value: '2' }
console.log(numsIterator.next()); // { done: false, value: '3' }
console.log(numsIterator.next()); // { done: true, value: undefined }
console.log(numsIterator.next()); // { done: true, value: undefined }
// 当他是可迭代对象时 就可以用for..of
for (const iterator of iteratorObj) {
    console.log(iterator); // 1 2 3
}

可迭代对象的应用

  1. JavaScript中语法:for ...of、展开语法(spread syntax)、yield*、解构赋值(Destructuring_assignment)
  2. 创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable])
  3. 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);

原生迭代器对象

很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:String、Array、Map、Set、arguments对象、NodeList集合

生成器

生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。

生成器函数也是一个函数,但是和普通的函数有一些区别:

  1. 生成器函数需要在function的后面加一个符号:*
  2. 生成器函数可以通过yield关键字来控制函数的执行流程
  3. 生成器函数的返回值是一个Generator(生成器)
  4. 生成器事实上是一种特殊的迭代器
function* foo() {
    console.log('start');
    const val1 = 1
    console.log(val1);
    yield 

    const val2 = 2
    console.log(val2);
    yield 

    const val3 = 3
    console.log(val3);
    yield 

    console.log('end');
}

// 生成器函数foo的执行体没有执行,它只是返回了一个生成器对象
const generator = foo()

// 执行到第一个yield,且暂停
generator.next()

// 执行到第二个yield,且暂停
generator.next()

// 执行到第三个yield,且暂停
generator.next()

// 执行剩余的
generator.next()

生成器函数执行 上面代码中我们直接调用foo他是没有执行的,他只是返回了一个生成器对象。要执行需要调用next(),但在上面的代码中next返回的是一个undefined,如果我们不希望返回的是undefined,我们可以通过yield来返回结果

function* foo() {
    console.log('start');
    const val1 = 1
    console.log(val1);
    yield val1

    const val2 = 2
    console.log(val2);
    yield val2

    const val3 = 3
    console.log(val3);
    yield val3

    console.log('end');
}

// 生成器函数foo的执行体没有执行,它只是返回了一个生成器对象
const generator = foo()

// 执行到第一个yield,且暂停
console.log(generator.next());

// 执行到第二个yield,且暂停
console.log(generator.next());

// 执行到第三个yield,且暂停
console.log(generator.next());

// 执行剩余的
console.log(generator.next());

生成器传递参数

function* foo(initial) {
    console.log('start');
    const val1 = yield '1' + initial

    const val2 = yield '2' + val1

    const val3 = yield '3' + val2

    console.log('end');
}

const generator = foo('xt')

console.log(generator.next()); // { value: '1xt', done: false }

console.log(generator.next('tx')); // { value: '2tx', done: false }

console.log(generator.next('haha')); // { value: '3haha', done: false }

console.log(generator.next()); // { value: undefined, done: true }

生成器替代迭代器

const nums = ['1', '2', '3']

function* createArrIterator(arr) {
    for (const item of arr) {
        yield item
    }
}

const numsIterator = createArrIterator(nums)
console.log(numsIterator.next()); // { done: false, value: '1' }
console.log(numsIterator.next()); // { done: false, value: '2' }
console.log(numsIterator.next()); // { done: false, value: '3' }
console.log(numsIterator.next()); // { done: true, value: undefined }

还可以使用yield*来生产一个可迭代对象

const nums = ['1', '2', '3']

function* createArrIterator(arr) {
    yield* arr
}

const numsIterator = createArrIterator(nums)
console.log(numsIterator.next()); // { done: false, value: '1' }
console.log(numsIterator.next()); // { done: false, value: '2' }
console.log(numsIterator.next()); // { done: false, value: '3' }
console.log(numsIterator.next()); // { done: true, value: undefined }

解决回调地狱

function getData() {
    requestData('url').then(res => {
        requestData('url' + res).then(res1 => {
            requestData('url'  + res1).then(res2 => {
                console.log(res2);
            })
        })
    })
}

如上我们每次请求需要用到上一次的结果,这样我们就需要不停的去嵌套,这样就会变得很难维护,如何解决呢?

function getData() {
    requestData('url').then(res => {
        return requestData('url' + res)
    }).then(res1 => {
        return requestData('url' + res1)
    }).then(res2 => {
        return requestData('url' + res2)
    }).then(res3 => {
        console.log(res3);
    })
}

我们首先会或想到我们可以直接返回一个Promise,如上,但是他的阅读性还是比较差,所以又可以想到用生成器的方式。

function* getData() {
   const res = yield requestData('url')
   const res1 = yield requestData('url' + res)
   const res2 = yield requestData('url' + res1)
   const res3 = yield requestData('url' + res2)
   console.log(res3);
}


// 自动执行generator函数
function execGenerator(genFn) {
    const generator = genFn()
    function exec(res) {
        const result = generator.next(res)
        if (result.done) return result.value
        result.value.then(res => {
            exec(res)
        })
    }
    exec()
}

execGenerator(getData)

因为在生成器函数中我们可以通过yield 来暂停函数的执行,所以我们可以利用这个特性解决他,看到这里你其实我们就可以想到async/await,其实async/await就是这个的语法糖