>웹 프론트엔드 >JS 튜토리얼 >ES6의 Generator 함수 사용법 소개

ES6의 Generator 함수 사용법 소개

不言
不言앞으로
2019-03-30 09:54:262213검색

이 글은 ES6의 제너레이터 기능 사용법을 소개합니다. 도움이 필요한 친구들이 참고할 수 있기를 바랍니다.

1. Generator 함수란 무엇인가요? Generator 함수는 ES6 표준에서 제안된 비동기 프로그래밍 솔루션입니다. 이러한 종류의 함수와 일반 함수의 가장 큰 차이점은 실행을 일시 중지하고 일시 중지된 위치에서 실행을 다시 시작할 수 있다는 것입니다.

구문적으로 말하면 Generator 함수는 많은 내부 상태를 캡슐화하는 상태 머신입니다.

기본적으로 생성기 기능은 횡단 객체 생성기입니다. (traverser 객체에 대해서는 Ruan Yifeng 선생님의 이 글을 참조하세요.) Generator 함수는 traverser 객체를 반환하며, 이 객체를 탐색하면 함수 내부의 각 상태를 차례로 얻을 수 있습니다.

2. 기본 구문

1. 생성기 함수 정의

생성기 함수 정의와 일반 함수 정의의 차이점은 다음과 같습니다.

함수 키워드와 함수 이름 사이에 *(별표)가 있습니다.

Yield는 함수 내부에서 각 함수의 내부 상태를 정의하는 데 사용됩니다.

함수 내부에 return 문이 있으면 함수 내부의 마지막 상태입니다.


간단한 예를 살펴보겠습니다.

// 定义
function* sayHello() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
// 调用
// 注意,hw获取到的值是一个遍历器对象
let g = sayHello();

위 예에서는 두 개의 yield 표현식과 return이 있는 sayHello라는 생성기 함수를 정의합니다. 코드> 표현식. 따라서 이 함수 내부에는 hello, worldreturn 문(실행 종료)의 세 가지 상태가 있습니다. 마지막으로 이 함수를 호출하여 traverser 객체를 가져와 g 변수에 할당합니다.

제너레이터 함수의 호출 방식은 일반 함수인 함수명()과 똑같습니다. 차이점은 다음과 같습니다.

  • 함수 호출 후 내부 코드(첫 번째 줄부터 시작)가 즉시 실행되지 않습니다. sayHello的Generator函数,它内部有两个yield表达式和一个return表达式。所以,该函数内部有三个状态:helloworldreturn语句(结束执行)。最后,调用这个函数,得到一个遍历器对象并赋值给变量g

    Generator函数的调用方法与普通函数完全一样,函数名()。不同的是:

    • 函数调用后,内部代码(从第一行开始)都不会立即执行。
    • 函数调用后会有一个返回值,这个值是一个指向内部状态的指针对象,实质就是一个包含函数内部状态的遍历器对象。

    Generator函数调用后不会立即执行,那么,我们如何让它开始执行内部的代码呢?又如何获取它内部的每一个状态呢?此时,我们必须调用返回的生成器对象的.next()方法,才能开始代码的执行,并且使得指针移向下一个状态。

    以上面的例子为例:

    g.next();
    // { value: 'hello', done: false }
    g.next();
    // { value: 'world', done: false }
    g.next();
    // { value: 'ending', done: true }
    g.next();
    // { value: undefined, done: true }

    上面的代码中,一共调用了四次g这个遍历器对象的.next()方法。第一次调用,sayHello这个Generator函数开始执行,直到遇到第一个yield表达式就会暂停执行。.next()方法会返回一个对象,它的value属性就是当前yield表达式的值hellodone属性的值false,表示遍历还没有结束。

    第二次再调用.next(),就会执行到第二个yield表达式处,并暂停执行,返回对应的对象。

    第三次调用.next(),函数执行到最后的return语句,此时标志着遍历器对象g遍历结束,所以返回的对象中value属性值就是return后面所跟的值endingdone属性值为true,表示遍历已经结束。

    第四次以及后面在调用.next()方法,返回的都会是{value: undefined, done: true }

    2、yield表达式

    由Generator函数返回的遍历器对象,只有调用.next()方法才会遍历到下一个内部状态,所以这其实是提供了一种可以暂停执行的函数,yield表达式就是暂停标志。

    遍历器对象的.next()方法的运行逻辑如下。

    1. 遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
    2. 下一次调用.next()方法时,再继续往下执行,直到遇到下一个yield表达式。
    3. 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
    4. 如果该函数没有return语句,则返回的对象的value属性值为undefined
    5. 함수가 호출된 후에는 반환 값이 있습니다. 이 값은 내부 상태를 가리키는 포인터 객체입니다. 본질적으로 함수의 내부 상태를 포함하는 트래버스 객체입니다.
제너레이터 함수는 호출 직후에 실행되지 않는데, 내부 코드 실행을 시작하게 하려면 어떻게 해야 할까요? 그 안에 모든 상태를 넣는 방법은 무엇입니까? 이 시점에서 반환된 생성기 개체의 .next() 메서드를 호출하여 코드 실행을 시작하고 포인터를 다음 상태로 이동해야 합니다.

위의 예를 예로 들어보겠습니다.

  // 出现在普通函数中,报错
  (function () {
    yield 'hello';
  })()

  // forEach不是Generator函数,报错
  [1, 2, 3, 4, 5].forEach(val => {
    yield val
  });
위 코드에서 g traverser 객체의 .next() 메서드는 총 4번 호출됩니다. . 처음 호출되면 sayHello 생성기 함수가 실행을 시작하고 첫 번째 yield 표현식을 만날 때까지 실행이 일시 중지됩니다. .next() 메서드는 value 속성이 현재 yield 표현식 hello의 값인 객체를 반환합니다. , done 속성 값은 순회가 종료되지 않았음을 나타내는 false입니다. 🎜🎜두 번째로 .next()를 호출하면 실행이 두 번째 yield 표현식에 도달하고 실행이 일시 중지되며 해당 객체가 반환됩니다. 🎜🎜.next()가 세 번째 호출되면 함수는 traverser 객체 의 순회 끝을 표시하는 마지막 <code>return 문까지 실행됩니다. g 이므로 반환된 객체의 value 속성 값은 ending 값과 return이 뒤따르는 값이고, done 속성은 true이며 순회가 종료되었음을 나타냅니다. 🎜🎜4번째 이후의 .next() 메서드 호출은 {value: undefine, done: true}를 반환합니다. 🎜

2. 항복 표현식

🎜Generator 함수에서 반환된 traverser 객체는 .next() 메서드를 호출해야만 다음 내부 상태로 이동하므로 실제로는 다음을 제공합니다. 실행을 일시 중지할 수 있는 함수 yield 표현식은 일시 중지 플래그입니다. 🎜🎜traverser 객체의 .next() 메서드의 연산 로직은 다음과 같습니다. 🎜
  1. yield 표현식이 발견되면 후속 작업의 실행이 일시 중지되고 yield 바로 다음 표현식의 값이 반환됩니다. 개체의 value 속성입니다. 🎜
  2. 다음번에 .next() 메서드가 호출되면 다음 yield 표현식이 나타날 때까지 실행이 계속됩니다. 🎜
  3. 새로운 yield 표현식이 발견되지 않으면 return 문이 나올 때까지 함수가 끝날 때까지 실행되며 return 명령문 뒤의 표현식 값은 반환된 객체의 value 속성 값으로 사용됩니다. 🎜
  4. 함수에 return 문이 없는 경우 반환된 개체의 value 속성 값은 정의되지 않음입니다. 🎜🎜🎜🎜주요: 🎜🎜
    1. yield关键字只能出现在Generator函数中,出现在别的函数中会报错。
      // 出现在普通函数中,报错
      (function () {
        yield 'hello';
      })()
    
      // forEach不是Generator函数,报错
      [1, 2, 3, 4, 5].forEach(val => {
        yield val
      });
    1. yield关键字后面跟的表达式,是惰性求值的。 只有当调用.next()方法、内部状态暂停到当前yield时,才会计算其后面跟的表达式的值。这等于为JavaScript提供了手动的“惰性求值”的语法功能。
    function* step() {
      yield 'step1';
    
      // 下面的yield后面的表达式不会立即求值,
      // 只有暂停到这一行时,才会计算表达式的值。
      yield 'step' + 2;
    
      yield 'setp3';
      return 'end';
    }
    1. yield表达式本身是没有返回值的,或者说它的返回值为undefined。使用.next()传参可以为其设置返回值。(后面会讲到)
    function* gen() {
      for (let i = 0; i < 5; i++) {
        let res = yield;  // yield表达式本身没有返回值
        console.log(res); // undefined
      }
    }
    let g = gen();
    g.next();   // {value: 0, done: false}
    g.next();   // {value: 1, done: false}
    g.next();   // {value: 2, done: false}

    yield与return的异同:

    相同点:

    • 两者都能返回跟在其后面的表达式的值。

    不同点:

    • yield表达式只是暂停函数向后执行,return是直接结束函数执行。
    • yield表达式可以出现多次,后面还可以有代码。return只能出现一次,后面的代码不会执行,在一些情况下还会报错。
    • 正常函数只能返回一个值,因为只能执行一次return。Generator函数可以返回一系列的值,因为可以有任意多个yield。

    3、.next()方法传参

    前面我们说到过,yield表达式自身没有返回值,或者说返回值永远是undefined。但是,我们可以通过给.next()方法传入一个参数,来设置上一个(是上一个)yield表达式返回值。

    来看一个例子:

    function* conoleNum() {
      console.log('Started');
      console.log(`data: ${yield}`);
      console.log(`data: ${yield}`);
      return 'Ending';
    }
    let g = conoleNum();
    
    g.next();      // 控制台输出:'Started'
    
    g.next('a');   // 控制台输出:'data: a'
    // 不传入参数'a',就会输出'data: undefined'
    
    g.next('b');   // 控制台输出:'data: b'
    // 不传入参数'a',就会输出'data: undefined'

    上面的例子,需要强调一个不易理解的地方。

    第一次调用.next(),此时函数暂停在代码第三行的yield表达式处。记得吗?yield会暂停函数执行,此时打印它的console.log(),也就是代码第三行的console,由于暂停并没有被执行,所以不会打印出结果,只输出了代码第二行的'Started'。

    当第二次调用.next()方法时,传入参数'a',函数暂停在代码第四行的yield语句处。此时参数'a'会被当做上一个yield表达式的返回值,也就是代码第三行的yiled表达式的返回值,所以此时控制台输出'data: a'。而代码第四行的console.log()由于暂停,没有被输出。

    第三次调用,同理。所以输出'data: b'

    4、Generator.prototype.throw()

    Generator函数返回的遍历器对象,都有一个.throw()方法,可以在函数体外抛出错误,然后在Generator函数体内捕获

    function* gen() {
      try {
        yield;
      } catch (e) {
        console.log('内部捕获', e);
      }
    };
    
    var g = gen();
    // 下面执行一次.next()
    // 是为了让gen函数体执行进入try语句中的yield处
    // 这样抛出错误,gen函数内部的catch语句才能捕获错误
    g.next();
    
    try {
      g.throw('a');
      g.throw('b');
    } catch (e) {
      console.log('外部捕获', e);
    }

    上面例子中,遍历器对象ggen函数体外连续抛出两个错误。第一个错误被gen函数体内的catch语句捕获。g第二次抛出错误,由于gen函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就会被抛出gen函数体,被函数体外的catch语句捕获。

    值得注意的是:

    • 如果Generator函数内部没有部署try...catch代码块,那么遍历器对象的throw方法抛出的错误,将被外部try...catch代码块捕获。
    • 如果Generator函数内部和外部都没有部署try...catch代码块,那么程序将报错,直接中断执行。

    遍历器对象的throw方法被捕获以后,会附带执行一次.next()方法,代码执行会暂停到下一条yield表达式处。看下面这个例子:

    function* gen(){
      try {
        yield console.log('a');
      } catch (e) {
        console.log(e);   // 'Error'
      }
      yield console.log('b');
      yield console.log('c');
    }
    var g = gen();
    
    g.next();   // 控制台输出:'a'
    
    g.throw('Error');  // 控制台输出:'b'
    // throw的错误被内部catch语句捕获,
    // 会自动在执行一次g.next()
    
    g.next();   // 控制台输出:'c'

    5、Generator.prototype.return()

    Generator函数返回的遍历器对象,还有一个.return()方法,可以返回给定的值,并且直接结束对遍历器对象的遍历。

    function* gen() {
      yield 1;
      yield 2;
      yield 3;
    }
    var g = gen();
    
    g.next();        // { value: 1, done: false }
    
    // 提前结束对g的遍历。尽管yield还没有执行完
    // 此时done属性值为true,说明遍历结束
    g.return('foo'); // { value: "foo", done: true }
    
    g.next();        // { value: undefined, done: true }

    如果.return()方法调用时,不提供参数,则返回值的value属性为undefined

    6、yield* 表达式

    yield* 用来在一个Generator函数里面执行另一个Generator函数。

    如果在一个Generator函数内部,直接调用另一个Generator函数,默认情况下是没有效果的。

    function* gen1() {
      yield 'a';
      yield 'b';
    }
    function* gen2() {
      yield 'x';
      // 直接调用gen1()
      gen1();
      yield 'y';
    }
    // 遍历器对象可以使用for...of遍历所有状态
    for (let v of gen2()){
      只输出了gen1的状态
      console.log(v);   // 'x' 'y'
    }

    上面的例子中,gen1gen2都是Generator函数,在gen2里面直接调用gen1,是不会有效果的。

    这个就需要用到 yield* 表达式。

    function* gen1() {
      yield 'a';
      yield 'b';
    }
    function* gen2() {
      yield 'x';
      // 用 yield* 调用gen1()
      yield* gen1();
      yield 'y';
    }
    
    for (let v of gen2()){
      输出了gen1、gen2的状态
      console.log(v);   // 'x' 'a' 'b' 'y'
    }

    小节

    이 글에서는 주로 Generator 함수의 기본 구문과 일부 세부 사항, Generator 함수 정의, Yield 표현식, .next() 메소드 및 매개변수 전달, .throw() 메소드, .return() 메소드 및 Yield* 표현식에 대해 설명합니다. .

    글 시작 부분에서 언급했듯이 Generator 함수는 ES6에서 제안한 비동기 프로그래밍에 대한 솔루션입니다. 실제 애플리케이션에서는 일반적으로 항복 키워드 뒤에 비동기 작업이 옵니다. 비동기 작업이 성공적으로 반환되면 .next() 메서드가 호출되어 비동기 프로세스를 다음 항복 표현식으로 넘겨줍니다.

    이 기사는 여기서 끝났습니다. 더 흥미로운 콘텐츠를 보려면 PHP 중국어 웹사이트의 JavaScript Video Tutorial 칼럼을 주목하세요!

위 내용은 ES6의 Generator 함수 사용법 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제