<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          JavaScript 生成器的簡介

          共 6607字,需瀏覽 14分鐘

           ·

          2021-11-19 20:54

          來源  | https://mp.weixin.qq.com/s/IdvvFfvgrwAt7DTJKnxGYA

          一、單線程的 js

          var a = 3;var b = 2;
          function foo() { a = a * 2; bar(); b++; console.log(a, b); // 7, 5}
          function bar() { a++; b = b * 2;}
          上面代碼中 bar 函數(shù)很直觀地在 b++ 與 a * 2 之間運行,現(xiàn)在假設(shè)一下,bar 現(xiàn)在不存在于 foo 函數(shù)里,那么怎么才能使單線程的 js 中斷 foo 函數(shù)去執(zhí)行bar ,然后再回來繼續(xù)執(zhí)行自己沒有執(zhí)行的代碼呢。

          二、yield 暫停

          對于上面的問題,只要我們能夠通知 foo 在某個地方暫停下來就可以實現(xiàn)這樣的需求。
          var a = 3;var b = 2;
          function* foo() { // 聲明生成器 a = a * 2; yield; b++; console.log(a, b);}
          function bar() { a++; b = b * 2;}
          有些地方使用的是function *foo(){...} 的形式

          那么現(xiàn)在要如何運行代碼才能達到想要的效果呢

          /* 構(gòu)造一個迭代器it來控制生成器foo,此時foo完全沒有被執(zhí)行 */var it = foo();
          /* foo開始執(zhí)行,一直到遇到y(tǒng)ield才停止,此時a = 6, b = 2 */it.next();console.log(a, b); // 6, 2
          /* foo讓出線程,執(zhí)行bar,此時a = 7, b = 4 */bar();console.log(a, b); // 7, 4
          /* foo繼續(xù)執(zhí)行,此時a = 7, b = 5 */it.next(); // 7, 5

          生成器是一類特殊的函數(shù),可以一次或多次啟動與停止,而且不一定非要完成

          三、輸入與輸出

          生成器特殊歸特殊,它始終都是一個函數(shù),依然具有一些函數(shù)的基本特性,例如接受參數(shù)(輸入),返回值(輸出)

          function* foo(x, y) {  return x * y;}
          var it = foo(6, 7);var res = it.next();console.log(res);// {value: 42, done: true}

          next 的調(diào)用結(jié)果是一個對象,擁有一個 value 屬性,持有從 foo 返回的值,也就是說,yield 會導(dǎo)致生成器在執(zhí)行過程中發(fā)送出一個值,有點類似上面的return

          3.1 迭代消息傳遞

          除了能接收參數(shù)并提供返回值,生成器還提供了更強大的內(nèi)建消息輸入輸出能力,通過 yield 和 next 實現(xiàn)

          function* foo(x) {  var y = x * (yield);  return y;}
          var it = foo(6);
          it.next();
          var res = it.next(7);console.log(res);// { value: 42, done: true }

          首先 6 作為參數(shù) x,調(diào)用it.next() ,啟動foo

          在foo 內(nèi)部,執(zhí)行語句var y = x... 時遇到y(tǒng)ield 表達式,在此處(賦值語句中間)暫停foo ,并要求調(diào)用代碼為yield 的表達式提供一個結(jié)果值,接下來調(diào)用it.next(7) ,這一句把 7 傳回作為被暫停的yield 表達式的結(jié)果

          3.2 從兩個視角看next 與yield

          一般來講,next 的數(shù)量要比yield 的數(shù)量多一個,這在理解代碼時會給人一種不協(xié)調(diào)不匹配的感覺

          只考慮生成器代碼

          var y = x * yield;return y;

          第一個yield 提出一個問題:我這里應(yīng)該插入什么值?

          誰來回答這個問題呢,第一個next 已經(jīng)運行使得生成器啟動并運行到此處,它顯然無法回答這個問題,因此必須由第二個next 調(diào)用回答第一個yield 提出的問題

          這就是不匹配——第二個next對第一個yield?

          接下來轉(zhuǎn)換一下視角,從迭代器角度看問題

          在此之前需要再次解釋以下yield的消息傳遞,它是雙向的,前面我們只提了next向暫停的yield發(fā)送值,下面看一下yield發(fā)出消息供next使用。

          function* foo(x) {  var y = x * (yield "hello world");  return y;}
          var it = foo(6);
          /* 第一個next不傳入任何東西,實際上就算傳了也會被瀏覽器悄咪咪丟掉 */var res = it.next();console.log(res); // { value: 'hello world', done: false }
          /* 給等待的yield傳一個7 */res = it.next(7);console.log(res); // { value: 42, done: true }

          第一個next 調(diào)用提出一個問題:foo要給我的下一個值是什么?

          誰來回答呢?第一個yield "hello world" 表達式

          這里沒有不匹配的問題

          但是,對于最后一個next ,也就是it.next(7) 提出的foo要給我的下一個值是什么的問題,沒有yield 來回答它了,那么由誰來回答呢

          答案是return 語句

          如果生成器中沒有return,則會有隱式的return undefined 來回答

          四、多個迭代器

          從語法使用的方面來看,通過一個迭代器控制生成器的時候,似乎是在控制聲明的生成器函數(shù)本身,但有一個細節(jié)需要注意,每次構(gòu)建一個迭代器,實際上就隱式構(gòu)建了生成器的一個實例,通過這個迭代器來控制其對應(yīng)的生成器實例

          同一個生成器的多個實例可以同時運行,甚至彼此交互。

          function* foo(i) {  var x = yield 2;  z++;  var y = yield x * z;  console.log({ i, x, y, z });}
          var z = 1;var it1 = foo(1);var it2 = foo(2);
          var val1 = it1.next().value;var val2 = it2.next().value;console.log({ val1, val2 }); // { val1: 2, val2: 2 }
          val1 = it1.next(val2 * 10).value; // z = 2, 1中x = 20, val1 = x * z = 40val2 = it2.next(val1 * 5).value; // z = 3, 2中x = 200, val2 = x * z = 600console.log({ val1, val2 }); // { val1: 40, val2: 600 }
          it1.next(val2 / 2); // { i: 1, x: 20, y: 300, z: 3 }it2.next(val1 / 4); // { i: 2, x: 200, y: 10, z: 3 }

          再來看一個例子。

          var a = 1;var b = 2;
          function foo() { a++; b = b * a; a = b + 3;}
          function bar() { b--; a = 8 + b; b = a * 2;}

          上面是一串普通的 js 代碼,對于普通的 JS 函數(shù),顯然要么先執(zhí)行 foo,要么先執(zhí)行 bar,二者不能交替執(zhí)行

          但是,使用生成器的話,交替執(zhí)行顯然是可能的

          var a = 1;var b = 2;
          function* foo() { a++; yield; b = b * a; a = (yield b) + 3;}
          function* bar() { b--; yield; a = (yield 8) + b; b = a * (yield 2);}

          根據(jù)每一步調(diào)用的相對順序的不同,上面的程序能產(chǎn)生多種不同的結(jié)果

          首先構(gòu)建一個輔助函數(shù)來控制迭代器

          function step(gen) {  // 初始化一個生成器來創(chuàng)建迭代器it  var it = gen();  var last;
          // 返回一個函數(shù),每次被調(diào)用都會將迭代器向前迭代一步,前面yield發(fā)出的值會在下一步發(fā)送回去 return function () { // 不管yield出來的是什么下一次都把它原原本本的傳回去 last = it.next(last).value; };}

          下面我們先從最基本的情況開始,讓 foo 在 bar 之前執(zhí)行結(jié)束。

          var s1 = step(foo);var s2 = step(bar);
          // 執(zhí)行foos1();s1();s1();
          // 執(zhí)行bars2();s2();s2();s2();
          console.log(a, b); // 11, 22

          然后我們使二者交替進行

          var s1 = step(foo);var s2 = step(bar);
          s2();// b--, b = 1, 遇到y(tǒng)ield,暫停!last接受第一個yield發(fā)出的值undefined
          s2();// 第一個yield返回值為undefined(無影響),執(zhí)行a = ...遇到y(tǒng)ield,暫停!last 接受第二個yield發(fā)出的值8
          s1();/* a++, a = 2, 遇到y(tǒng)ield,暫停!last接受第一個yield發(fā)出的值undefined */
          s2();// 第二個yield返回值為8,a = 8 + b = 8 + 1 = 9, 執(zhí)行b = 9 * ...遇到y(tǒng)ield,暫停!last 接受第三個yield發(fā)出的值2
          s1();/* 第一個yield返回值為undefined(無影響),b = b * a = 1 * 9 = 9, 執(zhí)行a = ..., 遇到y(tǒng)ield,暫停!last接受第二個yield發(fā)出的值b,也就是9 */
          s1();/* 第二個yield返回值為9,a = 9 + 3 = 12,s1執(zhí)行完畢,foo結(jié)束 */
          s2();// 第三個yield返回值為2,b = 9 * 2 = 18,s2執(zhí)行完畢,bar結(jié)束
          console.log(a, b); // 12, 18

          五、迭代器與生成器

          前面說了那么多“迭代器”與“生成器”,現(xiàn)在具體來談一談這二者是什么東東

          5.1 迭代器

          5.1.1 iterable

          在 JavaScript 中,迭代器是一個對象,它定義一個序列,并在終止時可能返回一個返回值。更具體地說,迭代器是通過使用 next() 方法實現(xiàn) Iterator protocol 的任何一個對象,該方法返回具有兩個屬性的對象:value,這是序列中的 next值;和 done,如果已經(jīng)迭代到序列中的最后一個值,則它為 true 。如果 value 和 done 一起存在,則它是迭代器的返回值。

          從 ES6 開始,從一個 iterable 中提取迭代器的方法是:iterable 必須支持以后函數(shù),其名稱是Symbol.iterable ,調(diào)用這個函數(shù)時,它會返回一個迭代器,通常每次調(diào)用會返回一個全新的迭代器

          后面我們根據(jù)例子具體分析

          5.1.2 生產(chǎn)者與迭代器

          假定現(xiàn)在你要產(chǎn)生一系列值,每一個值都與前面一個有特定的關(guān)系,要實現(xiàn)這一點,需要一個有狀態(tài)的生產(chǎn)者能記住其生成的最后一個值

          先用閉包整一個

          var getSomething = (function () {  var nextVal;  return function () {    if (nextVal === undefined) nextVal = 1;    else nextVal = nextVal * 3 + 6;    console.log(nextVal);    return nextVal;  };})();
          getSomething();getSomething();getSomething();getSomething();

          再試著對它修改,將它改為一個標(biāo)準的迭代器接口。

          var getSomething = (function () {  var nextVal;  return {    [Symbol.iterator]: function () {      return this;    },    next: function () {      if (nextVal === undefined) nextVal = 1;      else nextVal = nextVal * 3 + 6;      return { done: nextVal > 500, value: nextVal };    },  };})();
          getSomething.next().value; // 1getSomething.next().value; // 9getSomething.next().value; // 33getSomething.next().value; // 105

          其中有些令人疑惑的代碼大概就是[Symbol.iterable]: function() {return this} 這一行了

          這一行的作用是將getSomething 的值也構(gòu)建成為一個 iterable,現(xiàn)在它既是 iterable 也是迭代器。

          for (let v of getSomething) {  console.log(v);}/* 1933105321 */

          5.2 生成器

          可以把生成器看作一個值的生產(chǎn)者,我們通過迭代器接口的next 調(diào)用一次提取出一個值,所以嚴格來講,生成器本身并不是 iterrable,盡管執(zhí)行一個生成器就得到了一個迭代器

          如果使用生成器來實現(xiàn)前面的getSometing :

          function* getSomething() {  var nextVal;  while (true) {    if (nextVal === undefined) nextVal = 1;    else nextVal = nextVal * 3 + 6;    yield nextVal;  }}

          生成器會在每個yield 處暫停,函數(shù)getSomething 的作用域會被保持,也就意味著不需要閉包在調(diào)用之間保持變量狀態(tài)

          上面代碼也同樣可以使用for...of 循環(huán)

          // 注意這里是getSomething(),得到了它的迭代器來進行循環(huán)的for (var v of getSomething()) {  console.log(v);  // 不要死循環(huán)!  if (v > 500) {    break;  }}

          但是上面的代碼看起來getSomething 在break執(zhí)行之后被永遠掛起了。

          不過并非如此,for...of 循環(huán)的“異常結(jié)束”,通常由 break,return 或者未捕獲異常引起,會向生成器的迭代器發(fā)送一個信號使其終止。


          學(xué)習(xí)更多技能

          請點擊下方公眾號

          瀏覽 46
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品无码第一页在线观看 | 亚洲最大网站 | 亚洲久久视频 | 欧美肏逼网站 | 欧美又粗又大一区二区 |