ES2015减少的这两个个性之前始终没有很明确,或者是说看完教程就很容易遗记,这次通过写一篇博文来加强了解与记忆

Iterator

Iterator中文名为迭代器,顾名思义是用来循环遍历的,咱们平时说到循环遍历必定会想到for、while循环以及数组的内建办法。for、while循环就不多说了,这是语言中最根底的循环迭代办法,毛病是对于很简略的迭代须要写一些模板代码,不够易用。所以对于那些罕用的须要遍历的数据类型比方Array,JS提供了forEach,map,filter等不便且附带特定性能的办法。当然这些办法都属于数组,对于另外的咱们想遍历的数据结构比方字符串就没方法应用了,于是ES2015又退出了for...of循环,能够用来遍历任意的可迭代对象


那么问题来了,这个可迭代对象是什么呢?这就波及到ES2015退出的新概念,可迭代协定(iterable protocol)与迭代器协定(iterator protocol)。

  • 可迭代协定

    可迭代协定规定可迭代对象必须实现一个叫Symbol.iterator的办法,该办法必须返回一个迭代器对象。下面的问题就有了答案,只有一个对象自身或者原型链中具备这个办法,那么这个对象就是可迭代的。

    // 这样的对象就是一个可迭代对象const iterableObj = {    // 必须有该办法  [Symbol.iterator]() {    return <iterator object>; //该办法必须返回迭代器对象  }};

    迭代器对象又是什么呢?这就是迭代器协定的内容了。

  • 迭代器协定

    迭代器协定规定迭代器对象必须有一个next办法,该办法必须返回一个具备donevalue属性的对象。这样说比拟形象,以下代码示例能够形象的展现迭代器对象:

    // 这样的对象就是迭代器对象const itratorObj = {  next() {    return {      done: false, // 当该值为ture时迭代完结      value: null, // 每次迭代返回的值,当done为true时能够省略    };  }};


弄清楚了下面两个基本概念后,咱们就了解了何为可迭代对象。有些内置对象本人实现了可迭代协定,所以他们是可迭代的(能够用for...of遍历),比方Array,String,Set,Map等。当然咱们本人定义的对象也能够是可迭代的,只有咱们本人实现可迭代协定。上面就举一个例子。

const range = {  from: 1,  to: 5,};

咱们有以上对象,如果当初间接应用for...of循环去遍历,那必定是报错的,错误信息为range不可迭代。咱们想让他变的可迭代并且依照咱们本人想要的形式迭代:从from迭代到to,仅须要实现可迭代协定即可。

const range = {  form: 1,  to: 5,  [Symbol.iterator]() {    // 如何实现?        return {      next() {                // 上面的两个属性什么时候变动?        return {          done: false,          value: 1,        };      }    };  }};

说起来笨重,然而这个代码要怎么实现呢?这就波及到Symbol.iterator办法的运行机制了。

当一个对象进行迭代的时候,在迭代之前会先执行Symbol.iterator办法,而后应用该办法返回的迭代器对象来进行迭代管制。艰深来说就是,每次迭代的时候都执行一次next办法,把value值作为该次迭代的返回值,当done为true时迭代完结。顺着这个思路就能够实现咱们的自定义迭代办法了。

const range = {  from: 1,  to: 5,  [Symbol.iterator]() {    let cur = this.from; // 迭代前初始化一个以后迭代值    const to = this.to; // 暂存终止值        return {      next() {        const done = cur > to; // 当迭代值大于to的时候迭代完结                return {          done,          value: done ? undefined : cur++, // 将迭代值返回        };      }    };  }};// 验证for (let item of range) {  // 这里须要留神:for...of此类的迭代操作会疏忽done为true的值  console.log(item); // 1 2 3 4 5}

一般来说,举荐对象既实现可迭代协定,也实现迭代器协定,这样被称为格局良好的可迭代对象,就像这样:

const range = {  from: 1,  to: 5,  // 对象具备next办法,合乎迭代器协定  next() {    const done = this._iteratorVal > this.to;    return {      done,      value: done ? undefined : this._iteratorVal++,    };  },  [Symbol.iterator]() {    this._iteratorVal = this.from;    return this; // 可迭代协定返回对象自身  }};

咱们能够实现一个用于生成可迭代对象forEach的通用办法,更不便的去迭代非数组的可迭代对象。

function iterableForEachFactory(obj) {  return function (callback) {    let index = 0;    for (let item of obj) {      callback(item, index);      index++;    }  }}const rangeForEach = iterableForEachFactory(range);rangeForEach((item, index) => console.log(item, index)); // 1 2 3 4 5, 0 1 2 3 4

到目前为止,咱们只在对象中应用迭代器,有时候咱们仅仅只须要一个迭代器去进行自定义的迭代逻辑,相似于for、while循环,然而for while循环是主动的,一旦开始迭代就无奈暂停,只能彻底跳出循环。利用迭代器协定,咱们能实现一个能够暂停的手动循环,并且能自定义循环逻辑。

function iteratorMaker(param) {  let index = 0;  let lastValue = null;  // param能够是代表迭代次数的数字,也能够是自定义迭代逻辑的函数,也能够不传  const isFunction = typeof param === 'function';  const isNumber = typeof param === 'number';    return {    next() {      const nextIndex = index++;      lastValue = isFunction ? param(lastValue, nextIndex) : nextIndex;      // 如果传入了自定义迭代函数,那么返回undefined能够终止循环      const done = isFunction ?        lastValue === undefined :              isNumber ? nextIndex >= param : false;            return {        done,        value: done ? undefined : lastValue,      };    }  };}// 循环两次的一般迭代器const iteratorCircle2 = iteratorMaker(2);iteratorCircle2.next().value; // 0iteratorCircle2.next().value; // 1iteratorCircle2.next().value; // undefined// 每次翻倍的无穷迭代器const iteratorDoubleCircle = iteratorMaker(doubleCircle);function doubleCircle(lastValue) {  return lastValue ? lastValue * 2 : 1;}iteratorDoubleCircle.next().value; // 1iteratorDoubleCircle.next().value; // 2iteratorDoubleCircle.next().value; // 4iteratorDoubleCircle.next().value; // 8

Generator

前文具体阐明了Iterator的作用与用法,接下来了解并应用Generator(生成器)就非常简单了。能够这么简略粗犷的了解:Generator就是Iterator的语法糖,然而它有更高级的性能。他们的关系很容易让人想到Proxy与Reflect,Reflect能够完满配合Proxy的语法,它也有本人额定的性能。

  • 语法

    function* gen() {  yield 1;  yield 2;  yield 3;  return; // return是可选的,相当于done: true}// 这个就是generator对象,它既实现了可迭代协定,也实现了迭代器协定const g = gen();typeof g[Symbol.iterator]; // functiontypeof g.next; // functiong[Symbol.iterator]() === g; // true

    看到这里是不是很眼生,没错,generator对象和咱们之前实现的格局良好的可迭代对象range是很相似的。

    咱们能够很容易的将range的例子应用generator重写一下:

    const range = {  from: 1,  to: 5,  *[Symbol.iterator]() {    for (let i = this.from; i <= this.to; i++) {      yield i;    }  }};// 验证for (let item of range) {  console.log(item); // 1 2 3 4 5}
  • 组合

    Generator能够嵌套进行组合,来实现各种各样的性能。

    function* generateSequence(start, end) {  for (let i = start; i <= end; i++) yield i;}function* generatePasswordCodes() {  // 0..9  yield* generateSequence(48, 57); // 组合的语法为yield*前面跟任意generator函数  // A..Z  yield* generateSequence(65, 90);  // a..z  yield* generateSequence(97, 122);}let str = '';for(let code of generatePasswordCodes()) {  str += String.fromCharCode(code);}console.log(str); // 0..9A..Za..z

    也能够应用组合递归来实现数组打平

    const arr = [1, 2, 3, [4, 5, [6, 7, 8, [9]]]];function* flatten(arr) {  for (let item of arr) {    if (Array.isArray(item)) {      yield* flatten(item);    } else {      yield item;    }  }}const flatArr = [...flatten(arr)];console.log(flatArr); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
  • 双向yield

    仔细的读者会发现,到目前为止,咱们应用generator是实现不了上文中iteratorMaker函数的,这里咱们就须要介绍generator的一个最弱小的概念:双向yield。

    yield给人的感觉是与iterator中的value一样,向内部迭代输入值,然而它还能够接管内部next函数传入的值,作为下一次执行的输入后果。

    function* gen() {  const result = yield 1; // 将yield表达式赋值给一个变量就实现了双向yield  yield result * 2; // 下一次next函数传入的值会赋值给result}const g = gen();g.next().value; // 1g.next(2).value; // 4

    理解这个个性之后,咱们就能够对iteratorMaker函数用generator重写了:

    function iteratorMaker(param) {  let index = 0;  let lastValue = null;  const isFunction = typeof param === 'function';  const isNumber = typeof param === 'number';  function* gen() {    let passValue = null;    while (isNumber ? index <= param : lastValue !== undefined) {      passValue = yield (passValue === null ? lastValue : initial);    }  }  const g = gen();    return {    next() {      const nextIndex = index++;      lastValue = isFunction ? param(lastValue, nextIndex) : nextIndex;      return g.next(lastValue);    }  };}

总的来说,Iterator与Generator都是ES2015外面比拟少用的个性,也有肯定的复杂度,特地是Generator的双向yield,须要多入手写一写实例能力比拟好的了解它,心愿这篇文章能对你有用。