ES6-生成器及其實現

1. 初識生成器Generator

閑來晚上睡不著稽煤,你去數三羊核芽,1,2,3就真的三只羊,就沒了酵熙。轧简。。還是睡不著匾二,于是繼續(xù)產出(yield)羊:

function* SheepGenerator(){
    let index = 1;
    while(true){
        yield index++;
    }
}

let countSheep = SheepGenerator();

countSheep.next().value; // 1
countSheep.next().value; // 2
countSheep.next().value; // 3
countSheep.next().value; // 4
哮独。。察藐。

這樣皮璧,你能數到天荒地老。分飞。悴务。只要調用,就有產出浸须。這就是一個最基本的生成器函數惨寿。

形式上邦泄,Generator 函數是一個普通函數删窒,但是有兩個特征:

  • function關鍵字與函數名之間有一個星號*
  • 函數體內部使用yield表達式顺囊,定義不同的內部狀態(tài)肌索。

Generator 函數的調用方法與普通函數一樣,也是在函數名后面加上一對圓括號特碳。不同的是诚亚,調用 Generator 函數后晕换,該函數并不執(zhí)行,返回的也不是函數運行結果站宗,而是一個指向內部狀態(tài)的指針對象闸准,即遍歷器對象(Iterator Object)。必須調用遍歷器對象的next方法梢灭,使得指針移向下一個狀態(tài)夷家。那么什么是狀態(tài)呢?

2. 生成器的狀態(tài)

countSheep.next() // {value: 6, done: false}

通過next方法調用生成器函數敏释,發(fā)現返回一個對象库快,其中value表示當前產出的值,done表示是否還有下一個值钥顽,如果沒有(done: true)(但是當不存在return語句時义屏,就算沒有下一個yield,最后一個yield也會為done:false)蜂大,繼續(xù)調用返{value: undefined, done: true}闽铐,value值變?yōu)?code>undefined,就算下次沒有值了县爬,你也可以繼續(xù)調用阳啥,只不過返回的valueundefined

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world'; // { value: 'world', done: false }
  return 'ending'; // { value: 'ending', done: true }
}

let hw = helloWorldGenerator();

hw.next(); // { value: 'hello', done: false }

hw.next();// { value: 'world', done: false }

hw.next();// { value: 'ending', done: true }

hw.next();// { value: undefined, done: true }

hw.next();// { value: undefined, done: true }

3. yield表達式

yield將生成器分為一個一個節(jié)點,每次調用.next方法時财喳,指針就會移到yield節(jié)點處察迟,產出當前值,再次調用.next時耳高,指針就會移到下一個yield節(jié)點處扎瓶,以此類推。泌枪。概荷。如果遇到return語句,標志return是最后一個yield節(jié)點碌燕,return后面的yield會被忽略:

function* helloGenerator() {
  yield 'hello';
  return 'ending'; // yield節(jié)點截止標志
  yield 'World';  // 會被忽略
}

let hg = helloGenerator();
hg.next();//{ value: 'hello', done: false }
hg.next();// { value: 'ending', done: true }
hg.next();// { value: undefined, done: true }

Generator 函數可以不用yield表達式误证,這時就變成了一個單純的暫緩執(zhí)行函數:

function* f() {
  console.log('執(zhí)行了!')
}

var generator = f();
generator.next(); // '執(zhí)行了'

函數f如果是普通函數修壕,在為變量generator賦值時就會執(zhí)行愈捅。但是,函數f是一個 Generator 函數慈鸠,就變成只有調用next方法時蓝谨,函數f才會執(zhí)行。

yield表達式只能用在 Generator 函數里面,用在其他地方都會報錯譬巫。

4. yield* 表達式

如果yield表達式后面跟的是一個遍歷器對象咖楣,即在yield表達式后面加上星號,表明它返回的是一個遍歷器對象芦昔。稱為yield*表達式诱贿。

yield 表達式*,就是在一個 Generator 函數里面執(zhí)行另一個 Generator 函數:

function* foo() {
  yield 'a';
  yield 'b';
}

function* bar() {
  yield 'x';
  yield* foo(); // 內部執(zhí)行Generator bar
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

任何數據結構只要有 Iterator 接口咕缎,就可以被yield*遍歷:

function* gen(){
  yield* ["a", "b", "c"];
}

gen().next() // { value:"a", done:false }

5. 生成器(Generator)與迭代(遍歷)器(Iterator)

原生具備 Iterator 接口的數據結構如下瘪松。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函數的 arguments 對象
  • NodeList 對

一個對象如果要具備可被for...of循環(huán)調用的 Iterator 接口,就必須在Symbol.iterator的屬性上部署遍歷器生成方法(原型鏈上的對象具有該方法也可)锨阿。

迭代器協(xié)議

當一個對象只有滿足下述條件才會被認為是一個迭代器:

它實現了一個 next() 的方法并且擁有以下含義:

屬性
next 返回一個對象的無參函數宵睦,被返回對象擁有兩個屬性:done (boolean)true:迭代器已經超過了可迭代次數。這種情況下,value的值可以被省略如果迭代器可以產生序列中的下一個值墅诡,則為 false壳嚎。這等效于沒有指定done這個屬性。value - 迭代器返回的任何 JavaScript 值末早。done 為 true 時可省略烟馅。next 方法必須要返回一一個對象,該對象有兩個必要的屬性: done和value然磷,如果返回一個非對象值(比如false和undefined) 會展示一個 TypeError ("iterator.next() returned a non-object value") 的錯誤

可迭代協(xié)議

普通對象變成可迭代對象郑趁, 一個對象必須實現 @@iterator 方法, 即這個對象(或者它原型鏈 prototype chain 上的某個對象)必須有一個名字是 Symbol.iterator 的屬性:

Symbol.iterator屬性對應一個函數,執(zhí)行后返回當前對象的迭代器對象姿搜。

屬性
[Symbol.iterator] 返回一個對象的無參函數寡润,被返回對象符合迭代器協(xié)議。

自定義迭代器:

let myIterable = {};
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...myIterable]; // [1, 2, 3]

對數組和 Set 結構進行解構賦值時舅柜,會默認調用Symbol.iterator方法:

let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];

擴展運算符(...)也會調用默認的 Iterator 接口:

// 例一
var str = 'hello';
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

`生成器對象 既是迭代器也是可迭代對象:

let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

let iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

[...iterator]; // [1, 2, 3, 4, 5]

一旦next方法的返回對象的done屬性為true梭纹,for...of循環(huán)就會中止,且不包含該返回對象致份,即return語句的值不會被for..of 遍歷,也不會被解構出來:

function* foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  yield 5;
  return 6; // return不會被for..of 遍歷,也不會被解構出來
}
[...foo()]; // [1, 2, 3, 4, 5]
// foo()
for (let v of foo()) {
  console.log(v);
}
// 1 2 3 4 5

// 注意f
let f = foo()
for (let v of f) {
  console.log(v);
}

實現斐波那契數列:

function* fibonacci(){
    let [prev, curr] = [0, 1];
    while(true){
        yield curr;
        [prev, curr] = [curr, prev + curr];
    }
}
for(let n of fibonacci()) {
    if(n > 100) break;
    console.log(n)
}

迭代協(xié)議

6. Generator.prototype.next

  • 語法:gen.next(value)

next() 方法返回一個包含屬性 donevalue 的對象变抽。該方法也可以通過接受一個參數用以向生成器傳值。

function* gen() {
  while(true) {
    var value = yield null;
    console.log(value);
  }
}

let g = gen();
g.next(1); 
// "{ value: null, done: false }"
g.next(2); 
// 2
// "{ value: null, done: false }"

注意氮块,由于next方法的參數表示上一個yield表達式的返回值绍载,所以在第一次使用next方法時,傳遞參數是無效的滔蝉。

7. Generator.protytype.return

  • 語法:gen.return(value)

return() 方法返回給定的值(value)并結束生成器击儡。如果return方法調用時,不提供參數锰提,則返回值的value屬性為undefined曙痘。

function* gen() { 
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next();        // { value: 1, done: false }
g.return("foo"); // { value: "foo", done: true }
g.next();       // { value: undefined, done: true }

如果 Generator 函數內部有try...finally代碼塊,且正在執(zhí)行try代碼塊立肘,那么return方法會導致立刻進入finally代碼塊边坤,執(zhí)行完以后,整個函數才會結束谅年。

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 }

8. Generator.prototype.throw

  • 語法:gen.throw(exception)

throw() 方法用來向生成器拋出異常茧痒,并恢復生成器的執(zhí)行,返回帶有 donevalue 兩個屬性的對象融蹂。

function* gen() {
  while(true) {
    try {
       yield 42;
    } catch(e) {
      console.log(e);
    }
  }
}

let g = gen();
g.next(); // { value: 42, done: false }
g.throw(new Error("Something went wrong")); //"Something went wrong"

9.理解 next()旺订、throw()、return()

next()超燃、throw()倘待、return()這三個方法本質上是同一件事高氮,都是讓 Generator 函數恢復執(zhí)行,并且使用不同的語句替換yield表達式

next()是將yield表達式替換成一個值:

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;

throw()是將yield表達式替換成一個throw語句:

gen.throw(new Error('出錯了')); // Uncaught Error: 出錯了
// 相當于將 let result = yield x + y
// 替換成 let result = throw(new Error('出錯了'));

return()是將yield表達式替換成一個return語句:

gen.return(2); // Object {value: 2, done: true}
// 相當于將 let result = yield x + y
// 替換成 let result = return 2;

10. 手寫generator函數

下面實現這樣一個函數:

1..next調用:

其實這就是一個迭代器,實現了迭代器協(xié)議

let g = generator(1,2,3);
g.next(); // {value: 1, done: false}
g.next(); // {value: 2, done: false}
g.next(); // {value: 3, done: false}
g.next(); // {value: undefined, done: true}

使用單例模式及閉包(簡單理解琼富,就是函數內部嵌套的函數引用了內部變量)實現:

function generator(...rest) {
    let index = -1;  // 內部變量
    return {
        next() {    // 內部函數
            index++; // 形成閉包
            return {value: rest[index], done: !rest[index]}
        }
    }
}

// 測試
let g = generator(1,2);
g.next(); // {value: 1, done: false}
g.next(); // {value: 2, done: false}
g.next(); // {value: undefined, done: true}

2.for...of遍歷:

for...of遍歷會默認調用Symbol.iterator方法

其實這就是一個可迭代對象,實現了可迭代協(xié)議:

for(let i of g) {
    console.log(i);
}; // 依次輸出 1 2 3

上面雖然實現了.next操作竟贯,但是返回的是一個普通對象而不是一個可迭代對象盗誊,是不能使用for...of遍歷的,下面將這個普通對象變?yōu)榭傻鷮ο螅?/p>

function generator(...rest) {
    let index = -1;
    return {
        next() { // 產生迭代器
            index++;
            return {value: rest[index], done: !rest[index]}
        }, 
        [Symbol.iterator](){
            return this; // 返回當前迭代器
        }
    }
}

let g = generator(1,2,3);
// for..of
for(let i of g){
    console.log(i); // 輸出 1 2 3
}
// 解構
[...g]; // [1,2,3]

因為Generator既是迭代器(.next())又是可迭代對象([Symbol.iterator])士葫,所以我們的generator也要實現迭代器協(xié)議和可迭代協(xié)議乞而。

3..return結束:

let f = generator([1,2,3]);
g.next(); // {value: 1, done: false}
g.return(22); // {value: 22, done: true}
g.next(); // {value: undefined, done: true}

注意return是關鍵字,最好使用ES6屬性名表達式['return'](){}定義方法名

function generator(...rest) {
    let index = -1;
    let isReturned = false; // 判斷是否調用了return
    return {
        next() {
            index++;
            if (!isReturned) {
                return {value: rest[index], done: !rest[index]}
            }
            return {value: undefined, done: true}
        },
        [Symbol.iterator]() {
            return this;
        },
        ['return'](value) { // ES6屬性名表達式
            isReturned = true;
            return {value: value, done: true};
        },
    }
}

// 測試:
let a = generator(1,2,3)
{next: ?, return: ?, Symbol(Symbol.iterator): ?}
a.next()
// {value: 1, done: false}
a.return(111)
// {value: 111, done: true}
a.next()
// {value: undefined, done: true}
a.next()
// {value: undefined, done: true}

4..throw的實現:

function generator(...rest) {
    let index = -1;
    let isReturned = false; // 判斷是否調用了return
    let isThrowed = false; // 判斷是否調用了throw
    return {
        next() {
            index++;
            if (!isReturned && !isThrowed) {
                return {value: rest[index], done: !rest[index]}
            }
            return {value: undefined, done: true}
        },
        [Symbol.iterator]() {
            return this;
        },
        
        ['return'](value) { // ES6屬性名表達式
            isReturned = true;
            return {value: value, done: true};
        },
        // 同理:throw的實現
        ['throw'](value) { // ES6屬性名表達式
            isThrowed = true;
            throw value;
        }
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末慢显,一起剝皮案震驚了整個濱河市爪模,隨后出現的幾起案子,更是在濱河造成了極大的恐慌荚藻,老刑警劉巖呻右,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異鞋喇,居然都是意外死亡声滥,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門侦香,熙熙樓的掌柜王于貴愁眉苦臉地迎上來落塑,“玉大人,你說我怎么就攤上這事罐韩『读蓿” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵散吵,是天一觀的道長龙考。 經常有香客問我蟆肆,道長,這世上最難降的妖魔是什么晦款? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任炎功,我火速辦了婚禮,結果婚禮上缓溅,老公的妹妹穿的比我還像新娘蛇损。我一直安慰自己,他們只是感情好坛怪,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布淤齐。 她就那樣靜靜地躺著,像睡著了一般袜匿。 火紅的嫁衣襯著肌膚如雪更啄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天居灯,我揣著相機與錄音锈死,去河邊找鬼。 笑死穆壕,一個胖子當著我的面吹牛待牵,可吹牛的內容都是我干的。 我是一名探鬼主播喇勋,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缨该,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了川背?” 一聲冷哼從身側響起贰拿,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熄云,沒想到半個月后膨更,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡缴允,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年荚守,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片练般。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡矗漾,死狀恐怖,靈堂內的尸體忽然破棺而出薄料,到底是詐尸還是另有隱情敞贡,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布摄职,位于F島的核電站誊役,受9級特大地震影響获列,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蛔垢,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一击孩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧啦桌,春花似錦、人聲如沸及皂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽验烧。三九已至板驳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碍拆,已是汗流浹背若治。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留感混,地道東北人端幼。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像弧满,于是被迫代替她去往敵國和親婆跑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內容