天天看點

NodeJS總結(四):yield、return與柯裡化

對于ES6的生成器函數總結有四點:

1. yield必須放置在*函數中;

2. 每次執行到yield時都會暫停函數中剩餘代碼的執行;

3. *函數必須通過函數調用的方式(new方式會報錯)才能産生自身的執行個體,并且每個執行個體都互相獨立;

4. 一個生成器函數一旦疊代完成,則再也無法還原,一直停留在最後一個位置;

尤其是第二點,是非常強大的功能,暫停代碼執行,以前隻有在浏覽器環境中,alert、comfirm等系統内置函數才具有類似的能力,是以如果熟悉多線程的語言,你會找到類似的感覺,于是也有人說,有了yield,NodeJS就有協程的能力,完全可以處理多個需要協作的任務。

也是因為第二點,生成器函數也具有惰性求值的特性,針對這一特性,我們可以很容易寫出科裡化函數進行惰性求值,如下:

function *curr() {
    let items = [],
        value
    do {
        //  如果不需要傳回值,可以直接寫成value = yield
        //  每次傳回現有的數組元素
        value = yield items.slice()
        //  當等于-1時終止
        if(value !== -) {
            items.push(value)
        }
    } while(value !== -)
    //  進行求值
    let sum = 
    items.forEach(item => sum = sum + item)
    yield sum 
}
           

根據生成器函數的第二個特性,隻要函數中還有yield未執行,那麼剩餘的代碼就絕不會執行,是以上面的代碼中,隻要循環未完成,求值的代碼就不會執行,再看相關的測試代碼,如下:

let curring = curr()
//  必須要空轉一次,代碼啟動柯裡化
curring.next()
curring.next()
curring.next()
curring.next()
//  啟動求值過程
console.log(curring.next(-))
           

上面的代碼有一次空轉的過程,這是因為next方法的參數隻可以作為上一個yield表達式的傳回值,是以對所有的生成器函數而言,都無法擷取到第一次的next()方法的傳回值,具體的執行過程如下:

1. curring.next()空轉時,執行到yield items.slice()暫停,請記住,此時還沒有傳回值,而且value的指派操作還沒有執行,請記住指派語句的右側代碼先執行;

2. 繼續執行curring.next(1),傳回值為1,進行指派操作,并繼續剩餘的循環代碼,直到遇到yield才終止本次執行;

3. 繼續執行第二步,直到方法結束為止;

在上面的執行結果中,我們還發現輸出結果有些出乎意料,如下:

{ value: , done: false }
           

done竟然是false,這說明方法還沒有執行結束,必須還要執行一次curring.next,才能終結方法,done才能變為true,這實在是太低效了,又空轉了一次,那怎麼改進呢?很簡單,隻需要将最後的yield改為return即可,如下:

//  進行求值
let sum = 
items.forEach(item => sum = sum + item)
//  隻有使用return才能終結方法
return sum 
           

這是yield與return的差別:

1. yield僅代表本次疊代完成,并且還必有下一次疊代;

2. return則代表生成器函數完成;

最後,為了減少柯裡化代碼中不必要的一次空轉疊代,我們用一種掩耳盜鈴的方式封裝構造函數,為什麼是掩耳盜鈴,一看就明白:

//  對生成器函數進行封裝
function wrapper(fn) {
    //  這裡的...運算符将參數轉換為數組
    return function(...args) {
        //  除了使用析構與擴充運算,還可以使用apply函數,如下
        //let generator = fn.apply(null, args)
        //  這裡的...運算符是逆向運算,将數組又轉換為參數清單
        let generator =  fn(...args)
        //  将空轉放到這裡
        generator.next()
        return generator;
    }
}
//  現在應用代碼可以簡化了
let curring = wrapper(curr)()
curring.next()
curring.next()
curring.next()
           

總結

利用生成器函數可以進行惰性求值,但無法擷取到第一次next函數傳入的值,而且隻要執行了yield的傳回操作,那麼構造函數一定沒有執行完成,除非遇到了顯式的return語句。

繼續閱讀