Skip to content

Commit

Permalink
add: yield* 的使用
Browse files Browse the repository at this point in the history
  • Loading branch information
qianguyihao committed May 30, 2023
1 parent 7fb0fdb commit 756a4ca
Showing 1 changed file with 276 additions and 1 deletion.
277 changes: 276 additions & 1 deletion 07-JavaScript进阶/03-迭代器和生成器.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ class Person {
const person1 = new Person('千古壹号', ['前端', '工程师']);
const person2 = new Person('许嵩', ['想象之中', '有何不可']);

// Person的实例已经封装为可迭代兑现了,可以通过 for ... of 进行遍历
// Person的实例已经封装为可迭代对象了,可以通过 for ... of 进行遍历
for (const item of person2) {
console.log(item);
}
Expand Down Expand Up @@ -711,6 +711,281 @@ console.log(generator.next('next3')); // 指定第三阶段

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

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

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

- 方式1:return 语句。这个在前面已经讲过。
- 方式2:通过生成器的 return() 函数。
- 方式3:通过生成器的 throw() 函数抛出异常。

方式2的代码举例:

```js
// 通过 * 符号,定义一个生成器函数
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的代码举例:

```js
// 通过 * 符号,定义一个生成器函数
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
```

## 生成器的应用

### 用生成器代替迭代器

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

```js
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
```

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

代码举例:

```js
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* 的介绍

语法格式:

```js
yield* 某个可迭代对象
```

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

我们在前面讲过可迭代对象的应用场景,其中就包括 `yield*`

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

```js
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*` 的写法会非常简洁,如下:

```js
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*` 的方式也可以实现。代码举例:

```js
// 定义类
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}
```






















Expand Down

0 comments on commit 756a4ca

Please sign in to comment.