es6-迭代器和生成器

timg.jpg

前言

從家里回來了,翻了翻書,發現es6的知識點,忘了不少,還好記了點筆記,今年需要更加努力,看著以前的同學都開始娶妻生子,買車買房,我也要努力為自己的未來考慮

我的博客地址 :http://www.aymfx.cn/

迭代器

一個迭代器對象 ,知道如何每次訪問集合中的一項, 并跟蹤該序列中的當前位置。在 JavaScript 中 迭代器是一個對象,它提供了一個next() 方法,用來返回序列中的下一項。這個方法返回包含兩個屬性:done和 value。

//這是模仿es6迭代器的方式
    function createIterator (items){
        var i =0;
        return {
            next:function(){
                var done = (i>=items.length);
                var value = !done ? items[i++]:undefined;
                
                return {
                    done:done,
                    value:value
                }
            }
        }
    }
    var iterator= createIterator([1,2,3]);
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    
//{done: false, value: 1}
//VM42:18 {done: false, value: 2}
//VM42:19 {done: false, value: 3}
//VM42:20 {done: true, value: undefined}
//VM42:21 {done: true, value: undefined}

這是個簡單的es5的迭代器,在es6的迭代器更加復雜

什么是生成器

雖然自定義的迭代器是一個有用的工具,但由于需要顯式地維護其內部狀態,因此需要謹慎地創建。Generators提供了一個強大的選擇:它允許你定義一個包含自有迭代算法的函數, 同時它可以自動維護自己的狀態。它是這樣表示的

//生成器

function *createIterator(){
    yield 1;
    yield 2;
    yield 3;
    
}
//生成器的調用方式與普通函數相同,只不過返回的是一個迭代器
 var iterator= createIterator();
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());

//{value: 1, done: false}
//VM55:12 {value: 2, done: false}
//VM55:13 {value: 3, done: false}

每個yield相當于一個調用一次next方法,而且每次執行yield都會暫停一次。函數名前面的*代表它是一個生成器

yield只能在生成器中用,不能在普通函數使用,生成器的內部函數也不能使用(語法錯誤),另外yield關鍵字可以返回任何值或者表達式

function * createIterator(itmes){
    for(let i = 0; i< items.length;i++){
        yield items[i]
    }
}

var iterator= createIterator([1,2,3]);
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());

//{value: 1, done: false}
//VM98:8 {value: 2, done: false}
//VM98:9 {value: 3, done: false}
//VM98:10 {value: undefined, done: true}
//VM98:11 {value: undefined, done: true}

生成函數表達式

let createIterator = function *(items) {
    for (let i = 0; i< items.length ; i++){
        yield items[i];
    }
    
}

var iterator= createIterator([1,2,3]);
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());
    console.log(iterator.next());

//VM108:10 {value: 1, done: false}
//VM108:11 {value: 2, done: false}
//VM108:12 {value: 3, done: false}
//VM108:13 {value: undefined, done: true}
//VM108:14 {value: undefined, done: true}

不能用箭頭函數來創建生成器

生成器對象的方式

let o = {
    createIterator:function *(items) {
        for (let i = 0; i< items.length ; i++){
        yield items[i];
    }
    }
}

let o.createIterator([1,2,3])

也可以用es6的寫法

let o = {
    *createIterator(items) {
        for (let i = 0; i< items.length ; i++){
        yield items[i];
    }
    }
}

let o.createIterator([1,2,3])


可迭代對象和for-of循環

es6中所有的集合對象(數組,Set集合以及Map集合)和字符串都是可迭代對象,可迭代對象都是具有Symbol.iterator屬性,通過指定的函數可以返回一個作用于附屬對象的迭代器

由生成器創建的迭代器都是可迭代對象,因為它會默認為Symbol.iterator屬性賦值。

for -of 每次執行都會調用next()方法,并將返回的結果對象的value存儲在變量中,直到遇到對象的done屬性為true

 let values = [1,2,3];

for(let value of values){
      console.log(value);
}

//返回結果
// 1
// 2
// 3

訪問可迭代對象的默認的迭代器

let values = [1,2,3];
let iterator = values[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

//運行結果

//{value: 1, done: false}
//VM57:5 {value: 2, done: false}
//VM57:6 {value: 3, done: false}
//VM57:7 {value: undefined, done: true}

創建可以迭代的對象

默認情況下開發者定義的對象是不能迭代的,但是我們可以給Symbol.iterator屬性加一個生成器

let arr = {
      items:[],
      *[Symbol.iterator](){
          for(let item of this.items){
        yield item;
                                                        }
                                            }
}
let a = [1,2,3];
a.forEach(value => arr.items.push(value));

for(let x of arr){
      console.log(x);
 }
// 1
//2
//3

內建迭代器

es6自己定義了一些迭代器,我們只有在無法用這些內建迭代器實現功能時才可能自己創建

集合對象迭代器

es6的三種對象數組,Set集合,Map集合都內置了下列三種迭代器

  • entries() 返回一個迭代器,其值為多個鍵值對
  • values() 返回一個迭代器,其值為集合的值
  • keys() 返回一個迭代器,其值為集合中的所有鍵名

entries()栗子 三種表現

let colors = ['red','blue','green'];
let tracking = new Set([123,567,9012]);
let data = new Map();
data.set('title','es6教程');
data.set('format','ebook');
for (let entry of colors.entries()){
      console.log(entry);
}
for (let entry of tracking .entries()){
      console.log(entry);
}
for (let entry of data.entries()){
      console.log(entry);
}
運行結果

values()迭代器

let colors = ['red','blue','green'];
let tracking = new Set([123,567,9012]);
let data = new Map();
data.set('title','es6教程');
data.set('format','ebook');
for (let entry of colors.values()){
      console.log(entry);
}
for (let entry of tracking .values()){
      console.log(entry);
}
for (let entry of data.values()){
      console.log(entry);
}
a.png

數組的values()谷歌暫不支持,所以我們需要小心使用這些方法,有的瀏覽器還是支持的,谷歌不支持我肯定不會用

keys()迭代器

let colors = ['red','blue','green'];
let tracking = new Set([123,567,9012]);
let data = new Map();
data.set('title','es6教程');
data.set('format','ebook');
for (let entry of colors.keys()){
      console.log(entry);
}
for (let entry of tracking .keys()){
      console.log(entry);
}
for (let entry of data.keys()){
      console.log(entry);
}
a.png

不同的集合類型在使用for-of迭代有各自默認的迭代器

let colors = ['red','blue','green'];
let tracking = new Set([123,567,9012]);
let data = new Map();
data.set('title','es6教程');
data.set('format','ebook');

//與colors.values()方法相同
for(let value of colors){
  console.log(value);
}

//與tracking .values()方法相同
for(let value of tracking ){
  console.log(value);
}

//與data.entries()方法相同
for(let value of data){
  console.log(value);
}

a.png

WeakMap和WekSet因為需要管理弱引用,因此無法切確知道集合中存在的值,所以不能被迭代

解構的方式來用for-of

let data = new Map();
data.set('title','es6教程');
data.set('format','ebook');

for(let [key,value] of  data){
  console.log(key+":"+value);
}

//title:es6教程
//format:ebook

字符串迭代器

es5發布以后,字符串的慢慢變的像數組,于是我們有些方式可以用了,例如我們可以通過[]來獲取字符串的中的字符。但是我們怎么訪問雙字節,就如下面這種情況

var message = 'A ?? B';
for(let i = 0;i<message.length;i++){
  console.log(message[i]);
}
a.png

我們可以用 for-of來做迭代

var message = 'A??B';
for(let i of message){
  console.log(i);
}
a.png

NodeList迭代器

雖然在es6之前Nodelist和數組在內部差異表現不一致,容易造成困擾,但是es6之后,nodelist也有了自己的默認的迭代器,并且實現方式一致,因此我們可以這樣寫了

var divs = document.getElementsByTagName('div');

for(let nodeEle of divs){
  console.log(nodeEle);
}

展開運算符與非數組可迭代對象

//Set
let set = new Set([1,3,3,5,6]),arr = [...set];
console.log(arr); // [1, 3, 5, 6]

//Map
let map= new Map([['name','ly'],['sex','男']]),arr = [...map];
console.log(arr); 
 //0:(2) ["name", "ly"]
//1:(2) ["sex", "男"]

//數組字面量
let small = [1,2.3,4],mid = [2,3,4,6],all = [0,...small,...mid];
console.log(all); //[0, 1, 2.3, 4, 2, 3, 4, 6]

高級迭代器功能

給迭代器傳參數

function *createIterator(){
    let first = yield 1;
    let second = yield first+2;
    let third= yield second +3;
}
let iterator = createIterator();
console.log(iterator.next(1));
console.log(iterator.next(2));
console.log(iterator.next(3));
console.log(iterator.next(1));
a.png

第一個next和最后一個next,傳的值都會被丟棄,因為之前和之后都不存在可以用的值,所以傳參毫無意義,每次傳的參數都是作為上一個定義參數的值.

在迭代器中拋出錯誤

有時候我們需要增強生成器內部的編程彈性,需要將一些錯誤拋出去,讓迭代器繼續運行。

function *createIterator(){
    let first = yield 1;
    let second = yield first+2;
    let third= yield second +3;
}
let iterator = createIterator();
console.log(iterator.next(1));
console.log(iterator.next(4));
console.log(iterator.throw(new Error("boom")));
a.png

我們如何處理這些錯誤呢,可以這么寫

function *createIterator(){
    let first = yield 1;
    let second;
    try{
      second = yield first+2;
}catch(x){
    second = 3
}
   yield second +3;
}
let iterator = createIterator();
console.log(iterator.next(1));
console.log(iterator.next(4));
console.log(iterator.throw(new Error("boom")));
console.log(iterator.next());

a.png

通過return 可以提前結束函數的執行,再一次調用,屬性done將會被置為true

 function *createIterator(){
        yield 1;
        return;
        yield 2;
        yield 3;
}
let iterator = createIterator();
console.log(iterator .next());
console.log(iterator .next());
a.png

return 返回的值也可以作為一次執行的結果

 function *createIterator(){
        yield 1;
        return 20;
        yield 2;
        yield 3;
}
let iterator = createIterator();
console.log(iterator .next());
console.log(iterator .next());
console.log(iterator .next());
a.png

委托生成器

如果我們需要將兩個迭代器分別執行,但是又想只調用一個同樣的生成器,我們可以用委托生成器

function *createIteratorOne(){
        yield 1;
        yield 2;
}
function *createIteratorTwo(){
        yield 'blue';
        yield ';white';
}

 function *createIterator(){
        yield *createIteratorOne();
          yield *createIteratorTwo();
}
let iterator = createIterator();
console.log(iterator .next());
console.log(iterator .next());
console.log(iterator .next());

yield * 也可以用于字符串 例如 yield * 'hello',這時會調用字符串的默認迭代器

 function *createIterator(){
       yield * 'hello'
}
let iterator = createIterator();
console.log(iterator .next());
console.log(iterator .next());
console.log(iterator .next());
a.png

異步任務執行

生成器支持在代碼中暫停代碼的執行,因此我們可以挖掘一些用法

簡單任務生成器

function run(taskDef){
    //創建一個無使用限制的迭代器
    let task = taskDef();
    //開始執行任務
    let result = task.next();
    //循環調用next()的函數
    function step(){
        if(!result.done){
                result = task.next();
                 step();
        }
}
//開始執行迭代
  step();
}

//調用run()函數
run(function *(){
      console.log(1);
        yield;
      console.log(2);
        yield;
      console.log(3);
})

//逐步輸出 1 2 3

向任務執行器傳遞數據

function run(taskDef){
    //創建一個無使用限制的迭代器
    let task = taskDef();
    //開始執行任務
    let result = task.next();
    //循環調用next()的函數
    function step(){
        if(!result.done){
                result = task.next(result.value);
                 step();
        }
}
//開始執行迭代
  step();
}

//調用run()函數
run(function *(){
    let value =  yield 1;
    console.log(value );
    value =  yield value + 1;
    console.log(value );
})

//逐步輸出 1 2 

異步任務執行器

function run(taskDef){
    //創建一個無使用限制的迭代器
    let task = taskDef();
    //開始執行任務
    let result = task.next();
    //循環調用next()的函數
    function step(){
        if(!result.done){
                if(typeof result.value ==='function'){
                         result.value(function(err,data){
                          if(err){result = task.throw(err)};
                          return;
})
                } else {
               result = task.next();
                 step();
}
               
        }
}
//開始執行迭代
  step();
}

//讀取文件函數
let fs = require("fs");

function readFile(filename){
        return function(callback){
                fs.readFile(filename.,callback)
}
}

//調用
run(function *(){
        let contents = yield readFile("config.json");
         doSomethingWith(contents);
          console.log("Done")
})

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容