主要知識點:迭代器、生成器、可迭代對象以及for-of循環、迭代器的高級功能以及建立異步任務處理器數組
何爲迭代器?異步
迭代器是被設計專用於迭代的對象,帶有特定接口。全部的迭代器對象都擁有 next() 方 法,會返回一個結果對象。該結果對象有兩個屬性:對應下一個值的 value ,以及一個布爾 類型的 done ,其值爲 true 時表示沒有更多值可供使用。迭代器持有一個指向集合位置的 內部指針,每當調用了 next() 方法,迭代器就會返回相應的下一個值。函數
何爲生成器?fetch
生成器(generator ) 是能返回一個迭代器的函數。生成器函數由放在 function 關鍵字以後的一個星號( * ) 來表示,並能使用新的 yield
關鍵字。將星號緊跟在 function 關鍵字以後,或是在中間留出空格,都是沒問題的。例如:this
function*generator(){
yield 1;
yield 2;
yield 3;
}
let iterator = generator();
console.log(iterator.next().value);//1
console.log(iterator.next().value);//2
複製代碼
生成器函數最有意思的地方是它們會在每個yield
語句後中止,例如在上面的代碼中執行yield 1
後,該函數不會在繼續往下執行。等待下一次調用next()
後,纔會繼續往下執行yield 2
。spa
除了使用函數聲明的方式建立一個生成器外,還可使用函數表達式來建立一個生成器。因爲生成器就是一個函數,一樣可使用對象字面量的方式,將對象的屬性賦值爲一個生成器函數。設計
可迭代對象是包含Symbol.iterator
屬性的對象,這個Symbol.iterator
屬性對應着可以返回該對象的迭代器的函數。在ES6中,全部的集合對象(數組、Set和Map)以及字符串都是可迭代對象,所以它們都被指定了默認的迭代器。可迭代對象能夠與ES6中新增的for-of
循環配合使用。指針
迭代器解決了for
循環中追蹤索引的問題,而for-of
循環,則是徹底刪除追蹤集合索引的須要,更能專一於操做集合內容。for-of
循環在循環每次執行時會調用可迭代對象的next()
方法,並將結果對象的value
值存儲在一個變量上,循環過程直到結果對象done
屬性變成true
爲止:code
let arr = [1,2,3];
for(let num of arr){
console.log(num);
}
輸出結果爲:1,2,3
複製代碼
for-of
循環首先會調用arr數組中Symbol.iterator
屬性對象的函數,就會獲取到該數組對應的迭代器,接下來iterator.next()
被調用,迭代器結果對象的value
屬性會被放入到變量num
中。數組中的數據項會依次存入到變量num
中,直到迭代器結果對象中的done
屬性變成true
爲止,循環就結束。cdn
訪問可迭代對象的默認迭代器
可使用可迭代對象的Symbol.iterator來訪問對象上可返回迭代器的函數:
let arr = [1,2,3];
//訪問默認迭代器
let iterator = arr[Symbol.iterator]();
console.log(iterator.next().value); //1
console.log(iterator.next().value); //2
複製代碼
經過Symbol.iterator屬性獲取到該對象的可返回迭代器的函數,而後執行該函數獲得對象的可迭代器一樣的,但是使用Symbol.iterator
屬性來檢查對象是不是可迭代對象。
建立可迭代對象
數組,Set等集合對象是默認的迭代器,固然也能夠爲對象建立自定義的迭代器,使其成爲可迭代對象。那麼迭代器如何生成?咱們已經知道,生成器就是一個能夠返回迭代器的函數,所以自定義迭代器,就是寫一個生成器函數。同時,可迭代對象必須具備Symbol.iterator
屬性,而且該屬性對應着一個可以返回迭代器的函數,所以只須要將這個生成器函數賦值給Symbol.iterator
屬性便可:
//建立可迭代對象
let obj = {
items:[],
*[Symbol.iterator](){
for(let item of this.items){
yield item;
}
}
}
obj.items.push(1);
obj.items.push(2);
for(let num of obj){
console.log(num);
}
輸出:1,2
複製代碼
ES6中許多內置類型已經包含了默認的迭代器,只有當默認迭代器知足不了時,纔會建立自定義的迭代器。若是新建對象時,要想把該對象轉換成可迭代對象的話,通常纔會須要自定義迭代器。
集合迭代器
ES6中有三種集合對象:數組、Map和Set,這三種類型都擁有默認的迭代器:
集合的默認迭代器
當for-of循環沒有顯式指定迭代器時,集合對象會有默認的迭代器。values()方法是數組和Set默認的迭代器,而entries()方法是Map默認迭代器。
字符串的迭代器
ES6旨在爲Unicode提供了徹底支持,字符串的默認迭代器就是解決字符串迭代問題的一種嘗試,這樣一來,藉助字符串默認迭代器就能處理字符而非碼元:
//字符串默認迭代器
let str ='A B';
for(let s of str){
console.log(s); //A B
}
複製代碼
擴展運算符與非數組的可迭代對象
擴展運算符能做用於全部可迭代對象,而且會使用默認迭代器來判斷須要哪些值。在數組字面量中可使用擴展運算符將可迭代對象填充到數組中:
//擴展運算符可做用到全部可迭代對象
let arr = [1,2,3];
let array = [...arr];
console.log(array); [1,2,3]
複製代碼
而且,能夠不限次數在數組字面量中使用擴展運算符,並且能夠在任意位置用擴展運算符將可迭代對象填充到數組中:
let arr = [1,2,3];
let arr2 = [7,8,9];
let array = [...arr,5,...arr2];
console.log(array); //1,2,3,5,7,8,9
複製代碼
可以經過next()方法向迭代器傳遞參數**,當一個參數傳遞給next()方法時,該參數就會成爲生成器內部yield語句中的變量值。**
//迭代器的高級功能
function * generator(){
let first = yield 1;
let second = yield first+2;
let third = yield second+3;
}
let iterator = generator();
console.log(iterator.next()); //{value: 1, done: false}
console.log(iterator.next(4)); //{value: 6, done: false}
console.log(iterator.next(5)); //{value: 8, done: false}
console.log(iterator.next()); //{value: undefined, done: true}
複製代碼
示例代碼中,當經過next()方法傳入參數時,會賦值給yield語句中的變量。
在迭代器中拋出錯誤
能傳遞給迭代器的不只是數據,還能夠是錯誤,迭代器能夠選擇一個throw()
方法,用於指示迭代器應在恢復執行時拋出一個錯誤:
//迭代器拋出錯誤
function * generator(){
let first = yield 1;
let second = yield first+2;
let third = yield second+3;
}
let iterator = generator();
console.log(iterator.next()); //{value: 1, done: false}
console.log(iterator.next(4)); //{value: 6, done: false}
console.log(iterator.throw(new Error('Error!'))); //Uncaught Error: Error!
console.log(iterator.next()); //不會執行
複製代碼
在生成器中一樣可使用try-catch
來捕捉錯誤:
function * generator(){
let first = yield 1;
let second;
try{
second = yield first+2;
}catch(ex){
second = 6
}
let third = yield second+3;
}
let iterator = generator();
console.log(iterator.next()); //{value: 1, done: false}
console.log(iterator.next(4)); //{value: 6, done: false}
console.log(iterator.throw(new Error('Error!'))); //{value: 9, done: false}
console.log(iterator.next()); //{value: undefined, done: true}
複製代碼
生成器的return語句
因爲生成器是函數,你能夠在它內部使用 return
語句,既可讓生成器早一點退出執行,也能夠指定在 next()
方法最後一次調用時的返回值。大多數狀況,迭代器上的 next()
的最後一次調用都返回了 undefined
,但你還能夠像在其餘函數中那樣,使用 return
來指定另外一個返回值。在生成器內, return 代表全部的處理已完成,所以 done 屬性會被設爲 true ,而若是提供了返回值,就會被用於 value 字段。好比,利用return讓生成器更早的退出:
function * gene(){
yield 1;
return;
yield 2;
yield 3;
}
let iterator = gene();
console.log(iterator.next());//{value: 1, done: false}
console.log(iterator.next());//{value: undefined, done: true}
console.log(iterator.next());//{value: undefined, done: true}
複製代碼
因爲使用return語句,可以讓生成器更早結束,所以在第二次以及第三次調用next()方法時,返回結果對象爲:{value: undefined, done: true}
。
還可使用return語句指定最後返回值:
function * gene(){
yield 1;
return 'finish';
}
let iterator = gene();
console.log(iterator.next());//{value: 1, done: false}
console.log(iterator.next());//{value: "finish", done: true}
console.log(iterator.next());//{value: undefined, done: true}
複製代碼
當第二次調用next()方法時,返回了設置的返回值:finish
。第三次調用 next()
返回了一個對象,其 value
屬性再次變回undefined
,你在 return
語句中指定的任意值都只會在結果對象中出現一次,此後 value
字段就會被重置爲 undefined
。
生成器委託
生成器委託是指:將生成器組合起來使用,構成一個生成器。組合生成器的語法須要yield
和*
,*
落在yield
關鍵字與生成器函數名之間便可:
function * gene1(){
yield 'red';
yield 'green';
}
function * gene2(){
yield 1;
yield 2;
}
function * combined(){
yield * gene1();
yield * gene2();
}
let iterator = combined();
console.log(iterator.next());//{value: "red", done: false}
console.log(iterator.next());//{value: "green", done: false}
console.log(iterator.next());//{value: 1, done: false}
console.log(iterator.next());//{value: 2, done: true}
console.log(iterator.next());//{value: undefined, done: true}
複製代碼
此例中將生成器gene1和gene2組合而成生成器combined,每次調用combined的next()方法時,實際上會委託到具體的生成器中,當gene1生成器中全部的yield執行完退出以後,纔會繼續執行gene2,當gene2執行完退出以後,也就意味着combined生成器執行結束。
在使用生成器委託組合新的生成器時,前一個執行的生成器返回值能夠做爲下一個生成器的參數:
//利用生成器返回值
function * gene1(){
yield 1;
return 2;
}
function * gene2(count){
for(let i=0;i<count;i++){
yield 'repeat';
}
}
function * combined(){
let result = yield * gene1();
yield result;
yield*gene2(result);
}
let iterator = combined();
console.log(iterator.next());//{value: 1, done: false}
console.log(iterator.next());//{value: 2, done: false}
console.log(iterator.next());//{value: "repeat", done: false}
console.log(iterator.next());//{value: "repeat", done: false}
console.log(iterator.next());//{value: undefined, done: true}
複製代碼
此例中,生成器gene1的返回值,就做爲了生成器gene2的參數。
一個簡單的任務運行器
生成器函數中yield能暫停運行,當再次調用next()方法時纔會從新往下運行。一個簡單的任務執行器,就須要傳入一個生成器函數,而後每一次調用next()方法就會「一步步」往下執行函數:
//任務執行器
function run(taskDef) {
// 建立迭代器,讓它在別處可用
let task = taskDef();
// 啓動任務
let result = task.next();
// 遞歸使用函數來保持對 next() 的調用
function step() {
// 若是還有更多要作的
if (!result.done) {
result = task.next();
step();
}
}
// 開始處理過程
step();
}
run(function*() {
console.log(1);
yield;
console.log(2);
yield;
console.log(3);
});
複製代碼
run()
函數接受一個任務定義(即一個生成器函數) 做爲參數,它會調用生成器來建立一個 迭代器,並將迭代器存放在 task
變量上。第一次對 next()
的調用啓動 了迭代器,並將結果存儲下來以便稍後使用。step()
函數查看result.done
是否爲 false
,若是是就在遞歸調用自身以前調用 next()
方法。每次調用 next()
都會把返回的結果保 存在 result
變量上,它老是會被最新的信息所重寫。對於 step()
的初始調用啓動了處理 過程,該過程會查看 result.done
來判斷是否還有更多要作的工做。
可以傳遞數據的任務運行器
若是須要傳遞數據的話,也很容易,也就是將上一次yield
的值,傳遞給下一次next()
調用便可,僅僅只須要傳送結果對象的value
屬性:
//任務執行器
function run(taskDef) {
// 建立迭代器,讓它在別處可用
let task = taskDef();
// 啓動任務
let result = task.next();
// 遞歸使用函數來保持對 next() 的調用
function step() {
// 若是還有更多要作的
if (!result.done) {
result = task.next(result.value);
console.log(result.value); //6 undefined
step();
}
}
// 開始處理過程
step();
}
run(function*() {
let value = yield 1;
yield value+5;
});
複製代碼
異步任務
上面的例子是簡單的任務處理器,甚至仍是同步的。實現任務器也主要是迭代器在每一次調用next()
方法時彼此間傳遞靜態參數。若是要將上面的任務處理器改裝成異步任務處理器的話,就須要yield
可以返回一個可以執行回調函數的函數,而且回調參數爲該函數的參數便可。
什麼是有回調函數的函數?
有這樣的示例代碼:
function fetchData(callback) {
return function(callback) {
callback(null, "Hi!");
};
}
複製代碼
函數fetchData
返回的是一個函數,而且所返回的函數可以接受一個函數callback。當執行返回的函數時,其實是調用回調函數callback
。但目前而言,回調函數callback仍是同步的,能夠改形成異步函數:
function fetchData(callback) {
return function(callback) {
setTimeout(function() {
callback(null, "Hi!");
}, 50);
};
}
複製代碼
一個簡單的異步任務處理器:
//異步任務處理器
function run(taskDef){
//執行生成器,建立迭代器
let task = taskDef();
//啓動任務
let result = task.next();
function step(){
while(!result.done){
if(typeof(result.value)==='function' ){
result.value(()=>{
console.log('hello world');
})
}
result = task.next();
step();
}
}
step();
}
run(function *(){
//返回一個可以返回執行回調函數的函數,而且回調函數仍是該
//函數的參數
yield function(callback){
setTimeout(callback,3000);
}
});
複製代碼
上面的示例代碼就是一個簡單的異步任務處理器,有這樣幾點要點:
run
方法中傳入的是生成器函數;yield
關鍵字,返回的是一個可以執行回調函數的函數,而且回調函數是該函數的一個參數;使用迭代器能夠用來遍歷集合對象包含的數據,調用迭代器的next()
方法能夠返回一個結果對象,其中value
屬性表明值,done
屬性用來表示集合對象是否已經到了最後一項,若是集合對象的值所有遍歷完後,done
屬性爲true
;
Symbol.iterator
屬性被用於定義對象的默認迭代器,使用該屬性能夠爲對象自定義迭代器。當Symbol.iterator
屬性存在時,該對象能夠被認爲是可迭代對象;
可迭代對象可使用for-of循環,for-of循環不須要關注集合對象的索引,更能專一於對內容的處理;
數組、Set、Map以及字符串都具備默認的迭代器;
擴展運算符能夠做用於任何可迭代對象,讓可迭代對象轉換成數組,而且擴展運算符能夠用於數組字面量中任何位置中,讓可迭代對象的數據項一次填入到新數組中;
生成器是一個特殊的函數,語法上使用了*
,yield可以返回結果,並能暫停繼續往下執行,直到調用next()方法後,才能繼續往下執行。使用生成器委託可以將兩個生成器合併組合成一個生成器;
可以使用生成器構造異步任務處理器;