ES6迭代器和生成器
JavaScript 原有的表示“集合”的資料結構,主要是陣列(Array)和物件(Object),ES6 又添加了Map和Set。這樣就需要一種統一的介面機制,來處理所有不同的資料結構。遍歷器(Iterator)就是這樣一種機制。它是一種介面,為各種不同的資料結構提供統一的訪問機制。 任何資料結構只要部署 Iterator 介面,就可以完成遍歷操作(即依次處理該資料結構的所有成員) 。
1.Iterator的作用:
- 為各種資料結構,提供一個統一的、簡便的訪問介面;
- 使得資料結構的成員能夠按某種次序排列
- ES6創造了一種新的遍歷命令for...of迴圈,Iterator介面主要供for...of消費。
2.原生具備iterator介面的資料(可用for of遍歷)
- Array
- set容器
- map容器
- String
- 函式的 arguments 物件
- NodeList 物件
let arr3 = [1, 2, 'kobe', true]; for(let i of arr3){ console.log(i); // 1 2 kobe true } 複製程式碼
let str = 'abcd'; for(let item of str){ console.log(item); // a b c d } 複製程式碼
function fun() { for (let i of arguments) { console.log(i) // 1 4 5 } } fun(1, 4, 5) 複製程式碼
var engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]); for (var e of engines) { console.log(e); } // Gecko // Trident // Webkit 複製程式碼
3.迭代器的工作原理
- 建立一個指標物件,指向資料結構的起始位置。
- 第一次呼叫next方法,指標自動指向資料結構的第一個成員
- 接下來不斷呼叫next方法,指標會一直往後移動,直到指向最後一個成員
- 每呼叫next方法返回的是一個包含value和done的物件,{value: 當前成員的值,done: 布林值}
- value表示當前成員的值,done對應的布林值表示當前的資料的結構是否遍歷結束。
- 當遍歷結束的時候返回的value值是undefined,done值為false
4.手寫一個迭代器
function myIterator(arr) { let nextIndex = 0 return { next: function() { return nextIndex < arr.length ? { value: arr[nextIndex++], done: false } : { value: undefined, value: true } } } } let arr = [1, 4, 'ads']// 準備一個數據 let iteratorObj = myIterator(arr) console.log(iteratorObj.next()) // 所有的迭代器物件都擁有next()方法,會返回一個結果物件 console.log(iteratorObj.next()) console.log(iteratorObj.next()) console.log(iteratorObj.next()) 複製程式碼

5.注意點
① for of迴圈不支援遍歷普通物件
var obj = { a: 2, b: 3 } for (let i of obj) { console.log(i) // Uncaught TypeError: obj is not iterable } 複製程式碼
物件的Symbol.iterator屬性,指向該物件的預設遍歷器方法。 當使用for of去遍歷某一個數據結構的時候,首先去找Symbol.iterator,找到了就去遍歷,沒有找到的話不能遍歷,提示 Uncaught TypeError: XXX is not iterable
② 當使用擴充套件運算子(...)或者對陣列和 Set 結構進行解構賦值時,會預設呼叫Symbol.iterator方法
let arr1 = [1,3] let arr2 = [2,3,4,5] arr2 = [1,...arr2,6] console.log(arr2) // [1, 2, 3, 4, 5, 6] 複製程式碼
二、生成器
1.概念
- Generator 函式是 ES6 提供的一種非同步程式設計解決方案 ,語法行為與傳統函式完全不同
- 語法上,首先可以把它理解成, Generator 函式是一個狀態機,封裝了多個內部狀態 。
- Generator 函式除了狀態機,還是一個遍歷器物件生成函式 。
- 可暫停函式(惰性求值), yield可暫停,next方法可啟動。每次返回的是yield後的表示式結果
2.特點
- function關鍵字與函式名之間有一個星號;
- 函式體內部使用yield表示式,定義不同的內部狀態
function* generatorExample(){ console.log("開始執行") yield 'hello'; yield 'generator'; } // generatorExample() // 這種呼叫方法Generator 函式並不會執行 let MG = generatorExample() // 返回指標物件 MG.next() //開始執行{value: "hello", done: false} 複製程式碼
Generator 函式是分段執行的,呼叫next方法函式內部邏輯開始執行,遇到yield表示式停止,返回 {value: yield後的表示式結果/undefined, done: false/true}
,再次呼叫next方法會從上一次停止時的yield處開始,直到最後。
function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var 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 } 複製程式碼
第一次呼叫,Generator 函式開始執行,直到遇到第一個yield表示式為止。next方法返回一個物件,它的value屬性就是當前yield表示式的值hello,done屬性的值false,表示遍歷還沒有結束。
第二次呼叫,Generator 函式從上次yield表示式停下的地方,一直執行到下一個yield表示式。next方法返回的物件的value屬性就是當前yield表示式的值world,done屬性的值false,表示遍歷還沒有結束。
第三次呼叫,Generator 函式從上次yield表示式停下的地方,一直執行到return語句(如果沒有return語句,就執行到函式結束)。next方法返回的物件的value屬性,就是緊跟在return語句後面的表示式的值(如果沒有return語句,則value屬性的值為undefined),done屬性的值true,表示遍歷已經結束。
第四次呼叫,此時 Generator 函式已經執行完畢,next方法返回物件的value屬性為undefined,done屬性為true。以後再呼叫next方法,返回的都是這個值。
3.next傳遞引數
yield表示式本身沒有返回值,或者說總是返回undefined。next方法可以帶一個引數,該引數就會被當作上一個yield表示式的返回值。
function* generatorExample () { console.log('開始執行') let result = yield 'hello' console.log(result) yield 'generator' } let MG = generatorExample() MG.next() MG.next() // 開始執行 // undefined // {value: "generator", done: false} 複製程式碼
沒有傳值時result預設是undefined,接下來我們向第二個next傳遞一個引數,看下輸出結果是啥?
function* generatorExample () { console.log('開始執行') let result = yield 'hello' console.log(result) yield 'generator' } let MG = generatorExample() MG.next() MG.next(11) // 開始執行 // 11 // {value: "generator", done: false} 複製程式碼
4.與 Iterator 介面的關係
我們上文中提到物件沒有iterator介面,用for...of遍歷時便會報錯。
let obj = { username: 'kobe', age: 39 } for (let i of obj) { console.log(i) //Uncaught TypeError: obj is not iterable } 複製程式碼
由於 Generator 函式就是遍歷器生成函式,因此可以把 Generator 賦值給物件的Symbol.iterator屬性,從而使得該物件具有 Iterator 介面。
let obj = { username: 'kobe', age: 39 } obj[Symbol.iterator] = function* myTest() { yield 1; yield 2; yield 3; }; for (let i of obj) { console.log(i) // 1 2 3 } 複製程式碼
上面程式碼中,Generator函式賦值給Symbol.iterator屬性,從而使得obj物件具有了 Iterator 介面,可以被for of遍歷了。
5.Generator的非同步的應用
業務需求:
- 傳送ajax請求獲取新聞內容
- 新聞內容獲取成功後再次傳送請求,獲取對應的新聞評論內容
- 新聞內容獲取失敗則不需要再次傳送請求。
如何實現(前端核心程式碼如下):
function* sendXml() { // url為next傳參進來的資料 let url = yield getNews('http://localhost:3000/news?newsId=2');//獲取新聞內容 yield getNews(url);//獲取對應的新聞評論內容,只有先獲取新聞的資料拼湊成url,才能向後臺請求 } function getNews(url) { $.get(url, function (data) { console.log(data); let commentsUrl = data.commentsUrl; let url = 'http://localhost:3000' + commentsUrl; // 當獲取新聞內容成功,傳送請求獲取對應的評論內容 // 呼叫next傳參會作為上次暫停是yield的返回值 sx.next(url); }) } let sx = sendXml();// 傳送請求獲取新聞內容 sx.next(); 複製程式碼