javascript对迭代器进行迭代

通过调用生成器得到的迭代器,暴露出一个next方法能让我们向迭代器请求一个新值。next方法返回一个携带着生成值的对象,而该对象中包含的另一个属性done也向我们指示了生成器是否还会追加生成值。

//通过调用生成器得到的迭代器,暴露出一个next方法能让我们向迭代器请求一个新值。next方法返回一个携带着生成值的对象,而该对象中包含的另一个属性done也向我们指示了生成器是否还会追加生成值。
console.log('-------使用while循环迭代生成器结果--------------');
function* WeaponGenerator() {
  yield "Katana";
  yield "Wakizashi";
}

//新建一个迭代器
const weaponGenerator = WeaponGenerator();
//创建一个变量,用这个变量来保存生成器产生的值
let item;
//每次循环都会从生成器中取出一个值,然后输出该值。当生成器不会再生成值的时候,停止迭代。
while (!(item = weaponGenerator.next()).done) {
  if (item !== null) {
    console.log("value:" + item.value);
  }
}

 

通过生成器函数再次创建了一个迭代器对象:

const weaponGenerator = WeaponGenerator();

 

我们还创建了一个变量item,用于保存由生成器生成的单个值。随后,给While循环指定了条件:

while (!(item = weaponGenerator.next()).done) {

if (item !== null) {

console.log("value:" + item.value);

}

}

在每次迭代中,我们通过迭代器weaponGenerator的next方法从生成器中取一个值,然后把值存放在item变量中。和所有next返回的对象一样,item变量引用的对象中包含一个属性value为生成器返回的值,一个属性done指示生成器是否已经完成了值的生成。如果生成器中的值没有生成完毕,我们就会进入下次循环迭代,反之停止循环。

 

for-of循环不过是对迭代器进行迭代的语法糖:

for (let weapon of WeaponGenerator()) {

if (weapon !== undefined) {

console.log("weapon:" + weapon);

}

}

 

不同于手动调用迭代器的next方法,for-of循环同时还要查看生成器是否完成,它在后台自动做了完全相同的操作。

 

把执行权交给下一个生成器

正如在标准函数中调用另一个标准函数,我们需要把生成器的执行委托给另一个生成器。
console.log("------把执行权交给下一个生成器------");
console.log("---------------使用yield操作符将执行权交给另一个生成器--------------");
function* WarriorGenerator() {
  yield 'Sun Tzu';
  yield* NinjaGenerator(); //yield*将执行权交给另一个生成器
  yield 'Genghis Kha';
}


function* NinjaGenerator() {
  yield 'Hattori';
  yield 'Yoshi';
}

for (let warrior of WarriorGenerator()) {
  if (warrior !== null) {
    console.log("warrior:" + warrior);
  }
}

 

执行这段代码后会输出所有的字符串。第一个输出Sun Tzu不会有什么问题,因为它就是生成器WarriorGenerator得到的第一个值。

 

而对于第二个输出的值是Hattori需要解释一下:

在迭代器上使用yield*操作符,程序会跳转到另一个生成器上执行。在上述代码中,程序从WarriorGenerator跳转到一个新的NinjaGenerator生成器上,每次调用WarriorGenerator返回迭代器的next方法,都会执行重新寻址到了NinjaGenerator上。该生成器会一直持有执行权直到无工作可做。所以在上述代码中,生成Sun Tzu之后紧接着是Hattori和Yoshi。仅当NinjaGenerator的工作完成后,调用原来的迭代器才会继续输出Genghis Kha。注意,对于调用最初的迭代器代码来说,这一切都是透明的。for-of循环不会关心WarriorGenerator委托到另一个生成器上,它只关心在done状态到来之前一直调用next方法。

 

使用生成器

尝试生成唯一ID值。

console.log('-------------------------通过迭代器对象控制生成器--------------------------------');
//调用生成器函数不一定会执行生成器函数体。通过创建创建迭代器对象,可以与生成器通信。例如,可以通过迭代器对象请求满足条件的值。观察迭代器对象如何工作?

//定义一个生成器,它能生成一个包含两个weapon数据的序列
function* WeaponGenerator() {
  yield 'Katana';
  yield 'Wakizashi';
}

//调用生成器得到一个迭代器,从而我们能够控制生成器的执行
const weaponsIterator = WeaponGenerator();

//调用迭代器的next方法向生成器请求一个新值。
const result1 = weaponsIterator.next();
//结果为一个对象,其中包含着一个返回值,及一个指示器告诉我们生成器是否还会生成值
if (typeof result1 === 'object' && result1.value === 'Katana' && !result1.done) {
  console.log('Katana received!');
}

//再次调用next方法从生成器中获取新值。
const result2 = weaponsIterator.next();
if (typeof result2 === 'object' && result2.value === 'Wakizashi' && !result2.done) {
  console.log('Wakizashi received!');
}

//当没有可执行的代码,生成器就会返回'undefined'值,表示它的状态已经完成
const result3 = weaponsIterator.next();
if (typeof result3 === 'object' && result3.value === undefined && result3.done) {
  console.log('There are no more results!');
}

 

在创建某些对象时,我们经常需要为每个对象赋一个唯一的ID值。最简单的方式是通过一个全局的计数器变量,但这是一种非常丑陋的写法,因为这个计数器变量很容易就会不慎淹没在混乱的代码中。另一种是使用生成器。

console.log("--------------------使用生成器生成ID序列----------------------");
//定义生成器函数IdGenerator
function* IdGenerator() {
  //一个始终记录ID的变量,这个变量无法在生成器外部改变
  let id = 0;
  //循环生成无限长度的ID序列
  while(true) {
    yield ++id;
  }
}

//这个迭代器,我们能够访问向生成器请求新的ID值。
const idIterator = IdGenerator();

//请求3个新ID值
const ninjaTest1 = { id:idIterator.next().value};
const ninjaTest2 = { id:idIterator.next().value};
const ninjaTest3 = { id:idIterator.next().value};

//测试运行结果
if (ninjaTest1.id === 1) {
  console.log('First ninjaTest1 has id 1');
}

if (ninjaTest2.id === 2) {
  console.log('Second ninjaTest2 has id 2');
}

if (ninjaTest3.id === 3) {
  console.log('Third ninjaTest3 has id 3');
}

迭代器中包含一个局部变量id,其代表ID计数器。局部变量id仅能在生成器中被访问,故而完全不必要担心在代码的其他地方修改id值。随后是一个无限的while循环,其每次迭代都能生成一个新id值并挂起执行,直到下一次ID请求到达:

 

//定义生成器函数IdGenerator

function* IdGenerator() {

//一个始终记录ID的变量,这个变量无法在生成器外部改变

let id = 0;

//循环生成无限长度的ID序列

while(true) {

yield ++id;

}

}

 

注意:标准函数中一般不应该书写无限循环的代码。但在生成器中没问题!当生成器遇到一个yield语句,它就会一直挂起执行直到下次调用next方法,所以只有每次调用一次next方法,while循环才会迭代写一次并返回下一个ID值。

 

定义了生成器之后,又创建了一个迭代器对象:

const idIterator = IdGenerator();

 

我们能够调用idIterator.next()方法来控制生成器执行。每当遇到一次yield语句生成器就会停止执行,返回一个新的ID值可以给我们的对象赋值:

const ninjaTest1 = { id:idIterator.next().value};

 

看到这个方法是多么简单了吧?代码中没有任何会被不小心修改的全局变量。相反,我们使用迭代器从生成器中请求值。另外,如果还需要用另外一个迭代器来记录ID序列,我们只需要直接再初始化一个新迭代器就可以了。

 

使用迭代器遍历DOM树

网页的布局是基于DOM结构的,它是由HTML节点组成的树形结构,除了根节点的每个节点都只有一个父节点,而且可以有0个或多个孩子节点。由于DOM是网页开发中的基础,所以我们大部分代码都是围绕着对它的遍历。遍历DOM的相对简单的方式就是实现一个递归函数,在每次访问节点的时候都会被执行。

<div id="subTree">
    <form>
      <input type="text"/>
    </form>
    <p>Paragraph</p>
    <span>Span</span>
  </div>
  <script>
    console.log('--------------------遍历递归DOM树---------------------');
      function traverseDOM(element, callback) {
        //用回调函数处理当前节点
        callback(element);
        element = element.firstElementChild;
        //遍历每个子树
        while (element) {
         traverseDOM(element, callback);
         element = element.nextElementSibling;
        }
      }

      const subTree = document.getElementById("subTree");
      //通过调用traverseDOM方法从根节点开始遍历
      traverseDOM(subTree, function (element) {
        if (element !== null) {
          console.log("nodeName:" + element.nodeName);
        }
      });
  </script>

使用递归函数函数来遍历id为subtree的所有节点,在访问每个节点的过程中我们还记录了该节点的类型。

<div id="subTree">
    <form>
      <input type="text"/>
    </form>
    <p>Paragraph</p>
    <span>Span</span>
  </div>
  <script>
    console.log('--------------------使用生成器遍历DOM树-----------------');
    //用yield*将迭代控制转移到另一个DomTraveral生成器实例上。
    function* DomTraversal(element) {
      yield element;
      element = element.firstElementChild;
      while (element) {
        yield* DomTraversal(element);
        element = element.nextElementSibling;
      }
    }

    const subTree = document.getElementById('subTree');
    //使用for-of循环对节点进行循环迭代
    for(let element of DomTraversal(subTree)) {
      if (element !== null) {
        console.log("nodeName:" + element.nodeName);
      }
    }
  </script>

通过生成器实现DOM遍历,就像标准递一样简单,但它不必书写丑陋的回调函数代码。不同于在下一层递归处理每个访问过的节点子树,我们为每个访问过的节点创建一个生成器并将执行权交给它,从而使我们能够以迭代的方式书写概念上递归的代码。它的好处在于我们能够不凭借讨厌的回调函数,仅仅以一个简单的for-of循环就能处理生成的节点。

 

上述代码告诉我们如何在不必使用回调函数的情况下,使用生成器函数来解耦代码,从而将生产值(本例中是HTML节点)的代码和消费值(本例中的for-of循环打印、访问过的节点)的代码分隔开。除此以外,在很多场景下,使用迭代器要比使用递归都要自然,所以保持一个开放的思路很重要。

 参考《JavaScript忍者秘籍》

猜你喜欢

转载自blog.csdn.net/zhangying1994/article/details/85522365