五分鐘學會generator函式

從前有匹馬叫程式碼發表於2021-02-24

什麼是generator函式?

常規函式只會返回一個單一值(或者不返回任何值)。 而 Generator 可以按需一個接一個地返回(“yield”)多個值。它們可與 iterable 完美配合使用,從 而可以輕鬆地建立資料流。

如何建立一個generator函式,程式碼如下:

function* generator() {
    yield 1;
    yield '宋遠溪';
    yield {name:'宋遠溪'}
}

generator函式如何使用?繼續往下:

function* generator() {
    yield 1;
    yield '宋遠溪';
    yield {name:'宋遠溪'}
}

const genFn = generator();

console.log(
    genFn.next(),
    genFn.next(),
    genFn.next()
)
//{ value: 1, done: false } { value: '宋遠溪', done: false } { value: { name: '宋遠溪' }, done: false }

由程式碼可以觀察到,與普通函式不同的是generator函式呼叫會返回一個生成器物件,通過呼叫生成器物件的next(),可以返回generator函式體中的yield的值;

並且,通過呼叫next()我們可以觀察到,生成器物件時可迭代的!

yield 與 next 如何互動?繼續往下:

function* generator() {
    var name = yield "你叫什麼名字?";
    yield `你好${name}`;

}

var iterable = generator();

console.log(
    iterable.next().value,
    iterable.next('李煥英').value
)

//你叫什麼名字?你好李煥英
  1. 第一個 .next() 啟動了 generator 的執行……執行到達第一個 yield 。
  2. 結果被返回到外部程式碼中。
  3. 第二個 .next(4) 將 4 作為第一個 yield 的結果傳遞迴 generator 並恢復 generator 的執 行。
  4. ...以此類推

巢狀的generator,如何交換控制權,來看《JavaScript忍者祕籍第二版》的很經典的一個例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Generator函式</title>
</head>
<body>
    <div id="box">
        <form action="">
            <label for="">
                <input type="text">
            </label>
            <button>提交</button>
        </form>
        <p>Generator</p>
    </div>


    <script>
        var box = box || document.getElementById('box');

        function* traverseTree(element) {
            yield element;
            element = element.firstElementChild;
            while (element) {
                yield* traverseTree(element);
                element = element.nextElementSibling;
            }
        }

        const subTree = traverseTree(box);

        for (const element of subTree) {
            console.log(element.nodeName);
            /**
             * DIV
             * FORM
             * LABEL
             * INPUT
             * BUTTON
             * P
            */
        }
    </script>
</body>
</html>

我們可以通過yield* 將當前generator函式的控制權轉移到下一個generator函式中,讓我們再來看一下不用生成器函式的遍歷方法,程式碼如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Generator函式</title>
  </head>
  <body>
    <div id="box">
      <form action="">
        <label for="">
          <input type="text" />
        </label>
        <button>提交</button>
      </form>
      <p>Generator</p>
    </div>

    <script>
      var box = box || document.getElementById("box");

      function domTraverse(element, callback) {
        callback(element);
        element = element.firstElementChild;
        while (element) {
          domTraverse(element, callback);
          element = element.nextElementSibling;
        }
      }

      domTraverse(box, (element) => {
        console.log(element.nodeName);
        /**
         * DIV
         * FORM
         * LABEL
         * INPUT
         * BUTTON
         * P
         */
      });
    </script>
  </body>
</html>

通過這兩段程式碼可以看出,我們通過生成器函式將生產值(HTML節點) 與消費值(for 迴圈列印 訪問過的節點名稱)的程式碼分隔開,使得程式碼解耦;

 

我們還可以通過generator丟擲錯誤,程式碼如下:

function* generator() {
    try {
        var result = yield '你喜歡JavaScript嗎';
        if (result == '喜歡')
            yield '太棒了!'
        else
            yield '真遺憾' 
    }catch(e) {
        console.log('出錯了!');
        console.log(e);
    }
}

const gen = generator();

console.log(
    gen.next(),
)
gen.throw(new Error('故意丟擲錯誤!'));

//出錯了,Error:......

 

相關文章