48. ES6+ Generator 基礎知識
1. 前言
前面我們花了三節深入地學習了 ES6 的異步解決方案 Promise,本節學習的生成器也是為了解決異步而生的,但是它的出發思路和 Promise 截然不同。
上節我們學習了 ES6 中?迭代?的相關內容,并實現了一個迭代器。我們知道實現一個迭代器,我們需要手動添加對象的?Symbol.iterator
?屬性,并需要實現 next 方法。那么有沒有什么可以幫助我們自動實現迭代器呢?ES6 給出了生成器的方法來滿足我們的需求。我們不需要在對象上添加?Symbol.iterator
?屬性,使用生成器函數就可以實現迭代器的功能。本節我們將學習生成器的相關概念和基礎用法。
生成器是一個靈活的結構,能使得一個函數塊內部暫停和恢復代碼執行的能力。在實際應用中,使用生成器可以自定義迭代器和協程。
2. 生成器對象和生成器函數
有些概念是我們必須要理解的,前面在學習迭代器的時候,我們學習了迭代協議和迭代器協議,實現一個迭代器需要滿足這兩個協議才算是一個真正的迭代器。而本節的生成器和生成器函數也是如此,我們也需要知道生成器對象和生成器函數概念和它們直接的關系。
Generator 就是我們說的生成器,它包含兩個概念?生成器對象和生成器函數
。首先,要理解的是生成器對象和迭代器的關系,生成器對象是遵守迭代協議和迭代器協議實現的 Iterable 接口,可以理解生成器對象其實也是一個迭代器;然后,我們需要理解什么是生成器函數,生成器函數是由?function *
?來定義的,并且返回結果是一個 Generator 對象。
生成器是一個特殊的函數,在調用后會返回一個生成器對象,這個生成器對象是遵守可迭代協議和迭代器協議實現的 Iterable 接口。生成器可以使用 yield
關鍵字來暫停執行的生成器函數:
function* generator() { yield 'a'; yield 'b'; } var gen = generator(); // Object [Generator] {}
2.1 Generator.prototype.next()
生成器的 next()
方法和迭代器返回的結果是一樣的,返回了一個包含屬性 done
?和?value
?的對象,該方法也可以通過接受一個參數用以向生成器傳值。
使用 yield
返回的值會被迭代器的 next()
方法捕獲:
var gen = generator(); gen.next(); // {value: 'a', done: false} gen.next(); // {value: 'b', done: false} gen.next(); // {value: undefined, done: true}
從上面代碼的執行結果可以看出,生成器函數在執行后會返回一個生成器對象,這個生成器對象滿足迭代協議和迭代器協議,所以我們可以去手動調用它的 next() 方法去獲取每一步的返回值。從這里可以看出,生成器其實就是迭代器的一個應用,并且這個應用會在異步中大放異彩。
2.2 Generator.prototype.return()
return()
?方法返回給定的值并結束生成器。
var gen = generator(); gen.next(); // { value: 'a', done: false } gen.return("imooc"); // { value: "mybj", done: true } gen.next(); // { value: undefined, done: true }
另外,如果對已經完成狀態的生成器調用?return(value)
?則生成器會一直保持在完成狀態,如果出入參數,value
?會設置成傳入的參數,done
?的值不變:
var gen = generator(); gen.next(); // { value: 1, done: false } gen.next(); // { value: 2, done: false } gen.next(); // { value: undefined, done: true } gen.return(); // { value: undefined, done: true } gen.return(1); // { value: 1, done: true }
2.2 Generator.prototype.throw()
throw()
?方法用來向生成器拋出異常,并恢復生成器的執行,返回帶有?done
?及?value
?兩個屬性的對象。
function* generator() { while(true) { try { yield 'mybj' } catch(e) { console.log("Error caught!"); } } } var gen = generator(); gen.next(); // { value: "mybj", done: false } gen.throw(new Error("error")); // "Error caught!"
3. Generator 案例
3.1 類數組轉化
將一個類數組轉化為一個真正的數組方式有很多,ES6 提供了?Array.from()
?可以將類數組轉化為數組 。另外在一些函數中可以使用?[...argument]
?的方式轉化類數組。
function fn() { const arg = [...arguments]; console.log(arg); } fn(1, 2, 3); // [1, 2, 3]
當然我們知道類數組的定義,所以我們自己定義一個類數組,看能不能使用展開運算符將類數組轉化為數組:
const likeArr = { 0: 1, 1: 2, length: 2, } console.log([...likeArr]); // Uncaught TypeError: likeArr is not iterable
上面代碼中我們定義了一個類數組,但是使用展開運算符報錯了,提示我們 likeArr 不是一個迭代器。因為在函數中類數組是內部幫我們實現了迭代器的功能,而我們自己定義的類數組是不具有迭代器功能的,那我們來自己實現一個:
likeArr[Symbol.iterator] = function() { let index = 0; return { next: () => { return { value: this[index], done: index++ === this.length} } } } console.log([...likeArr]); // [1, 2]
上面的代碼我們在 likeArr 對象上定義了?Symbol.iterator
它具有迭代功能。上面代碼中我們需要手動地去實現 next()
方法,這比較麻煩,那能不能簡化一下呢?我們的生成器函數就出場了:
likeArr[Symbol.iterator] = function* () { let index = 0; while (index !== this.length) { yield this[index++]; } } console.log([...likeArr]); // [1, 2]
上面的代碼使用了生成器函數,并且沒有去手動實現 next() 方法,從這里我們也能很清楚地知道迭代器和生成器的關系。而且使用生成器函數更加簡單方便。
3.2 單步獲取質數
還有一個案例是面試中經常會考到的。
題目:實現一個函數,每次調用返回下一個質數,要求不使用全局變量,且函數本身不接受任何參數。
從題目的要求可以知道,這個函數每次調用都會返回一個質數,也就是說每次調用后都會返回一個函數。
首先我們定義一個判斷一個數是否為質數的方法:
function isPrime(num) { for (let i = 2; i <= Math.sqrt(num); i++) { if (num % i === 0) { return false } } return true }
傳統的方式是使用閉包方法來解決:
function primeHandler() { let prime = 1 return () => { while (true) { prime++ if (isPrime(prime)) { return prime } } } } const getPrime = primeHandler() console.log(getPrime()); // 2 console.log(getPrime()); // 3 console.log(getPrime()); // 5
既然是單步執行的,那么我們就可以使用迭代器方式實現:
var prime = {} prime[Symbol.iterator] = function() { let prime = 1; return { next() { while(true) { prime++ if (isPrime(prime)) { return prime; } } } } } var getPrime = prime[Symbol.iterator]().next; console.log(getPrime()); // 2 console.log(getPrime()); // 3
上一個實例我們知道實現迭代器的方式是很麻煩的,可以使用生成器函數去替代迭代器的功能,所以上面的代碼可以使用生成器函數改造如下:
function* primeGenerator () { let prime = 1 while (true) { prime++ if (isPrime(prime)) { yield prime } } } var getPrime = primeGenerator().next().value console.log(getPrime()); // 2 console.log(getPrime()); // 3
4. 小結
本節我們主要學習了生成器的概念和用法,需要生成器對象是由生成器函數返回的結果,生成器對象是遵守迭代協議和迭代器協議實現的 Iterable 接口。生成器其實就是對迭代器的應用。另外,通過兩個案例更加深刻地理解了生成器的應用場景,對比了生成器和迭代器的不同。
1. 本站所有文章教程及資源素材均來源于網絡與用戶分享或為本站原創,僅限用于學習和研究。
2. 如果內容損害你的權益請聯系客服QQ:1642748312給予處理。
碼云筆記 » 48. ES6+ Generator 基礎知識