【JS基礎】ES6語法

iterator迭代器

在ES6以前遍歷數組的方法有如下四種:數組

// 第一種
for(var i = 0; i < array.length; i++){
    console.log(array[i])
}
// 第二種
array.forEach(function(item,index){
    console.log(item)
})
// 第三種
for(var index in array){
    console.log(array[index])
}
// 第四種
for(var value of array){
    console.log(value)
}

在上面的遍歷方式中,第二種方式有一種小缺陷,就是不能使用break語句中斷執行,也不能使用return語句返回到外層函數。它會一直遍歷完數組的全部元素。第三種方式是一個更糟糕的方式,在遍歷過程當中,賦值給index的不是number類型,而是字符串類型,for-in循環除了遍歷數組外,還會遍歷自定義屬性,甚至是遍歷出原型鏈上的屬性。而且for-in的遍歷順序不能獲得保障。數據結構

第四種方法是ES6中新增的遍歷數組的方法,它能夠正確響應break、continuereturn語句,for-in語句除了能遍歷數組外,還能遍歷類數組對象,如DOM的NodeList對象,arguments對象,也能遍歷字符串、Set對象、Map對象。函數

for-of循環語句可以遍歷各類集合的。是由於這些對象都有一個迭代器的方法,迭代器(Iterator)是一種接口,爲各類不一樣的數據結構提供統一的訪問機制,任何數據結構只要部署了iterator接口,就能夠完成遍歷操做。好比下面這種狀況:this

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}
                }
            }
        }
    }
}
for(var value of obj){
    console.log(value)    // 輸出   hello  world
}

迭代器也能夠直接使用Array默認的iterator。prototype

// 也能夠直接使用Array的迭代器
let newObj = {
    0:'a',
    1:'b',
    2:'c',
    [Symbol.iterator]:Array.prototype[Symbol.iterator]
}
for(var value of newObj){
    console.log(value)   // 輸出 a b c
}

for-of循環語句實際上是調用遍歷對象的[Symbol.iterator]方法,該方法返回一個iterator,裏面有一個next方法,for循環會不斷調用這個iterator.next方法來獲取下一個值,直到返回值中的done屬性爲ture時結束循環。除了添加iterator外還能夠使用yield實現循環:線程

let obj = {
    [Symbol.iterator]: function* (){
        for(var i = 0; i < 100; i++){
            yield i
        }
    }
}
for(var value of obj){
    console.log(value)
}

iterator的遍歷過程

  1. 建立一個指針對象,指向當前數據結構的起始位置,也就是說,遍歷器對象的本質就是一個指針對象。
  2. 第一次調用指針對象的next方法,能夠將指針指向數據結構的第一個成員。
  3. 不斷調用指針對象的next方法,直到它指向數據結構的結束位置。

當使用for-of循環遍歷某個數據結構時,該循環會自動去尋找iterator接口。只要一個對象含有iterator接口,那麼該對象就能夠被遍歷。指針

使用iterator的場景

  • 解構賦值
let arr = [1,2,3,5]
let [first,...second] = arr
  • 擴展運算符
var str = 'hello'
[...str]    //  ['h','e','l','l','o']
  • yield*

yield*後面跟的是一個可遍歷的結構,它就會調用該結構的遍歷器接口。code

let generator = function* (){
    yield* [2,3,4]
}
  • 其餘場合

因爲數組的遍歷會調用遍歷器接口,因此任何接受數組做爲參數的場合,其實都調用了遍歷器接口。好比for-of、Promise.all()、Promise.race()對象

生成器Generators

生成器函數與普通函數不一樣的點:普通函數使用function關鍵字聲明,生成器函數使用function*聲明,在生成器函數內部,有相似return語法的yeild關鍵字,與return不一樣的是,生成器函數能夠yeild屢次,在函數執行過程當中,遇到yield表達式當即暫停,後續可恢復執行狀態。接口

function* question(name){
    yield "你好" + name + '!'
    yield "但願你能喜歡"
    yield "下次再見"
    return '拜拜'
}
var iter = question('小明')
iter.next()    // {value: "你好小明!", done: false}
iter.next()   // {value: "但願你能喜歡", done: false}
iter.next()    // {value: "下次再見", done: false}
iter.next()    // {value: '拜拜', done: true}

generator函數在調用後,並不會當即執行,而是返回一個iterator對象,每次生成器執行到yield語句後,生成器函數的堆棧結構(本地變量、參數、臨時值、生成器內部當前的執行位置)被移除堆棧。然而,生成器對象保留了對這個堆棧結構的引用,因此稍後調用.next()方法能夠從新激活堆棧結構而且繼續執行。不過生成器不是線程,它仍然處於JS單線程裏。

若是運行到後面沒有yield表達式,就會一直運行到函數結束,直到return語句爲止,並將return表達式的值賦值給最後返回對象的value值,若是沒有return語句,則返回對象的value值爲undefined。
generator生成器是iterator的生成函數,執行generator函數,返回的就是iterator迭代器。

next方法的參數

function* gen(){
    for(let i = 0; true; i++){
        let reset = yield i
        if(reset){
            i = -1
        }
    }
}
let g = gen()
g.next()     // {value:0,done:false}
g.next(true)    // {value:0;done:false}

yield表達式自己沒有返回值,或者說老是返回undefined,next方法能夠帶一個參數,該參數就會被做爲上一個yeild表達式的值。generator從暫停狀態到恢復運行,它的上下文狀態是不變的,經過next方法傳入參數,能夠在generator函數開始運行以後,繼續向函數內部注入值。

throw方法

function* gen(){
    try {
        yield '123'
    }catch(e){
        console.log('內部捕獲',e)
    }
}
let g = gen()
g.next()
try {
    g.throw('a')
    g.throw('b')
}catch(e){
    console.log('外部捕獲','b')
}
// 內部捕獲 a
// 外部捕獲 b

上述代碼中,遍歷器對象g連續拋出錯誤,第一個錯誤被generator函數體內的catch語句捕獲,第二次拋出錯誤時,因爲catch語句已經執行過了,不會捕獲該錯誤,因此這個錯誤由函數體外的catch捕獲。若是函數內部沒有try-catch語句,那麼throw方法拋出的錯誤將被外部的try-catch捕獲。遍歷器對象g調用throw方法後,拋出的異常被generator函數體捕獲之後,會附帶執行下一條yield語句。一旦generator執行過程拋出錯誤,且沒有被內部捕獲,generator函數就不會執行下去了。若是此後再調用next方法,將返回對象{value:undefined,done:true}

return方法

function* gen(){
    yield 1;
    yield 2;
    yiled 3;
}
var g = gen()
g.next()      // {value:1,done:false}
g.return('end')  // {value:'end',done:true}
g.next()     // {value:undefined,done:true}

若是generator函數內部有try-finally代碼塊,那麼return方法會推遲到finally代碼塊執行完畢再執行。

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }

next()、throw()、return()這三個方法都是讓generator函數恢復執行,而且使用不一樣的語句替換yield表達式,next()是將yeild表達式替換成一個值,throw()是將yeild替換成一個throw語句,return()是將yield語句替換成一個return語句。

yield*

在generator函數內部再調用另外一個generator函數,默認狀況下是沒有效果的,這個時候就須要用到yield*表達式,用來在一個generator函數內執行另外一個generator函數。

相關文章
相關標籤/搜索