Posts Generator
Post
Cancel

Generator

1. Interator(遍历器)

四种数据集合 Array Object Set Map

Interator 遍历器: 依次处理该数据的所有成员(指针对象) 一是为各种数据结构,提供一个统一的、简便的访问接口; 二是使得数据结构的成员能够按某种次序排列; 三是 ES6 创造了一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费。

1
2
3
4
5
6
7
8
9
10
11
Iterator 的遍历过程是这样的。

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。

每一次调用next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。

具有Symbol.iterator属性的对象都是可遍历的,调用这个属性,就会返回一个遍历器对象

1
2
3
4
5
6
7
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator](); // 调用 arr 的遍历器生成函数, 返回遍历器对象, 指向数组起始位置

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

原生具备 Iterator 接口的数据结构如下:Array/Map/SetString/TypedArray/函数的 arguments 对象/NodeList 对象,

一个对象如果要具备可被for…of循环调用的 Iterator 接口,就必须在Symbol.iterator的属性上部署遍历器生成方法(原型链上的对象具有该方法也可)

1
2
3
4
5
6
7
8
9
10
11
12
const arr = ['red', 'green', 'blue'];

for(let v of arr) {
  console.log(v); // red green blue
}

const arr = ['red', 'green', 'blue'];

arr.forEach(function (element, index) {
  console.log(element); // red green blue
  console.log(index);   // 0 1 2
});

2. Generator定义

2-1. function关键字与函数名之间有一个星号;
2-2. 函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

3. yield表达式和next()。

(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function* foo(x) {
  console.log("start");
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5); `生成一个遍历器,函数不会执行`
a.next() // "start" Object{value:6, done:false}  `输出start, 执行到第一个yield, value = 第一个yield 后面表达式的值 = x+1 = 5`
a.next() // Object{value:NaN, done:false} `next()没有传值,上一个yield (x+1)的值为NaN, 所以y值为NaN, y/3为NaN`
a.next() // Object{value:NaN, done:true} `next()没有传值,上一个yield (y/3)的值为NaN, z为NaN, 5+NaN+NaN`

var b = foo(5);
b.next() // { value:6, done:false } `第一个next传值是无效的, next是给上一个yield表达式传值`
b.next(12) // { value:8, done:false } => `yield(x + 1) = 12; y = 2*12 = 24; yield value = > y/3 = 8;`
b.next(13) // { value:42, done:true } =>`yield(y / 3) = 13; z = 13; yield value => x+y+z = 5+24+13 = 42`

####  Object{value:6, done:false} value => yield后表达式的值,无则返回undefined; done => generator执行完返回done

4. yield*

用来在一个 Generator 函数里面执行另一个 Generator 函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function* foo() {
  yield 'a';
  yield 'b';
}

function* bar() {
  yield 'x';
  // 手动遍历 foo()
  for (let i of foo()) {
    console.log(i);
  }
  // 等于 yield* foo()
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}

5.throw()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var g = function* () {
  try {
    yield;
  } catch (e) {
    console.log('内部捕获', e);
  }
};

var i = g(); // 返回一个遍历器对象
i.next(); // 执行到 yield暂停 返回 {value: NaN, done: false}

try {
  i.throw('a'); // 把yield表达式替换成throw ("a"); generator函数catch到error, done: true
  i.throw('b'); // done: true 就不再执行generator函数
} catch (e) {
  console.log('外部捕获', e);
}
5-1.throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。
因为第一次执行next方法,等同于启动执行 Generator 函数的内部代码,否则 Generator 函数还没有开始执行,这时throw方法抛错只可能抛出在函数外部
1
2
3
4
5
6
7
8
9
10
11
12
function* gen() {
  try {
    yield 1;
  } catch (e) {
    console.log('内部捕获');
  }
}

var g = gen();
g.throw(1);
// Uncaught 1
// g.throw(1)执行时,next方法一次都没有执行过。这时,抛出的错误不会被内部捕获,而是直接在外部抛出,导致程序出错
5-2.throw方法被捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var gen = function* gen(){
  try {
    yield console.log('a');
  } catch (e) {
    // ...
  }
  yield console.log('b');
  yield console.log('c');
}

var g = gen();
g.next() // a
g.throw() // b
g.next() // c
`上面代码中,g.throw方法被捕获以后,自动执行了一次next方法,所以会打印b。
另外,也可以看到,只要 Generator 函数内部部署了try...catch代码块,
那么遍历器的throw方法抛出的错误,不影响下一次遍历`

6. return()

6-1. 调用return()后, 返回的对象{value: return的值/undefined, done: true};
1
2
3
4
5
6
7
8
9
10
function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next() // { value: 1, done: false }
g.return() // { value: undefined, done: true }
6-2. 执行try代码块,那么return()方法会导致立刻进入finally代码块,执行完以后,整个函数才会结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }
7. next()/throw()/return()的共同之处
7-1. next()是将yield表达式替换成一个值。
1
2
3
4
5
6
7
8
9
10
11
const g = function* (x, y) {
  let result = yield x + y;
  return result;
};

const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}

gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
7-2. throw()是将yield表达式替换成一个throw语句。
1
2
3
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
`// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error(`'出错了'));
7-3. return()是将yield表达式替换成一个return语句。
1
2
3
gen.return(2); // Object {value: 2, done: true}
`// 相当于将 let result = yield x + y
// 替换成 let result = return 2;`

8. Iterator 和 Generator关系

任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。 由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

对象的Symbol.iterator属性 -》 实现了Iterator 接口 即 遍历器生成函数 -》 返回遍历器对象

1.

1
2
3
4
5
6
7
8
9
10
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]
`上面代码中,Generator 函数赋值给Symbol.iterator属性,
从而使得myIterable对象具有了 Iterator 接口,可以被...运算符遍历了`

2.

1
2
3
4
5
6
7
8
9
function* gen(){
  // some code
}

var g = gen();

g[Symbol.iterator]() === g
`gen是一个 Generator 函数,调用它会生成一个遍历器对象g。
它的Symbol.iterator属性,也是一个遍历器对象生成函数,执行后返回它自己。`
This post is licensed under CC BY 4.0 by the author.