JavaScript进阶——05-迭代器和生成器【万字长文,感谢支持】

迭代器

概念

迭代器(Iterator)是 JavaScript 中一种特殊的对象,它提供了一种统一的、通用的方式遍历个各种不同类型的数据结构。可以遍历的数据结构包括:数组、字符串、Set、Map 等可迭代对象。我们也可以自定义实现迭代器,以支持遍历自定义的数据结构。

通过迭代器,我们可以按顺序逐个获取数据中的元素,不需要手动跟踪索引(索引也可称之为指针、游标)。迭代器的行为很像数据库中的游标(cursor)。

我们也不需要关心可迭代对象内部的实现细节,即不需要关心目标对象是数组还是字符串,还是其他的数据结构。对于迭代器来说,这些数据结构都是一样的处理方式。

迭代器是一种常见的编程模式,最早出现在1974年设计的CLU编程语言中。不仅仅在JS中,其他许多编程语言(比如 Java、Python 等)都提供了迭代器的概念和实现。技术实现各有不同,但目的都是帮助我们用通用的方式遍历对象的数据结构,提高代码的简洁性、可读性和维护性。

迭代协议

迭代协议并不是编程语言的内置实现或语法,而是协议。迭代协议具体分为两个协议:可迭代协议、迭代器协议。

迭代器协议规定了产生一系列值(无论是有限个还是无限个)的标准方式。

迭代器是一个具体的对象,这个对象要符合迭代器协议。在JS中,某个对象只有实现了符合特定要求的 next() 方法,这个对象才能成为迭代器

实现原理:next() 方法

在JS中,迭代器的实现原理是通过定义一个特定的next() 方法,该方法在每次迭代中返回一个包含两个属性的对象:done 和 value。

具体来说,next() 方法有如下要求:

(1)参数:无参数或者有一个参数。

(2)返回值:返回一个应当有以下两个属性的对象。属性值如下:

  • done 属性(Boolean 类型):表示迭代是否已经完成。当迭代器遍历完所有元素时,done 为 true,否则为 false。具体解释如下:

    • 如果迭代器可以产生序列中的下一个值,则为 false,这等价于没有指定 done 属性。

    • 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 可以省略,如果 value 依然存在,即为迭代结束之后的默认返回值。

  • value 属性:包含当前迭代步骤的值,可能是具体的值,也可能是 undefined。每次调用 next() 方法时,迭代器返回下一个值。done 为true时,可以省略。

举例:为数组创建迭代器

按照上面讲的迭代器协议,我们可以给一个数组手动创建一个用于遍历的迭代器。代码举例如下:

 const strArr = ['qian', 'gu', 'yi', 'hao'];​// 为数组封装迭代器function createArrayIterator(arr) {let index = 0;return {next: () => {if (index < arr.length) {return { done: false, value: arr[index++] };} else {return { done: true };}},};}​const strArrIterator = createArrayIterator(strArr);console.log(JSON.stringify(strArrIterator.next()));console.log(JSON.stringify(strArrIterator.next()));console.log(JSON.stringify(strArrIterator.next()));console.log(JSON.stringify(strArrIterator.next()));console.log(JSON.stringify(strArrIterator.next()));

打印结果:

 {"done":false,"value":"qian"}{"done":false,"value":"gu"}{"done":false,"value":"yi"}{"done":false,"value":"hao"}{"done":true}

你可能会有疑问:实际开发中,我们真的需要大费周章地为一个简单的数组写一个迭代器函数吗?数组直接拿来遍历不就完事了吗?

是的,这大可不必。初衷是为了了解迭代器的原理。

可迭代对象

我们要注意区分一些概念:迭代器、可迭代对象、容器。迭代器是提供迭代功能的对象。可迭代对象是被迭代的目标对象,也称之为容器。

概念

当一个对象实现了 iterable protocol 协议时,它就是一个可迭代对象。这个对象要求必须实现了 @@iterator 方法,在内部封装了迭代器。我们可以通过 Symbol.iterator 函数调用该迭代器。

当我们使用迭代器的方式去遍历数组、字符串、Set、Map 等数据结构时,这些数据对象就属于可迭代对象。这些数据对象本身,内部就自带了迭代器。

可是,有些数据对象,并不具备可迭代的能力,那要怎么封装成可迭代对象呢?以及,可迭代对象需要具备什么特征?可迭代对象有什么用处?这就是本段要讲的内容。

可迭代对象的特征

凡是可迭代对象,都具备如下特征:

1、可迭代对象都有一个 [Symbol.iterator] 函数。通过这个函数,我们可以进行数据遍历操作。以一个简单的数组进行举例:

 const myArr = ['qian', 'gu', 'yi', 'hao'];​console.log(typeof myArr[Symbol.iterator]);console.log(myArr[Symbol.iterator]);​console.log(typeof myArr[Symbol.iterator]());console.log(myArr[Symbol.iterator]());// 获取数组自带的迭代器对象const myIterator = myArr[Symbol.iterator]();​// 通过迭代器的 next() 方法遍历数组console.log(myIterator.next());console.log(myIterator.next());console.log(myIterator.next());console.log(myIterator.next());console.log(myIterator.next());

打印结果:

image-20230525211636012

2、可迭对象可以进行 for ... of 操作。其实 for ... of 底层就是调用了 @@iterator 方法。代码举例:

 const myArr = ['qian', 'gu', 'yi', 'hao'];​// 可迭代对象可以进行 for ... of 操作。for ... of 也是一种遍历操作。for (const item of myArr) {// 这里的 item,其实就是迭代器里的 value 属性的值。console.log(item);}

打印结果:

 qianguyihao

原生可迭代对象

以下这些对象,都是原生可迭代对象,请务必记住:

  • String 字符串

  • Array 数组

  • Map

  • Set

  • arguments 对象

  • NodeList 对象(DOM节点的集合)

原生可迭代对象的内部已经实现了可迭代协议,它们都符合可迭代对象的特征。比如,它们内部都有一个迭代器;他们可以用 for ... of 进行遍历。

为何要记住上面这些可迭代对象,因为可迭代对象的应用场景非常多,且非常好用。我们继续往下学习。

可迭代对象的应用场景(重要)

可迭代对象有许多应用场景,包括但不仅限于:

1、JavaScript的语法:

  • for ... of

  • 展开语法 ...

  • yield*

  • 解构赋值

2、创建一些对象:

  • new Map([Iterable]):参数是可选的,可不传参数,也可以传一个可迭代对象作为参数

  • new WeakMap([iterable])

  • new Set([iterable])

  • new WeakSet([iterable])

3、方法调用

  • Array.from(iterable):将一个可迭代对象转为数组

  • Promise.all(iterable)

  • Promise.race(iterable)

今后在遇到这些应用场景时,这些原生可迭代对象可以直接拿来用。

比如说,通过阅读官方文档后我们得知,new Set()的写法中,括号里的参数可以不写,也可以传入一个可迭代对象 iterable。那么,字符串、数组、Set、Map等可迭代对象,在你需要的时候都可以传进去使用。而且,const a = new Set()写法中,变量 a 也是一个可迭代对象。

Promise.all(iterable) 只能传数组吗?非也。准确来说,Promise.all()的参数中,传入的不是数组,而是一个可迭代对象。代码举例:

 const promise1 = Promise.resolve('promise1 resolve');const promise2 = Promise.resolve('promise2 resolve');const promise3 = Promise.resolve('promise3 resolve');​const promiseSet = new Set();promiseSet.add(promise1);promiseSet.add(promise2);promiseSet.add(promise3);​// 准确来说,Promise.all()的参数中,传入的不是数组,而是一个可迭代对象Promise.all(promiseSet).then(res => {console.log('res:', res);});

代码举例:

 res: ['promise1 resolve', 'promise2 resolve', 'promise3 resolve']

arguments 同样是一个可迭代对象,但不是数组。我们可以通过Array.from(iterable)方法将 arguments 转为数组,进而让其享受数组的待遇,调用数组的各种方法。代码举例:

 foo('a', 'b', 'c');​// 定义函数function foo() {// Array.from() 中的参数可以传入可迭代对象,将参数转为数组。arguments 是 foo() 函数的参数const arr = Array.from(arguments);console.log(arr);}

打印结果:

['a', 'b', 'c']

学完了迭代器、可迭代对象的知识之后,很多关于函数传参、数据遍历、数据结构等方面的JS知识,就能融会贯通了。

手写迭代器

很多数据对象由于不是可迭代对象,我们可以为其手动创建一个迭代器,这个数据对象就成了可迭代对象。

为普通对象创建外部迭代器

代码举例:

const myObj1 = {strArr: ['qian', 'gu', 'yi', 'hao'],
};// 为 myObj.strArr 封装迭代器
let index = 0;
const strArrIterator = {next: () => {if (index < myObj1.strArr.length) {return { done: false, value: myObj1.strArr[index++] };} else {return { done: true };}},
};console.log(strArrIterator.next());
console.log(strArrIterator.next());
console.log(strArrIterator.next());
console.log(strArrIterator.next());
console.log(strArrIterator.next());

打印结果:

{done: false, value: 'qian'}
{done: false, value: 'gu'}
{done: false, value: 'yi'}
{done: false, value: 'hao'}
{done: true}

将普通对象封装为可迭代对象

上面的数据 myObj1,不属于可迭代对象,因此我们单独写了一个迭代器对象 strArrIterator。但是这两个对象是分开的。

还有一种更高级的做法是,把迭代器封装到数据对象的内部。完事之后,这个数据对象就是妥妥的可迭代对象。

将普通的数据对象封装为可迭代对象时,具体做法是:在数据对象内部,创建一个名为[Symbol.iterator]的迭代器函数,这个函数名是固定的(这种写法属于计算属性名);然后这个函数内需要返回一个迭代器,用于迭代当前的数据对象。

我们以下面这两个对象为例:

const myObj1 = {strArr: ['qian', 'gu', 'yi', 'hao'],
};const myObj2 = {name: 'qianguyihao',skill: 'web',
};

如果尝试用 for of 去遍历它们,会报错:

const myObj2 = {name: 'qianguyihao',skill: 'web',
};for (const item of myObj2) {// 打印报错:Uncaught TypeError: myObj2 is not iterable。意思是:myObj2 不是可迭代对象console.log(item);
}

所以,我们可以将这两个普通对象封装为可迭代对象。

1、将 myObj1 封装为可迭代对象,遍历 myObj1.strArr。代码举例如下:

const myObj1 = {strArr: ['qian', 'gu', 'yi', 'hao'],// 在 myObj1 的内部创建一个迭代器[Symbol.iterator]: function () {let index = 0;const strArrIterator = {next: function () {if (index < myObj1.strArr.length) {return { done: false, value: myObj1.strArr[index++] };} else {return { done: true };}},};return strArrIterator;},
};// 获取 myObj1 的迭代器对象
const strArrIterator = myObj2[Symbol.iterator]();
// 通过迭代器遍历 myObj1.strArr 的数据
console.log(strArrIterator.next());
console.log(strArrIterator.next());
console.log(strArrIterator.next());
console.log(strArrIterator.next());
console.log(strArrIterator.next());

打印结果:

{done: false, value: 'qian'}
{done: false, value: 'gu'}
{done: false, value: 'yi'}
{done: false, value: 'hao'}
{done: true}

上方代码有一个改进之处,如果把迭代器函数改成箭头函数,就可以通过 this.strArr 代表 myObj2.strArr 了,写法更简洁。代码改进如下:

const myObj1 = {strArr: ['qian', 'gu', 'yi', 'hao'],// 在 myObj1 的内部创建一个迭代器[Symbol.iterator]: function () {let index = 0;const strArrIterator = {next: () => {if (index < this.strArr.length) {return { done: false, value: this.strArr[index++] };} else {return { done: true };}},};return strArrIterator;},
};// 获取 myObj1 的迭代器对象
const strArrIterator = myObj2[Symbol.iterator]();
// 通过迭代器遍历 myObj1.strArr 的数据
console.log(strArrIterator.next());
console.log(strArrIterator.next());
console.log(strArrIterator.next());
console.log(strArrIterator.next());
console.log(strArrIterator.next());

打印结果不变。

2、将 myObj2 封装为可迭代对象,遍历里面的键值对。代码举例如下:

const myObj2 = {name: 'qianguyihao',skill: 'web',// 将普通对象 myObj2 封装为可迭代对象,目的是遍历 myObj2 的键值对[Symbol.iterator]: function () {// const keys = Object.keys(this); // 获取对象的 key// const values = Object.values(this); // 获取对象的 valueconst entries = Object.entries(this); // 获取对象的键值对let index = 0;const iterator = {next: () => {if (index < entries.length) {return { done: false, value: entries[index++] };} else {return { done: true };}},};return iterator;},
};// 可迭对象可以进行for of操作,遍历对象的键值对
for (const item of myObj2) {const [key, value] = item;console.log(key, value);
}

打印结果:

name qianguyihao
skill web

将自定义类封装为可迭代对象

在面向对象开发时,如果你希望自己创建的类也具备可迭代的能力,那么,你可以在定义类的时候手动添加一个 @@iterator方法,让其成为可迭代对象。

代码举例:

// 定义类
class Person {constructor(name, arr) {this.name = name;this.arr = arr;}// 定义一个名为 [Symbol.iterator] 的实例方法,封装迭代器[Symbol.iterator]() {let index = 0;const iterator = {next: () => {if (index < this.arr.length) {return { done: false, value: this.arr[index++] };} else {return { done: true };}},};return iterator;}
}const person1 = new Person('千古壹号', ['前端', '工程师']);
const person2 = new Person('许嵩', ['想象之中', '有何不可']);// Person的实例已经封装为可迭代对象了,可以通过 for ... of 进行遍历
for (const item of person2) {console.log(item);
}

打印结果:

想象之中
有何不可

如何中断迭代器,停止继续遍历

迭代器在遍历数据对象的过程中,如果我们希望在符合指定条件下停止继续遍历,那么,我们可以使用 break、return、throw 等关键字中断迭代器。其中, break 关键字用得最多。

此外,我们还可在迭代器函数中添加一个名为return()的方法,这个方法的作用是监听迭代器的中断,书写代码的位置与 next()方法并列。

代码举例如下:

const myObj2 = {name: 'qianguyihao',skill: 'web',// 将普通对象 myObj2 封装为可迭代对象,目的是遍历 myObj2 的键值对[Symbol.iterator]: function () {const entries = Object.entries(this); // 获取对象的键值对let index = 0;const iterator = {next: () => {if (index < entries.length) {return { done: false, value: entries[index++] };} else {return { done: true };}},// 【关键代码】监听迭代器的中断return: () => {console.log('迭代器被中断了');return { done: true };},};return iterator;},
};// 可迭对象可以进行 for of 操作,遍历对象的键值对
for (const item of myObj2) {const [key, value] = item;console.log(key, value);if (value == 'qianguyihao') {// 【关键代码】如果发现 value 为 qianguyihao,则中断迭代器,停止继续遍历break;}
}

打印结果:

name qianguyihao
迭代器被中断了

根据打印结果可以看出,迭代器只遍历了 myObj2 对象的第一个元素,符合指定条件后,通过 break 语句中断了迭代器,停止了继续遍历;与此同时,迭代器中的 return() 函数监听到了迭代器的中断。对了,return() 函数中,还需要写 return { done: true }表示迭代器的使命已结束;如果不写这行则会报错:Uncaught TypeError: Iterator result undefined is not an object

生成器

概念

我们平时写的函数,基本是通过 return 返回值,或者发生异常,函数才会终止执行。这还不够灵活。

生成器是 ES6 中新增的一种特殊的函数,所以也称为“生成器函数”。它可以更灵活地控制函数什么时候执行, 什么时候暂停等等,控制精度很高

生成器函数使用 function* 语法编写。最初调用时,生成器函数不执行任何代码,而是返回一个称为 Generator 的迭代器。通过调用生成器的 next() 方法时,Generator 函数将执行,直到遇到 yield 关键字时暂停执行。

可以根据需要多次调用该函数,并且每次都返回一个新的 Generator,但每个 Generator 只能迭代一次。

生成器函数和普通函数的区别

  • 生成器函数需要在 function 关键字后面加一个符号 *

  • 生成器函数可以通过 yield 关键字控制函数的执行流程。

  • 生成器函数的返回值是一个生成器(Generator)。生成器是一种特殊的迭代器。

生成器函数拆解

定义一个生成器函数

如果要定义一个生成器函数,我们需要在function单词和函数名之间加一个*符号。

*符号有下面四种写法,最推荐的是第一种写法:

function* generator1() { /*code*/ } // 推荐写法
function *generator2() { /*code*/ }
function * generator3() { /*code*/ }
function*generator4() { /*code*/ }

截图如下:

image-20230624184902630

代码举例:

function* foo() {console.log('1');console.log('2');console.log('3');
}foo();

yield 表达式

但是上面的代码写完后,并不会有打印结果,因为我们还需要调用生成器的 next()方法,生成器函数才会执行,直到遇到 yield 关键字后暂停执行;反反复复,直到遇到 return关键字,或者遇到函数末尾时,结束执行。代码举例:

// 通过 * 符号,定义一个生成器函数
function* foo() {console.log('1');yield;console.log('2');// 下面这行 console.log('a') 会跟 yield 一起执行yield console.log('a');console.log('3');
}const generator = foo(); // 返回一个生成器对象
// 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行
generator.next(); // 这行代码执行后,打印结果是:1
generator.next(); // 这行代码执行后,打印结果是:1 2 a
generator.next(); // 这行代码执行后,打印结果是:1 2 a 3

仔细看注释,生成器 generator 每调用一次 next() ,foo()函数里的代码就往下执行一次,直到遇到 yield 后暂停。

next() 方法的返回值

生成器既然是一种特殊的迭代器,那么它也有 next()方法,而且 next()方法里同样有 done 和 value 这两个属性。我们来看看这两个属性的默认属性值是什么:

// 通过 * 符号,定义一个生成器函数
function* foo() {console.log('阶段1');yield;console.log('阶段2');yield;console.log('阶段3');return; // 执行 return 之后,函数不再继续往下走了,生成器的 next()方法的 done 属性值为 true。console.log('阶段4');
}// 执行生成器函数,返回一个生成器对象
const generator = foo();
// 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行;遇到 return关键字,或者遇到函数末尾时,结束执行。
console.log(generator.next());
console.log(generator.next());
console.log(generator.next());

打印结果:

阶段1
{value: undefined, done: false}阶段2
{value: undefined, done: false}阶段3
{value: undefined, done: true}

上方代码的打印结果可以看出,生成器函数在遇到函数的末尾,或者遇到 return 之后,函数就不再继续往下走了,next()方法的 done 属性值为 true。

还可以看到,next()方法的 value 属性值默认为 undefined,如果某些情况下我们希望 value属性有值的话,可以通过 yield 关键字进行传递。代码举例:

// 通过 * 符号,定义一个生成器函数
function* foo() {console.log('阶段1');yield 'a'; // 【关键代码】yield 后面写的内容,就是传递给 next() 方法的 value 属性值console.log('阶段2');yield 'b';console.log('阶段3');return; // 这里的 return,相当于 return undefinedconsole.log('阶段4');
}// 执行生成器函数,返回一个生成器对象
const generator = foo();
// 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行;遇到 return关键字,或者遇到函数末尾时,结束执行。
console.log(generator.next()); // 打印生成器对象的 next()方法
console.log(generator.next());
console.log(generator.next());

打印结果:

阶段1
{value: 'a', done: false}阶段2
{value: 'b', done: false}阶段3
{value: undefined, done: true}

next() 方法的参数

根据前面的代码实例得知,生成器函数是分多个阶段执行的。此时有一个诉求:如何给当前阶段传递参数呢?

答案是:可以通过当前阶段 next() 方法的参数,给当前阶段传值。这个参数值会成为上一个阶段 yield 语句的返回值。

代码举例:

// 通过 * 符号,定义一个生成器函数
function* foo() {console.log('阶段1');// 【关键代码】第二次调用 next()方法时,通过 res2 接收 next()方法的参数值const res2 = yield 'a';console.log('阶段2:', res2);// 第三次调用 next()方法时,通过 res3 接收 next()方法的参数值const res3 = yield 'b';console.log('阶段3:', res3);return;
}// 执行生成器函数,返回一个生成器对象
const generator = foo();
// 调用生成器的 next()方法,生成器才会执行,直到遇到 yield 后暂停执行;遇到 return关键字,或者遇到函数末尾时,结束执行。
console.log(generator.next()); // 执行第一阶段
console.log(generator.next('next2')); // 执行第二阶段,并传参
console.log(generator.next('next3')); // 指定第三阶段

打印结果:

阶段1
{value: 'a', done: false}阶段2: next2
{value: 'b', done: false}阶段3: ntext3
{value: undefined, done: true}

第一次学习时,这段代码可能比较难理解。在理解时需要注意的是,将 next2 这个属性值赋值给 res2,这个操作的执行时机是在第二阶段的最开始做的,不是在第一阶段的末尾做的。并且,这个属性值是通过第一阶段的 yield 返回值接收的。

如何中途结束生成器的执行

如果想在中途结束生成器的执行,有三种方式:

  • 方式1:return 语句。这个在前面已经讲过。

  • 方式2:通过生成器的 return() 函数。

  • 方式3:通过生成器的 throw() 函数抛出异常。

方式2的代码举例:

// 通过 * 符号,定义一个生成器函数
function* foo() {console.log('阶段1');const res2 = yield 'a';console.log('阶段2:', res2);const res3 = yield 'b';console.log('阶段3:', res3);return;
}// 执行生成器函数,返回一个生成器对象
const generator = foo();
console.log(generator.next());
// 【关键代码】通过生成器的 return()方法, 立即结束 foo 函数的执行
console.log(generator.return('next2'));
// 这行写了也没用,阶段2、阶段3都不会执行的
console.log(generator.next('next3'));

打印结果:

阶段1
{value: 'a', done: false}{value: 'next2', done: true}{value: undefined, done: true}

上方代码可以看出,阶段2、阶段3都不会执行;return()方法里的参数传给了 value 属性。

方式3的代码举例:

// 通过 * 符号,定义一个生成器函数
function* foo() {console.log('阶段1');const res2 = yield 'a';console.log('阶段2:', res2);const res3 = yield 'b';console.log('阶段3:', res3);return;
}// 执行生成器函数,返回一个生成器对象
const generator = foo();
console.log(generator.next());
// 【关键代码】通过生成器的 throw()方法抛出异常, 立即结束 foo 函数的执行
console.log(generator.throw(new Error('next2 error')));
// 这行写了也没用,阶段2、阶段3都不会执行的
console.log(generator.next('next3'));

打印结果:

阶段1
{value: 'a', done: false}Uncaught Error: next2 error

生成器的应用

用生成器代替迭代器

在前面的迭代器内容中,我们学习过“将普通对象封装为可迭代对象”。那段代码改用生成器的写法也可以实现。代码举例:

const myObj2 = {name: 'qianguyihao',skill: 'web',// 将普通对象 myObj2 封装为可迭代对象,目的是遍历 myObj2 的键值对。通过生成器 function* 的的方式实现[Symbol.iterator]: function* () {const entries = Object.entries(this); // 获取对象的键值对for (let index = 0; index < entries.length; index++) {// 【关键代码】通过 yield 控制迭代器分阶段执行;并将每个阶段的值存放到迭代器的 next() 方法的 value 属性中yield entries[index];}},
};// 写法1:通过 for ... of 遍历可迭代对象
for (const item of myObj2) {const [key, value] = item;console.log(key, value);
}console.log('---');// 写法2:通过 next() 方法遍历可迭代对象。与写法1等价。
const iterator = myObj2[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

打印结果:

name qianguyihao
demo.html:30 skill web
---
done: false, value:['name', 'qianguyihao']
done: false, value:['skill', 'web']
done: true, value:undefined

在指定数字范围内生成一个值

代码举例:

function* createValueGenerator(start, end) {// 前闭后开for (let i = start; i < end; i++) {yield i;}
}const valueGenerator = createValueGenerator(1, 3);
console.log(valueGenerator.next());
console.log(valueGenerator.next());
console.log(valueGenerator.next());

打印结果:

{value: 1, done: false}
{value: 2, done: false}
{value: undefined, done: true}

yield* 的使用

使用 yield* 迭代可迭代对象

语法格式:

yield* 某个可迭代对象

yield* 是 yield 的一种语法糖,也就是一种简写形式。它会依次迭代一个可迭对对象,每次迭代一个值,并且会产生一个新的可迭代对象

我们在前面讲过可迭代对象的应用场景,简单提到过 yield*,接下来讲讲它具体如何使用的。

先来看下面这段代码,用生成器的方式迭代数组:

function* createArrayGenerator(arr) {for (const item of arr) {yield item;}
}const myArr = ['a', 'b', 'c'];
const arrGenerator = createArrayGenerator(myArr);
console.log(arrGenerator.next());
console.log(arrGenerator.next());
console.log(arrGenerator.next());
console.log(arrGenerator.next());

打印结果:

{value: 'a', done: false}
{value: 'b', done: false}
{value: 'c', done: false}
{value: undefined, done: true}

上面这段代码,换成 yield* 的写法会非常简洁,如下:

function* createArrayGenerator(arr) {// 【关键代码】yield* 的后面必须是一个可迭代对象yield* arr;
}const myArr = ['a', 'b', 'c'];
const arrGenerator = createArrayGenerator(myArr);
console.log(arrGenerator.next());
console.log(arrGenerator.next());
console.log(arrGenerator.next());
console.log(arrGenerator.next());

打印结果不变。代码解释:yield* 的后面必须是一个可迭代对象,而且 createArrayGenerator()函数会返回一个可迭代对象 arrGenerator。

将自定义类封装为可迭代对象

我们在前面学习迭代器时,曾通过迭代器“将自定义类封装为可迭代对象”。那段代码,改用生成器 yield* 的方式也可以实现。代码举例:

// 定义类
class Person {constructor(name, arr) {this.name = name;this.arr = arr;}// 【关键代码】在定义生成器函数时,如果没有 function 这个单词的话,也可以直接在函数名的前面添加 * 符号。*[Symbol.iterator]() {// 【关键代码】一行代码,直接遍历 this.arr 这个可迭代对象,同时返回一个新的可迭代对象yield* this.arr;}
}const person1 = new Person('千古壹号', ['前端', '工程师']);
const person2 = new Person('许嵩', ['想象之中', '有何不可']);// Person的实例已经封装为可迭代对象了,可以通过 for ... of 进行遍历
for (const item of person1) {console.log(item);
}console.log('---');// 也可以通过 Symbol.iterator() 方法进行遍历
const personIterator = person2[Symbol.iterator]();
console.log(personIterator.next());
console.log(personIterator.next());
console.log(personIterator.next());

打印结果:

前端
工程师
---
{value: '想象之中', done: false}
{value: '有何不可', done: false}
{value: undefined, done: true}

赞赏作者

创作不易,你的赞赏和认可,是我更新的最大动力:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/12481.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【class8】人工智能初步(图像识别-----卷积神经网络)

上节回顾 上节课&#xff0c;我们简单了解了图像识别和深度学习的相关知识。 快速回顾一下吧&#xff5e; A图像识别是以图像的主要特征为基础的。B. 图像分辨率决定图像的质量。 C&#xff0e; 像素是图像中的最小单位D. 在图像识别的原理上&#xff0c;计算机和人类在本质…

面试加分项:精通Java高并发下的锁优化策略

1. 问题背景与挑战 1.1 并发编程中的挑战 在现代软件开发中&#xff0c;高并发已成为衡量系统质量的一项关键因素&#xff0c;特别是对于那些需要同时处理数以万计甚至百万级用户请求的服务。并发编程的挑战在于如何有效地同步多个线程&#xff0c;确保数据的一致性和系统的稳…

3ds Max与Maya不同之处?两者哪个更适合云渲染?

3ds Max 和 Maya 都是知名的3D软件&#xff0c;各有其特色。3ds Max 以直观的建模和丰富的插件生态闻名&#xff1b;Maya 则在动画和角色创作方面更为出色。两者都支持云渲染技术&#xff0c;能帮助用户在云端高效完成项目。 一、3ds Max和Maya之间的主要区别&#xff1a; 3ds…

短视频的拍摄方式有哪些:四川京之华锦信息技术公司

创意与技术并存的艺术之旅 在数字媒体高速发展的今天&#xff0c;短视频已经成为人们获取信息、表达情感、展示才艺的重要窗口。从社交平台到新闻资讯&#xff0c;再到教育娱乐&#xff0c;短视频无处不在&#xff0c;其独特的魅力和广泛的传播力让人们对它的拍摄方式产生了浓…

全像宇宙投影第三部时间与空间(全文)下载

当这个人向空中凝视时&#xff0c;他所在的房间渐渐变得透明而朦胧&#xff0c;空中渐渐浮现一个久远前景像。突然他觉得自己在皇宫中庭内&#xff0c;他面前站著一位年轻女士非常美丽&#xff0c;有著橄榄色的皮肤。他可以见到她的颈项、手腕、脚踝上都挂著金饰&#xff0c;还…

df 中的 NoneType、空和 None

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 目录 简介什么是 NoneType&#xff1f;什么是空&#xff08;Empty&#xff09;&#xff1f;什么是 None&#xff1f;Python 中如何判断 NoneType&#xff1f;Pandas DataFrame 中的 NoneType、空和 None实操&#x…

HIVE大数据平台SQL优化分享

相信很多小伙伴在面试的时候&#xff0c;必然跳不过去的一个问题就是SQL脚本的优化&#xff0c;这是很多面试官爱问的问题&#xff0c;也是可以证明你实力进阶的一个重要的能力。 下面给大家分享一个重量级的大数据行业sql技能---hive大数据平台SQL优化。 此文章是大数据平台…

TimesFM: 预训练的时间序列基础模型

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在阅读过程中有些知识点存在盲区&#xff0c;可以回到如何优雅的谈论大模型重新阅读。另外斯坦福2024人工智能报告解读为通识性读物。若对于如果…

Ajax额

原生Ajax xml 已被json取代 http 请求方法urlhttp版本号 network 谷歌浏览器查看请求报文和响应报文 F12 network header里面有 请求头 响应头 点击view source 可以查看请求响应行 请求体在请求行头下面 get请求有url参数&#xff0c;请求体变为query String…

网络传输,请每次都开启 TCP_NODELAY

原文&#xff1a;Marc Brooker - 2024.05.09 &#xff08;注&#xff1a;不必过于担心这个问题&#xff0c;大部分现代库&#xff0c;语言&#xff08;如 Go&#xff09;&#xff0c;代理&#xff08;如 Envoy&#xff09;&#xff0c;都默认设置了 TCP_NODELAY。如果遇到网络…

AIGC数字人视频创作平台,赋能企业常态化制作数字内容营销

随着数字人技术不断发展&#xff0c;AIGC、元宇宙等相关产业迅速发展&#xff0c;企业通过3D虚拟数字人定制&#xff0c;打造出专属的数字人作为企业与用户沟通的新桥梁。 作为3D、AI数字人技术服务商及方案提供商&#xff0c;广州虚拟动力一直致力于为各领域企业通过3D虚拟数字…

Franz Electron + React 源码启动运行填坑指南

环境要求 安装miniconda python 环境electron/rebuild用得着&#xff0c;miniconda 默认自带的 python 是 3.11 版本&#xff0c;比较新&#xff1b; 安装virsual studio 2019 要把C桌面相关的都安装了&#xff0c;大概需要20G&#xff0c;不要安装到 C 盘&#xff0c;都安装到…

【C++】认识C++(上)

目录 从C到C命名空间同名冲突命名空间的定义命名空间的使用 C的输入和输出缺省参数&#xff08;默认参数&#xff09; 从C到C C语言的出现是计算机科学和工程史上的一个重要里程碑&#xff0c;许多现代计算机语言都受C语言的影响。C语言是面向过程的&#xff0c;结构化和模块化…

力扣HOT100 - 279. 完全平方数

解题思路&#xff1a; 动态规划 class Solution {public int numSquares(int n) {int[] dp new int[n 1];// 初始化dp数组&#xff0c;默认最坏情况是每个数都是由1相加得到的for (int i 1; i < n; i) {dp[i] i;}for (int i 1; i < n; i) {for (int j 1; j * j &…

Python Twisted库:异步网络编程的利器

更多Python学习内容&#xff1a;ipengtao.com 在现代网络应用开发中&#xff0c;异步编程已经成为一种必备的技能。Python Twisted库是一款强大的异步网络编程框架&#xff0c;它提供了丰富的工具和功能&#xff0c;使得开发者可以轻松地构建高性能的网络应用。 基本概念 Twist…

在虚机VirtualBox7.0.8安装Androidx86_64系统详细步骤要点

最近需要用到安卓系统蓝牙功能做测试&#xff0c;就选择了Virtualboxandroidx86方案&#xff0c;先把系统安装好&#xff0c;后面看是否可以比较好的完成蓝牙功能测试。如果可以的话&#xff0c;我会再发文分享下的&#xff0c;敬请期待。 1.准备材料 &#xff08;1&#xff…

Python API和微服务的测试库之httpretty使用详解

概要 在现代软件开发中,API和微服务的测试是确保应用稳定性和功能正确性的关键环节。Python的HTTPretty库提供了一个强大的工具,允许开发者在不实际发起网络请求的情况下模拟HTTP请求和响应。本文将全面介绍HTTPretty的安装、特性、基本与高级功能,并结合实际应用场景,展示…

企业如何利用美国多IP服务器来提升网站的安全性?

企业如何利用美国多IP服务器来提升网站的安全性? 在当前网络环境下&#xff0c;网站安全性日益成为企业面临的重要挑战。为了有效应对各种潜在威胁&#xff0c;越来越多的企业选择利用美国多IP服务器来提升其网站的安全性。这种服务器配置能够通过一系列策略来增加网站的安全…

5G NR 吞吐量计算 and 4G LTE 吞吐量计算

5G NR Throughput References • 3GPP TS 38.306 V15.2.0 (2018-06) ➤J : number of aggregated component carriers in a band or band combination ➤Rmax : 948/1024 • For the j-th CC, Vlayers(j) is the maximum number of layers ➤Qm(j) : Maximum modulation orde…

视频推拉流/视频直播点播平台EasyDSS使用Mysql数据库接口报错502如何处理?

视频推拉流/视频直播点播EasyDSS互联网直播平台支持一站式的上传、转码、直播、回放、嵌入、分享功能&#xff0c;具有多屏播放、自由组合、接口丰富等特点。平台可以为用户提供专业、稳定的直播推流、转码、分发和播放服务&#xff0c;全面满足超低延迟、超高画质、超大并发访…