JavaScript 中的迭代器与生成器

134 阅读4分钟

迭代器

例子

const arr = [1, 2, 3, 4];
for (x of arr) {
  console.log(x);
}

console.log(arr[Symbol.iterator]);  // ƒ values() { [native code] }

const iterator = arr[Symbol.iterator]();
for (let result = iterator.`next()`; !result.done; result = iterator.`next()`) {
  console.log(result.value);
};

三个对象

  • 可迭代对象: 在 JavaScript 中,可迭代对象就是继承了 Iterable 接口的对象,即拥有 Symbol.iterator 作为键的属性。而这个属性必须引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器
  • 迭代器对象: 迭代器对象是一种一次性使用的对象,用于迭代与其关联的可迭代对象。使用 next() 方法在可迭代对象中遍历数据
  • 迭代器结果对象: 迭代器结果对象就是迭代器返回的对象,是一个拥有 value 键与 done 键的对象,迭代器对象每次调用 next() 即返回迭代器结果对象,数据放在 value 属性中,当迭代结束时, donetrue

迭代器对象也是可以迭代的

console.log(arr[Symbol.iterator]()[Symbol.iterator])
console.log(arr[Symbol.iterator]()[Symbol.iterator]()[Symbol.iterator])

console.log(arr[Symbol.iterator]()[Symbol.iterator]()[Symbol.iterator] == arr[Symbol.iterator]()[Symbol.iterator]) // true

自定义迭代器

class Counter {
  constructor(n) {
    this.n = n;
  }
  [Symbol.iterator]() {
    let x = 0, n = this.n;
    return {
      next() {
        return x > n ? { done: true } : {value: x++};
      },
      [Symbol.iterator]() {
        return this;
      }
    }
  }
}
let counter = new Counter(10);
const countNext = counter[Symbol.iterator]();
console.log(countNext); // {next: ƒ, Symbol(Symbol.iterator): ƒ}
const countResult = countNext.next();
console.log(countResult); // {value: 0}
for(x of counter) {
  console.log(x)
}

我们在类中实现 Symbol.iterator 方法,并返回拥有 next() 方法的对象,则这就可以实例化出可迭代对象,并可以使用 for of 等进行遍历

其中, counter 就是可迭代对象, countNext 是迭代器对象, countResult 是迭代结果对象

提前终止迭代器

我们还可以给迭代器添加一个 return() 方法,用于将没有消费完但又不再需要的迭代器对象做一些操作,比如关闭文件等

for of 循环中如果使用 break ,就会自动调用迭代器的 return()return() 必须返回一个迭代器结果对象, donetrue

class Counter {
  constructor(n) {
    this.n = n;
  }
  [Symbol.iterator]() {
    let x = 0, n = this.n;
    return {
      next() {
        return x > n ? { done: true } : {value: x++};
      },
      [Symbol.iterator]() {
        return this;
      },
      return() {
        console.log('return()');
        return { done: true }
      }
    }
  }
}
let counter = new Counter(10);
const iterator = counter[Symbol.iterator]();
for(x of iterator) {
  console.log(x)
  if (x === 4) break;
}
console.log("counter:", counter);
// 这里会继续打印上上面打印完的元素
for(x of iterator) {
  console.log(x)
}

生成器

例子

function* generator() {
  yield 35;
  yield 'knight'
}

const gen = generator();
console.log(gen.next()); // {value: 35, done: false}
console.log(gen.next()); // {value: "knight", done: false}
console.log(gen.next()); // {value: undefined, done: true}
console.log("+++++++++++");
for (x of generator()) {
  console.log(x) // 35 // knight
}

生成器是一个方法,只是在声明时需要在 function 之后,方法名之前加一个 * 号, * 号两边是否又空格都无所谓,如果使用简便写法,将 * 放在方法名前面即可

无法使用箭头函数声明生成器

yield

在生成器中,函数在执行之后回生成一个生成器对象,调用生成器对象的 next() 方法回开始执行函数,知道遇到 yield 关键字,则暂停执行,并返回生成器结果对象,当在此调用 next() 方法,则继续执行函数,知道返回 done: true ,生成器对象消费完之后如果继续调用 next() 方法,则返回 {value: undefined, done: true}

yield 关键字只能在生成器函数中使用,即带有 *号的方法,如果在生成器中的普通函数或方法中使用也不可以,

class Counter {
  constructor(n) {
    this.n = n;
  }
  *[Symbol.iterator]() {
    for (let i = 0; i < this.n; i++) {
      yield i;
    }
  }
}
let counter = new Counter(10);
for(x of counter) {
  console.log(x)
}

也可以直接使用生成器实现迭代器函数

yield*

yield* 可以迭代可迭代对象,然后会送得到的每一个值

function* generator(arr) {
  yield* arr
}

const gen = generator('qweasdzxc');
for (const x of gen) {
  console.log(x)
}

所以通过 yield* 我们可以递归我们的生成器

function* generator(n) {
  if (n) {
    yield* generator(n-1);
    yield n-1;
  }
}

const gen = generator(10);
for (const x of gen) {
  console.log(x)
}

生成器返回值

function* generator() {
  yield 35;
  yield 'knight';
  return 'over';
  yield 'not';
}

const gen = generator();
console.log(gen.next()); // {value: 35, done: false}
console.log(gen.next()); // {value: "knight", done: false}
console.log(gen.next()); // {value: "over", done: true}
console.log(gen.next()); // {value: undefined, done: true}

如果我们在生成器中写 return ,那么返回值回作为迭代器结果对象的 value 值,只不过此时 donetrue ,所以 for of 等会忽略这个 vaule

而在 return 之后的逻辑则不回执行

yieldnext() 传值

yield 关键字是有返回值的,返回值就是调用 next() 时传进去的值

第一次调用 next() 时传的值无法获取到

function* generator() {
  const innerFirst = yield 35;
  console.log('innerFirst', innerFirst);
  const innerSecond = yield 'knight';
  console.log('innerSecond', innerSecond);
}

const gen = generator();
console.log('outFirst', gen.next('outFirst'));
console.log('outSecond', gen.next('outSecond'));
console.log('outThird', gen.next('outThird'));

image1.jpg

提前终止生成器

return()

如果调用 return() 可以提前终止迭代器对象,如果迭代器对象未消费完也无法再进行消费

function* generator() {
  yield 35;
  yield 'knight';
  yield 'coder';
}

const gen = generator();
console.log(gen.next()); // {value: 35, done: false}
console.log(gen.next()); // {value: "knight", done: false}
console.log(gen.return()); // {value: undefined, done: true}
console.log(gen.next()); // {value: undefined, done: true}

throw()

使用 throw() 可以想生成器抛一个错误

function* generator() {
  const innerFirst = yield 35;
  console.log('innerFirst', innerFirst);
  const innerSecond = yield 'knight';
  console.log('innerSecond', innerSecond);
  const innerThird = yield 'coder';
  console.log('innerThird', innerThird);
}

const gen = generator();
console.log(gen.next('outFirst'));
console.log(gen.next('outSecond'));
console.log(gen.throw('outThrow'));
console.log(gen.next('outThird'));

image2.jpg

我们也可以使用 try catch 语句来处理这个错误

function* generator() {
  try{
    const innerFirst = yield 35;
    console.log('innerFirst', innerFirst);
    const innerSecond = yield 'knight';
    console.log('innerSecond', innerSecond);
    const innerThird = yield 'coder';
    console.log('innerThird', innerThird);
  } catch(e) {
    console.log('catch', e)
  }
}

const gen = generator();
console.log(gen.next('outFirst'));
console.log(gen.next('outSecond'));
console.log(gen.throw('outThrow'));
console.log(gen.next('outThird'));

image3.jpg