淺談ES6中迭代器和生成器


文章首發javascript


本文已經默認你已經知道generator是什麼以及for...of和數據類型map怎麼用了。html

前ES6的時代怎麼遍歷

先來一道思考題:java

經過下面的變量ios

  1. 尋找xiaohong(假設名稱惟一)是否喜歡basketball
  2. 全部同窗的名字
const students = {
    xiaohong: {
        age: '22',
        fav: ['sleep', 'basketball'],
        teachers: {
            english: 'daming',
            chinense: 'helios',
            math: ['helios2', 'helios3']
        }
    },
    xiaoming: {
        age: '22',
        fav: ['sleep', 'basketball', 'football'],
        teachers: {
            english: 'daming',
            chinense: 'helios',
            math: ['helios2', 'helios3']
        }
    },
}

複製代碼

對於第一個問題來講,咱們可使用各類循環語句: for/whilegit

for (let i =0 ;i < students[xiaoming].fav.length; i++) {
  if (students[xiaoming].fav[i] === 'basketball') console.log(true)
}


let i = 0;
while(i++ < students[xiaoming].fav.length) { if (students[xiaoming].fav[i] === 'basketball') console.log(true) } 複製代碼

for...ofes6

for (let item of students[xiaoming].fav ) {
  if (item === 'basketball') console.log(true)
}

複製代碼

那麼對於第二個問題來講,由於for/while是不能遍歷對象的,因此行不通,可是對象有一個專屬的遍歷方法 for...ingithub

咱們來看一下怎麼經過for...in來遍歷:數組

for (let stu in students) {
   console.log(stu)
}

複製代碼

你可能會想了,經過for...in去遍歷數組會怎樣呢? 咱們看一下經過for...in去遍歷:網絡

for (let item in students[xiaoming].fav) {
   console.log(item)
  // 或者去判斷
}

複製代碼

哎呀,經過for...in不也照樣能實現數組的遍歷麼,那爲何不歸結到數組的遍歷裏面去呢! 這裏面還有一些細節須要去了解(這也是上面的「對象有一個專屬的遍歷方法」爲何加粗),咱們經過一段代碼去解釋:數據結構

const num = [5, 6, 7]
for (let i in  num) {console.log(i + 1)}

// 01
// 11
// 21

複製代碼

這是由於for-in 是爲普通對象({key: value})設計的,因此只能遍歷到字符串類型的鍵。

還有下面這個雖然不經常使用,可是也是不得不說的:

const arr = [5, 6, 7]
arr.foo = function() {}
for (let i in arr) {
    console.log(i)
}

// 5
// 6
// 7
// foo !!!

複製代碼

foo屬於arr上面的方法,被遍歷出來是說的過去的。

那麼用for...of咱們來看看會怎麼樣

for (let stu of students){}
// Uncaught TypeError: students is not iterable

複製代碼

is not iterable,這個iterable是神馬東西,咱們接下來下面一步步的看。

先從可迭代(iterable)和迭代器(iterator)提及

iterable是ES6對iteration(迭代/遍歷)引入的接口。

若是一個對象被視爲iterable(可迭代)那麼它必定有一個Symbol.iterator屬性,這個屬性返回一個iterator(迭代器)方法,這個方法返回一個規定的對象(這個後面會說)。也就是說iterableiterator的工廠,iterable可以建立iteratoriterator是用於遍歷數據結構中元素的指針。

二者之間的關係

Axel Rauschmaye大神的圖簡直不能再清晰了。

image

數據消費者: javascript自己提供的消費語句結構,例如for...of循環和spread operator (...) 數據源: 數據消費者可以經過不一樣的源(Array,string)獲得供數據消費者消費的值;

數據消費者支持全部的數據源這是不能夠行的,由於還可能增長新的數據消費者數據源。所以ES6引入了Iterable接口數據源去實現,數據消費者去使用

可迭代協議(iterable protocol)和迭代器協議(iterator protocol)

可迭代協議(iterable protocol)

可迭代協議(iterable protocol) 是容許js對象可以自定義本身的迭代行爲。

簡單的說只要對象有Symbol.iterator這個屬性就是可迭代的,咱們能夠經過重寫(一些對象實現了iterable,好比Array,string)/添加(對於沒有實現iterable的對象好比object,能夠添加這個屬性,使之變爲可迭代的)該熟悉使之變爲可迭代的。

當一個對象須要被迭代(for...of 或者 spread operator )的時候,他的Symbol.iterator函數被調用而且無參數,而後返回一個迭代器。

迭代器協議(iterator protocol)

迭代器協議定義了一種標準的方式來產生一個有限或無限序列的值。

當一個對象被認爲是一個迭代器的時候,它會至少實現next()方法,next()方法返回兩個屬性value(d迭代器返回的值)和done(迭代時候已經結束)。

還有幾個可選的方法能夠被實現,具體請看:sec-iterator-interface

iterable協議iterator協議還有next之間的關係

image

來源於網絡

而後談談ES6中的for...of提及

再文章的最開始咱們已經說了再前ES6的時候,如何去遍歷。 如今咱們說說ES6新增的for...of的做用。

for...in

在前面也已經說了,在ES6以前遍歷object的時候用for...in循環,for...in會遍歷對象上全部可枚舉的值(包括原型(prototype)上的屬性),好比下面這樣:

function foo() {
    this.name = 'helios'
}

foo.prototype.sayName = function() {
    return this.name;
}
var o = new foo();
for (var i in o) {
    console.log(i)
}
// name
// sayName
複製代碼

若是咱們只想遍歷對象自身的屬性,可使用hasOwnProperty,以下:

function foo() {
    this.name = 'helios'
}

foo.prototype.sayName = function() {
    return this.name;
}
var o = new foo();
for (var i in o) {
    if (!o.hasOwnProperty(i)) continue;
    console.log(i)
}
複製代碼

若是咱們不想讓一個對象的屬性,在for...in中不被遍歷出來,但是使用Object.defineProperty來定義對象上的屬性是否可別枚舉(更多的屬性請看:Object.defineProperty()),具體以下面代碼:

var obj = {name: 'helios'}

Object.defineProperty(obj, 'age', {
    enumerable: false
})

for (var i in obj) {
    console.log(i)
}

複製代碼

在這一小節的最後咱們來講說for...in中的in操做符的含義:

prop in obj

  • 含義: 判斷prop是否在obj中
  • prop:對象的key屬性的類型(string / Symbol)
  • 返回值: boolean

咱們來看一組例子:

var o = {
    name: 'heliso'
}
console.log('name' in o) // true
console.log(Symbol.iterator in o) // false
console.log('toString' in o)  // true

複製代碼

這個操做符雖然也適用於數組,可是儘可能仍是不要用在數組中,由於會比較奇怪,以下代碼:

var arr = [6, 7,8]

console.log(7 in arr)  // false
console.log(1 in arr)  // true
console.log('length' in arr)  // true

複製代碼

主要是前兩個比較奇怪對不對,由於對於數組prop表明的是數組的索引而爲其存在的值。 按照這樣的思路,正在看的讀者你能思考一下in操做符在字符串中是怎麼的模式麼?

for...of能遍歷的集合

只要是實現了Interable接口的數據類型都能被遍歷。

javascript內部實現的有:

  • Array
  • String
  • Map
  • Set
  • arguments
  • DOM data structures

並非全部的iterable內容都來源於數據結構,也能經過在運行中計算出來,例如全部ES6的主要數據結構有三個方法可以返回iterable對象。

  • entries() 返回一個可遍歷的entries
  • keys() 返回一個可遍歷的 entries 的keys。
  • values() 返回一個可遍歷的 entries 的values。

若是for...of不能遍歷怎麼辦

那就數據結構(數據源)去實現iterable就能夠了。

用通俗的話說就是,你若是要遍歷一個對象的話,有一下幾個步驟:

  1. 對象若是沒實現Symbol.iterator那就去實現
  2. 對象的Symbol.iterator函數要返回一個iterator
  3. iterator返回一個對象,對象中至少要包含一個next方法來獲取
  4. next方法返回兩個值valuedone

如今說說怎麼使object變爲可迭代的

上面咱們已經鋪墊了這麼多了,咱們說了javascript中object是不能被迭代了,由於沒有實現iterable,如今讓咱們來實踐一下讓object變的可迭代。

第一步: 先嚐試着使用for...of遍歷object

下面這樣寫確定是不行的

const obj = {
    name: 'helios',
    age: 23
}

for (let it of obj) {
    console.log(it)
}
// TypeError: obj is not iterable
複製代碼

第二步: 讓object實現iterable接口

const obj = {
    name: 'helios',
    age: 23,
    [Symbol.iterator]: function() {
        let age = 23;
        const iterator = {
            next() {
                return {
                    value: age,
                    done: age++ > 24
                }
            }
        }
        return iterator
    }
}

複製代碼

若是iterableiterable是一個對象的話,上面的代碼能夠簡化爲:

function iterOver() {
    let age = 23;
    const iterable = {
        [Symbol.iterator]() {return this},
        next() {
            return {
                value: age,
                done: age++ > 24
            }
        }
    }

    return iterable
}

for (let i of iterOver()) {
    console.log(i)
}
複製代碼

如今生成器(generator)能夠出場了

咱們若是每次想把一個不能迭代的對象變爲可迭代的對象,在實現Symbol.iterator的時候,每次都要寫返回一個對象,對象裏面有對應的next方法,next方法必須返回valua和done兩個值。

這樣寫的話每次都會很繁,好在ES6提供了generator(生成器)能生成迭代器,咱們來看簡化後的代碼:

const obj = {
    name: 'helios',
    age: 23,
    [Symbol.iterator]: function* () {
        while (this.age <= 24) yield this.age++
    }
}

for (let it of obj) {
    console.log(it)
}

複製代碼

讓object可迭代真的有意義麼

知乎的這個回答是頗有水平的了:爲何es6裏的object不可迭代?

在stackoverflow中也有很高質量的回答:Why are Objects not Iterable in JavaScript?

在上面的回答中從技術方面說了爲何Object不能迭代(沒有實現iterable),還說了以什麼樣的方式去遍歷Object是個難題,因此把如何迭代的方式去留給了開發者。

可是仍是要思考的一個問題就是:咱們真有必要去迭代對象字面量麼?

想一下咱們要迭代對象字面量的什麼呢?是keys仍是values亦或者是entries,這三種方式在ES6提供的新的數據類型map裏面都有呀,徹底是能夠代替object的。在這裏不說objectmap的區別,只是說說在ES6之後咱們想把兩個事物關聯起來的時候,不必定要非得是用對象字面量了,map支持的更好一下。

對於何時用對象字面量(object)何時使用map咱們能夠作一下總結:

  • 對象字面量(object)應該是靜態的,也就是說咱們應該已經知道了裏面有多少個,和對象的屬性有什麼

  • 使用對象字面量(object)的通常場景有:

    • 不須要去遍歷對象字面量(object)的全部屬性的時候
    • 咱們知道了裏面有多少個屬性和對象的屬性是什麼的時候
    • 須要去JSON.stringifyJSON.parse時候
  • 其餘的狀況用map,其餘的狀況包括:

    • key不是字符串或者symbol的時候
    • 須要去遍歷的時候
    • 要獲得長度的時候
    • 遍歷的時候對順序有要求的(對象字面量(object)可能不是按照你寫的順序)

也並不說是map就確定比對象字面量(object)好,map也有以下的缺點:

  • 不能使用對象解構
  • 不能JSON.stringify/JSON.parse

參考

相關文章
相關標籤/搜索