迭代器是什麼?遇到這種新的概念,莫慌張。node
迭代器是一種特殊對象,每個迭代器對象都有一個next(),該方法返回一個對象,包括value和done屬性。json
ES5實現迭代器的代碼以下:segmentfault
//實現一個返回迭代器對象的函數,注意該函數不是迭代器,返回結果才叫作迭代器。 function createIterator(items) { var i = 0; return { next() { var done = (i >= items.length); // 判斷i是否小於遍歷的對象長度。 var value = !done ? items[i++] : undefined; //若是done爲false,設置value爲當前遍歷的值。 return { done, value } } } } const a = createIterator([1, 2, 3]); //該方法返回的最終是一個對象,包含value、done屬性。 console.log(a.next()); //{value: 1, done: false} console.log(a.next()); //{value: 2, done: false} console.log(a.next()); //{value: 3, done: false} console.log(a.next()); //{value: undefined, done: true}
生成器是函數:用來返回迭代器。數組
這個概念有2個關鍵點,一個是函數、一個是返回迭代器。這個函數不是上面ES5中建立迭代器的函數,而是ES6中特有的,一個帶有*(星號)的函數,同時你也須要使用到yield。dom
//生成器函數,ES6內部實現了迭代器功能,你要作的只是使用yield來迭代輸出。 function *createIterator() { yield 1; yield 2; yield 3; } const a = createIterator(); console.log(a.next()); //{value: 1, done: false} console.log(a.next()); //{value: 2, done: false} console.log(a.next()); //{value: 3, done: false} console.log(a.next()); //{value: undefined, done: true}
生成器的yield關鍵字有個神奇的功能,就是當你執行一次next(),那麼只會執行一個yield後面的內容,而後語句終止運行。異步
即便你是在for循環中使用yield關鍵字,也會暫停循環。函數
function *createIterator(items) { for(let i = 0; i < items.length; i++) { yield items[i] } } const a = createIterator([1, 2, 3]); console.log(a.next()); //{value: 1, done: false}
yield只能夠在生成器函數內部使用,若是在非生成器函數內部使用,則會報錯。測試
function *createIterator(items) { //你應該在這裏使用yield items.map((value, key) => { yield value //語法錯誤,在map的回調函數裏面使用了yield }) } const a = createIterator([1, 2, 3]); console.log(a.next()); //無輸出
函數表達式很簡單,就是下面這種寫法,也叫匿名函數,不用糾結。ui
const createIterator = function *() { yield 1; yield 2; } const a = createIterator(); console.log(a.next());
一個對象長這樣:this
const obj = {}
咱們能夠在obj中添加一個生成器,也就是添加一個帶星號的方法:
const obj = { a: 1, *createIterator() { yield this.a } } const a = obj.createIterator(); console.log(a.next()); //{value: 1, done: false}
再次默讀一遍,迭代器是對象,生成器是返回迭代器的函數。
凡是經過生成器生成的迭代器,都是能夠迭代的對象(可迭代對象具備Symbol.iterator屬性),也就是能夠經過for of將value遍歷出來。
function *createIterator() { yield 1; yield 2; yield 3; } const a = createIterator(); for(let value of a) { console.log(value) } // 1 2 3
上面的例子告訴咱們生成器函數返回的迭代器是一個能夠迭代的對象。其實咱們這裏要研究的是Symbol.iterator的用法。
function *createIterator() { yield 1; yield 2; yield 3; } const a = createIterator(); //a是一個迭代器 const s = a[Symbol.iterator]();//使用Symbol.iterator訪問迭代器 console.log(s.next()) //{value: 1, done: false}
Symbol.iterator還能夠用來檢測一個對象是否可迭代:
typeof obj[Symbol.iterator] === "function"
在ES6中,數組、Set、Map、字符串都是可迭代對象。
默認狀況下定義的對象(object)是不可迭代的,可是能夠經過Symbol.iterator建立迭代器。
const obj = { items: [] } obj.items.push(1);//這樣子雖然向數組添加了新元素,可是obj不可迭代 for (let x of obj) { console.log(x) // _iterator[Symbol.iterator] is not a function } //接下來給obj添加一個生成器,使obj成爲一個能夠迭代的對象。 const obj = { items: [], *[Symbol.iterator]() { for (let item of this.items) { yield item; } } } obj.items.push(1) //如今能夠經過for of迭代obj了。 for (let x of obj) { console.log(x) }
上面提到了,數組、Set、Map都是可迭代對象,即它們內部實現了迭代器,而且提供了3種迭代器函數調用。
一、entries() 返回迭代器:返回鍵值對
//數組 const arr = ['a', 'b', 'c']; for(let v of arr.entries()) { console.log(v) } // [0, 'a'] [1, 'b'] [2, 'c'] //Set const arr = new Set(['a', 'b', 'c']); for(let v of arr.entries()) { console.log(v) } // ['a', 'a'] ['b', 'b'] ['c', 'c'] //Map const arr = new Map(); arr.set('a', 'a'); arr.set('b', 'b'); for(let v of arr.entries()) { console.log(v) } // ['a', 'a'] ['b', 'b']
二、values() 返回迭代器:返回鍵值對的value
//數組 const arr = ['a', 'b', 'c']; for(let v of arr.values()) { console.log(v) } //'a' 'b' 'c' //Set const arr = new Set(['a', 'b', 'c']); for(let v of arr.values()) { console.log(v) } // 'a' 'b' 'c' //Map const arr = new Map(); arr.set('a', 'a'); arr.set('b', 'b'); for(let v of arr.values()) { console.log(v) } // 'a' 'b'
三、keys() 返回迭代器:返回鍵值對的key
//數組 const arr = ['a', 'b', 'c']; for(let v of arr.keys()) { console.log(v) } // 0 1 2 //Set const arr = new Set(['a', 'b', 'c']); for(let v of arr.keys()) { console.log(v) } // 'a' 'b' 'c' //Map const arr = new Map(); arr.set('a', 'a'); arr.set('b', 'b'); for(let v of arr.keys()) { console.log(v) } // 'a' 'b'
雖然上面列舉了3種內建的迭代器方法,可是不一樣集合的類型還有本身默認的迭代器,在for of中,數組和Set的默認迭代器是values(),Map的默認迭代器是entries()。
對象自己不支持迭代,可是咱們能夠本身添加一個生成器,返回一個key,value的迭代器,而後使用for of循環解構key和value。
const obj = { a: 1, b: 2, *[Symbol.iterator]() { for(let i in obj) { yield [i, obj[i]] } } } for(let [key, value] of obj) { console.log(key, value) } // 'a' 1, 'b' 2
const str = 'abc'; for(let v of str) { console.log(v) } // 'a' 'b' 'c'
迭代器真是無處不在啊,dom節點的迭代器你應該已經用過了。
const divs = document.getElementByTagName('div'); for(let d of divs) { console.log(d) }
const a = [1, 2, 3]; const b = [4, 5, 6]; const c = [...a, ...b] console.log(c) // [1, 2, 3, 4, 5, 6]
你說什麼?上面講了一堆廢話都是基礎功能?還有高級功能沒講?
高級功能不復雜,就是傳參、拋出異常、生成器返回語句、委託生成器。
一、傳參
生成器裏面有2個yield,當執行第一個next()的時候,返回value爲1,而後給第二個next()傳入參數10,傳遞的參數會替代掉上一個next()的yield返回值。在下面的例子中就是first。
function *createIterator() { let first = yield 1; yield first + 2; } let i = createIterator(); console.log(i.next()); // {value: 1, done: false} console.log(i.next(10)); // {value: 12, done: false}
二、在迭代器中拋出錯誤
function *createIterator() { let first = yield 1; yield first + 2; } let i = createIterator(); console.log(i.next()); // {value: 1, done: false} console.log(i.throw(new Error('error'))); // error console.log(i.next()); //再也不執行
三、生成器返回語句
生成器中添加return表示退出操做。
function *createIterator() {
let first = yield 1;
return;
yield first + 2;
}
let i = createIterator();
console.log(i.next()); // {value: 1, done: false}
console.log(i.next()); // {value: undefined, done: true}
四、委託生成器
生成器嵌套生成器
function *aIterator() { yield 1; } function *bIterator() { yield 2; } function *cIterator() { yield *aIterator() yield *bIterator() } let i = cIterator(); console.log(i.next()); // {value: 1, done: false} console.log(i.next()); // {value: 2, done: false}
ES6以前,咱們使用異步的操做方式是調用函數並執行回調函數。
書上舉的例子挺好的,在nodejs中,有一個讀取文件的操做,使用的就是回調函數的方式。
var fs = require("fs"); fs.readFile("xx.json", function(err, contents) { //在回調函數中作一些事情 })
那麼任務執行器是什麼呢?
任務執行器是一個函數,用來循環執行生成器,由於咱們知道生成器須要執行N次next()方法,才能運行完,因此咱們須要一個自動任務執行器幫咱們作這些事情,這就是任務執行器的做用。
下面咱們編寫一個異步任務執行器。
//taskDef是一個生成器函數,run是異步任務執行器 function run(taskDef) { let task = taskDef(); //調用生成器 let result = task.next(); //執行生成器的第一個next(),返回result function step() { if(!result.done) { //若是done爲false,則繼續執行next(),而且循環step,直到done爲true退出。 result = task.next(result.value); step(); } } step(); //開始執行step() }
測試一下咱們編寫的run方法,咱們再也不須要console.log N個next了,由於run執行器已經幫咱們作了循環執行操做:
run(function *() { let value = yield 1; value = yield value + 20; console.log(value) // 21 })
本章講了3個概念,迭代器、生成器、任務執行器。
迭代器是一個對象。
生成器是一個函數,它最終返回迭代器。
任務執行器一個函數(或者也叫生成器的回調函數),幫咱們自動執行生成器的內部運算,最終返回迭代器。
不知道看到這裏,你明白3者的區別和用法沒?