混子前端所知道關於ES6的Iterator


因爲是關於Iterator的文章,因此前文不得不提起 JavaScript 表示「集合」的數據結構,主要是數組(Array)和 對象(Object),ES6又添加了 Map 和 Set,這樣就有了四種數據集合,用戶還能夠組合使用它們,定義本身的數據結構,這樣就須要一種統一的接口機制,來處理全部不一樣的數據結構。
---本文摘選阮一峯《ECMAScript 6 標準入門》

Iterator(遍歷器)的概念

說明:遍歷器(Iterator)就是這樣一種機制。它是一種接口,爲各類不一樣的數據結構提供統一的訪問機制。任何數據結構只要部署Iterator接口,就能夠完成遍歷操做。
前端

Iterator的做用有三個:
數組

  • 爲各類數據結構,提供統一 簡便的訪問接口
  • 使數據結構的成員可以按某種次序排列
  • ES6創造了一種新的遍歷 for...of 循環,Iterator接口主要供 for...of 消費。

Iterator的遍歷過程大概是這樣的:
bash

  • 建立一個指針對象,指向當前數據結構的起始位置。
  • 第一次調用指針對象的
     
    next 方法,能夠將指針指向數據結構的第一個成員。
  • 第二次調用指針對象的 next 方法,指針就指向數據結構的第二個成員。
  • 不斷調用指針對象的 next 方法,直到它指向數據結構的結束位置。

PS:每一次調用 next方法,都會一個包含 value和 done兩個屬性的對象。數據結構

value  屬性是當前成員的值, done  屬性是一個布爾值,表示遍歷是否結束,來看代碼:

JS code:

var it = makeIterator(['a', 'b']);

it.next()    // { value: "a", done: false }
it.next()    // { value: "b", done: false }
it.next()    // { value: undefined, done: true }

function makeIterator(array) {
    var nextIndex = 0;
    return {
        next: function() {
            return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true};
        }
    };
}複製代碼

說明:makeIterator 是遍歷器生成函數,做用就是返回一個遍歷器對象。對數組 ['a', 'b'] 執行這個函數,就會返回該數組的遍歷器對象 ( 指針對象 ) it,指針對象的 next 方法,用來移動指針,next 方法返回一個對象,對象具備 value函數

 
和  done
 
兩個屬性, value  屬性返回當前位置的成員, done  屬性是一個布爾值,表示遍歷是否結束,便是否還有必要再一次調用  next  方法。


數據結構的默認Iterator接口

說明:Iterator接口的目的:爲全部數據結構提供統一的訪問機制( for...of循環 ) ,當使用 for...of 循環遍歷某種數據結構時,該循環會自動去尋找Iterator接口。ui

ES6規定,數據結構只要具備 Symbol.iterator 屬性,就是可遍歷的this

Symbol.iterator 屬性自己是一個函數,是當前數據結構默認的遍歷器生成函數,執行這個函數,就會返回一個遍歷器。spa

PS:Symbol.iterator 是一個表達式,返回prototype

 
Symbol  對象的
 
iterator  屬性,是一個預約義好類型爲Symbol的特殊值,因此要放在方括號內, 來看代碼:

JS code:

const obj = {
    [Symbol.iterator]: function () {
        return {
            next: function () {
                return {
                  value: 1,
                  done: true
                };
            }
        };
    }
};
複製代碼

說明:對象 obj 是可遍歷的,由於有 Symbol.iterator指針

 
屬性。執行這個屬性,會返回一個遍歷器對象。對象的根本特徵就是具備  next  方法。調用
 
next  方法,會返回一個表明當前成員的信息對象,具備
 
value 
 
done  兩個屬性。

在ES6中,有三類數據結構原生具有Iterator接口:數組、某些相似數組的對象、Set和Map結構,來看代碼:

JS code:

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next()    // { value: 'a', done: false }
iter.next()    // { value: 'b', done: false }
iter.next()    // { value: 'c', done: false }
iter.next()    // { value: undefined, done: true }

複製代碼

說明:變量arr是一個數組,原生具備遍歷器接口,部署在 arr 的 Symbol.iterator 屬性上面,因此,調用這個屬性,就獲得遍歷器對象。

上面提到三類數據結構原生具有Iterator接口,有可能會問爲何沒有咱們常見的對象Object,那如今爲你們解答,對象之因此沒有默認部署Iterator接口,是由於對象的哪一個屬性先遍歷,哪一個屬性後遍歷是不肯定的,須要開發者手動指定,因此一個對象若是要有可被

 
for...of  循環調用的Iterator接口,就必須在
 
Symbol.iterator  的屬性上部署遍歷器生成方法(原型鏈上的對象具備該方法也可),首先來看類部署Iterator接口的寫法,來看代碼:

JS code:
class RangeIterator {
    constructor(start, stop) {
        this.value = start;
        this.stop = stop;
    }  [Symbol.iterator]() { return this; }

  next() {
      var value = this.value;
      if (value < this.stop) {
          this.value++;
          return {done: false, value: value};
        } else {
          return {done: true, value: undefined};
        }
    }
}

function range(start, stop) {
    return new RangeIterator(start, stop);
}

for (var value of range(0, 3)) {
    console.log(value);
}複製代碼

說明:Symbol.iterator 屬性對應一個函數,執行後返回當前對象的遍歷器對象。

下面是另外一個爲對象添加Iterator接口的例子,來看代碼:

JS code:

let obj = {
    data: [ 'hello', 'world' ],
    [Symbol.iterator]() {
        const self = this;
    	let index = 0;
    	return {
      	    next() {
                if (index < self.data.length) {
          	    return {
            	        value: self.data[index++],
            	        done: false
          	    };
                } else {
          	    return { value: undefined, done: true };
        	}
      	    }
    	};
    }
};

複製代碼

PS:對於相似數組的對象(存在數值鍵名和length屬性)部署Iterator接口,有一個簡便方法,就是 Symbol.iterator 方法直接引用數組的Iterator接口,來看代碼:

JS code:

NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];

[...document.querySelectorAll('div')] // 能夠執行了

複製代碼

若是是相似數組的對象調用數組的 Symbol.iterator 方法呢?來看代碼

JS code:

let iterable = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator]
};

for (let item of iterable) {
    console.log(item); // 'a', 'b', 'c'
}

複製代碼

注意:普通對象部署數組的 Symbol.iterator 方法,並沒有效果。 


調用Iterator接口的場合

有一些場合會默認調用Iterator接口(即

 
Symbol.iterator  方法),除了
 
for...of  循環,還有幾個別的場合。

  • 解構賦值
    對數組和Set結構進行解構賦值時,會默認調用
     
    Symbol.iterator 方法。

JS code:

let set = new Set().add('a').add('b').add('c');

let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];
複製代碼

  • 擴展運算符
    擴展運算符(...)也會調用默認的iterator接口。

JS code:

// 例一
var str = 'hello';
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

複製代碼

說明:提供了一種簡便機制,能夠將任何部署了Iterator接口的數據結構 轉爲數組,即只要某個數據結構部署了Iterator接口,就能夠對它使用擴展運算符,將其轉爲數組。
  • yield*
    yield*後面跟的是一個可遍歷的結構,它會調用該結構的遍歷器接口。

JS code:

let generator = function* () {
    yield 1;
    yield* [2,3,4];
    yield 5;
};

var iterator = generator();

iterator.next()    // { value: 1, done: false }
iterator.next()    // { value: 2, done: false }
iterator.next()    // { value: 3, done: false }
iterator.next()    // { value: 4, done: false }
iterator.next()    // { value: 5, done: false }
iterator.next()    // { value: undefined, done: true }

複製代碼

  • 其餘場合
    因爲數組的遍歷會調用遍歷器接口,因此任何接受數組做爲參數的場合,其實都調用了遍歷器接口,如:
        1. for...of
        2. Array.from()
        3. Map(), Set(), WeakMap(), WeakSet()
        4. Promise.all()
        5. Promise.race()

字符串的Iterator接口

字符串是一個相似數組的對象,也原生具備Iterator接口,來看代碼:

JS code:

var someString = "hi";
typeof someString[Symbol.iterator]
// "function"

var iterator = someString[Symbol.iterator]();

iterator.next()  // { value: "h", done: false }
iterator.next()  // { value: "i", done: false }
iterator.next()  // { value: undefined, done: true }
複製代碼

說明:調用

 
Symbol.iterator  方法返回一個遍歷器對象,在這個遍歷器上能夠調用 next 方法,實現對於字符串的遍歷。


固然還能夠覆蓋原生的

 
Symbol.iterator  方法,達到修改遍歷器行爲的目的,來看代碼:

JS code:

var str = new String("hi");

[...str] // ["h", "i"]

str[Symbol.iterator] = function() {
    return {
        next: function() {
            if (this._first) {
                this._first = false;
                return { value: "bye", done: false };
            } else {
                return { done: true };
            }
        },
        _first: true
    };
};

[...str] // ["bye"]
str // "hi"

複製代碼

說明:字符串 str 的

 
Symbol.iterator  方法被修改了,因此擴展運算符 ( ... )  返回的值變成了bye ,而字符串自己仍是 hi


Iterator接口與Generator函數

Symbol.iterator 方法的最簡單實現,仍是使用Generator函數,來看代碼:

JS code:

var myIterable = {};

myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

[...myIterable] // [1, 2, 3]

// 或者採用如下簡單寫法

let obj = {
    * [Symbol.iterator]() {
        yield 'hello';
    	yield 'world';
    }
};

for (let x of obj) {
    console.log(x);
};

// hello
// world複製代碼

說明:Symbol.iterator 方法幾乎不用部署代碼,僅用 yield 命令給出每一步的返回值便可。


遍歷器對象的return(),throw()

說明:遍歷器對象除了具備

 
next  方法,還能夠具備
 
return  方法和
 
throw  方法。若是本身寫遍歷器對象生成函數, next  方法是必須部署的, return  方法和  throw  方法是否部署是可選的。

return 適用場景:若是 for...of 循環提早退出(一般是由於出錯,或有 break 語句 或 continue 語句),就會調用

 
return  方法。若是一個對象在完成遍歷前,須要清理或釋放資源,就能夠部署
 
return  方法,來看代碼:

JS code:

function readLinesSync(file) {
    return {
        next() {
      	    if (file.isAtEndOfFile()) {
                file.close();
        	return { done: true };
      	    }
    	},
    	return() {
      	    file.close();
      	    return { done: true };
    	},
    };
}
複製代碼

說明:函數 readLinesSync 接受一個文件對象做爲參數,返回一個遍歷器對象,其中除了

 
next  方法,還部署了
 
return  方法。

throw 方法主要是配合Generator函數使用,通常的遍歷器對象用不到這個方法。


好了,關於 Iterator 混子前端就總結到這裏,老規矩,歡迎點贊和糾錯!

最後,祝你們工做愉快!

( 心裏os:明天終於週五了 )

相關文章
相關標籤/搜索