饶顺和 發表於 2019-11-26 16:34:00

TypeScript 迭代器(iterator)和生成器(generator)

<p>⒈迭代器(iterator)</p>
<p>  1.可迭代性</p>
<p>  当一个对象实现了<code>Symbol.iterator</code>属性时,我们认为它是可迭代的。 一些内置的类型如&nbsp;<code>Array</code>,<code>Map</code>,<code>Set</code>,<code>String</code>,<code>Int32Array</code>,<code>Uint32Array</code>等都已经实现了各自的<code>Symbol.iterator</code>。 对象上的&nbsp;<code>Symbol.iterator</code>函数负责返回供迭代的值。</p>
<p>  2.for..of语句</p>
<p><code>  for..of</code>会遍历可迭代的对象,调用对象上的<code>Symbol.iterator</code>方法。 下面是在数组上使用&nbsp;<code>for..of</code>的简单例子:</p>
<div class="cnblogs_code">
<pre>let array = ;

</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (let item of array) {
    console.log(item); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 7, "fanqi", true</span>
}</pre>
</div>
<p>  3.<code>for..of</code>&nbsp;vs.&nbsp;<code>for..in</code>&nbsp;语句</p>
<p><code>  for..of</code>和<code>for..in</code>均可迭代一个列表,但它们之间的区别很大,最明显的区别莫过于它们用于迭代的值不同,<code>for..in</code>迭代的是对象<strong>键</strong>的列表,而<code>for..of</code>迭代的是对象<strong>值</strong>的列表。</p>
<p>  下面的例子展示了两者之间的区别:</p>
<div class="cnblogs_code">
<pre>let list = ;

</span><span style="color: rgba(0, 0, 255, 1)">for</span> (let i <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> list) {
    console.log(i); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> "0", "1", "2",</span>
<span style="color: rgba(0, 0, 0, 1)">}

</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (let i of list) {
    console.log(i); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> "4", "5", "6"</span>
}</pre>
</div>
<p>  另一个区别是<code>for..in</code>可以操作任何对象;它提供了查看对象属性的一种方法。 但是&nbsp;<code>for..of</code>关注于迭代对象的值。内置对象<code>Map</code>和<code>Set</code>已经实现了<code>Symbol.iterator</code>方法,让我们可以访问它们保存的值。</p>
<div class="cnblogs_code">
<pre>let pets = <span style="color: rgba(0, 0, 255, 1)">new</span> Set(["Cat", "Dog", "Hamster"<span style="color: rgba(0, 0, 0, 1)">]);
pets[</span>"species"] = "mammals"<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 0, 255, 1)">for</span> (let pet <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> pets) {
    console.log(pet); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> "species"</span>
<span style="color: rgba(0, 0, 0, 1)">}

</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (let pet of pets) {
    console.log(pet); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> "Cat", "Dog", "Hamster"</span>
}</pre>
</div>
<p>  但这样的特性仅仅在ES6及以上才生效。</p>
<p>  当我们将TypeScript的代码生成目标设定为ES5或ES3,迭代器就只允许在<code>Array</code>类型上使用。 在非数组值上使用&nbsp;<code>for..of</code>语句会得到一个错误,即使这些非数组值已经实现了<code>Symbol.iterator</code>属性也是不可以的。</p>
<p>  编译器会生成一个简单的<code>for</code>循环做为<code>for..of</code>循环,比如:</p>
<div class="cnblogs_code">
<pre>let numbers = ;
</span><span style="color: rgba(0, 0, 255, 1)">for</span><span style="color: rgba(0, 0, 0, 1)"> (let num of numbers) {
    console.log(num);
}</span></pre>
</div>
<p>  生成的代码为:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">var</span> numbers = ;
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> _i = 0; _i &lt; numbers.length; _i++<span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> num =<span style="color: rgba(0, 0, 0, 1)"> numbers;
    console.log(num);
}</span></pre>
</div>
<h4>  当目标设定为兼容ECMAScipt 2015或更高的引擎时,编译器会生成相应引擎的<code>for..of</code>内置迭代器实现方式。</h4>
<p>⒉生成器(generator)</p>
<p>  function* 是用来创建generator函数的语法(在MDN的文档中generator称为生成器)</p>
<p>  调用generator函数时会返回一个generator对象。generator对象遵循迭代器接口,即通常所见到的next、return和throw函数。</p>
<p>  generator函数用于创建懒迭代器,例如下面的这个函数可以返回一个无限整数的列表:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span>*<span style="color: rgba(0, 0, 0, 1)"> infiniteList(){
let i </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">while</span>(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">){
    yield i</span>++<span style="color: rgba(0, 0, 0, 1)">;
}
}

</span><span style="color: rgba(0, 0, 255, 1)">const </span>iterator =<span style="color: rgba(0, 0, 0, 1)"> infiniteList();
</span><span style="color: rgba(0, 0, 255, 1)">while</span>(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">){
console.log(iterator.next()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">{ value: xxxx, done: false }</span>
}</pre>
</div>
<p>  当然,也可以设定某个条件终止它,而不只是永远循环下去。如下所示:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span>*<span style="color: rgba(0, 0, 0, 1)"> infiniteList(){
let i </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">while</span>(i &lt; 3<span style="color: rgba(0, 0, 0, 1)">){
    yield i</span>++<span style="color: rgba(0, 0, 0, 1)">;
}
}

</span><span style="color: rgba(0, 0, 255, 1)">const </span>iterator =<span style="color: rgba(0, 0, 0, 1)"> infiniteList();
</span><span style="color: rgba(0, 0, 255, 1)">while</span>(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">){
console.log(iterator.next()); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">{ value: xxxx, done: false }</span>
}</pre>
</div>
<p>  可以说这个设定是generator中最令人兴奋的部分。它在实质上允许一个函数可以暂停执行,比如当我们执行了第一次的iterator.next()后,可以先去做别的事,再回来继续执行iterator.next(),这样剩余函数的控制权就交给了调用者。</p>
<p>  当你直接调用generator函数时,它并不会执行,它只会创建一个generator对象。</p>
<p>  在下面的例子中,我们可以看到一个更灵活的使用方式:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span>*<span style="color: rgba(0, 0, 0, 1)"> generator(){
console.log(</span>'开始执行'<span style="color: rgba(0, 0, 0, 1)">);
yield </span>0<span style="color: rgba(0, 0, 0, 1)">;
console.log(</span>'执行暂停'<span style="color: rgba(0, 0, 0, 1)">);
yield </span>1<span style="color: rgba(0, 0, 0, 1)">;
console.log(</span>'执行结束'<span style="color: rgba(0, 0, 0, 1)">);
}

</span><span style="color: rgba(0, 0, 255, 1)">const</span> iterator =<span style="color: rgba(0, 0, 0, 1)"> generator();

console.log(iterator.next());

console.log(</span>'a'<span style="color: rgba(0, 0, 0, 1)">);
console.log(iterator.next());

console.log(</span>'b'<span style="color: rgba(0, 0, 0, 1)">);
console.log(iterator.next());

console.log(</span>'c');</pre>
</div>
<p>  执行它则会看到如下输出结果:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">开始执行
{ value: </span>0, done: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)"> }
a
执行暂停
{ value: </span>1, done: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)"> }
b
执行结束
{ value: undefined, done: </span><span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)"> }
c</span></pre>
</div>
<p>  从上面代码可以得知:</p>
<ul>
<li style="list-style-type: none">
<ul>
<li>generator对象只会在调用next()时开始执行。</li>
<li>函数在执行到yield语句时会暂停并返回yield的值。</li>
<li>函数在next被调用时继续恢复执行。</li>
</ul>
</li>
</ul>
<p>  所以实质上generator函数的执行与否是由外部的generator对象控制的。</p>
<p>  不过除了yield传值到外部,我们也可以通过next传值到内部进行调用。下面的例子展示了iterator.next传值的方式:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span>*<span style="color: rgba(0, 0, 0, 1)"> generator(){
let y </span>=<span style="color: rgba(0, 0, 0, 1)"> yield;
console.log(`</span>'Hello ${y}'<span style="color: rgba(0, 0, 0, 1)">`);
}

</span>const iterator =<span style="color: rgba(0, 0, 0, 1)"> generator();

console.log(iterator.next());
console.log(</span>'a'<span style="color: rgba(0, 0, 0, 1)">);
console.log(iterator.next(</span>'fanqi'));</pre>
</div>
<p>  执行它则会看到如下输出结果:</p>
<div class="cnblogs_code">
<pre>{ value: undefined, done: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)"> }
a
</span>'Hello fanqi'<span style="color: rgba(0, 0, 0, 1)">
{ value: undefined, done: </span><span style="color: rgba(0, 0, 255, 1)">true</span> }</pre>
</div>
<p>  以上便是next和return函数的内容,接下来我们来看一下throw函数如何处理迭代器内部报错。</p>
<p>  下面是一个iterator.throw的例子: </p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span>*<span style="color: rgba(0, 0, 0, 1)"> generator(){
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">{
    yield </span>1<span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)">(error){
    console.log(error.message);
}
}

const iterator </span>=<span style="color: rgba(0, 0, 0, 1)"> generator();

console.log(iterator.next());
console.log(</span>'a'<span style="color: rgba(0, 0, 0, 1)">);
console.log(iterator.</span><span style="color: rgba(0, 0, 255, 1)">throw</span>(<span style="color: rgba(0, 0, 255, 1)">new</span> Error('未知的错误发生!')));</pre>
</div>
<p>  执行它则会看到如下输出结果: </p>
<div class="cnblogs_code">
<pre>{ value: 1, done: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)"> }
a
未知的错误发生!
{ value: undefined, done: </span><span style="color: rgba(0, 0, 255, 1)">true</span> }</pre>
</div>
<p>  通过以上的案例我们可以得知,外部是可以对generator内部进行干涉的:</p>
<ul>
<li style="list-style-type: none">
<ul>
<li>外部系统可以传递一个值到generator函数体中。</li>
<li>外部系统可以抛入一个异常到generator函数体中。      </li>
</ul>
</li>
</ul>
<p>  </p>
<p>&nbsp;</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/fanqisoft/p/11936558.html
頁: [1]
查看完整版本: TypeScript 迭代器(iterator)和生成器(generator)