1. 程式人生 > >NodeJS總結(四):yield、return與柯里化

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 !== -1) {
            items.push(value)
        }
    } while(value !== -1)
    //  進行求值
    let sum = 0
items.forEach(item => sum = sum + item) yield sum }

根據生成器函式的第二個特性,只要函式中還有yield未執行,那麼剩餘的程式碼就絕不會執行,所以上面的程式碼中,只要迴圈未完成,求值的程式碼就不會執行,再看相關的測試程式碼,如下:

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

上面的程式碼有一次空轉的過程,這是因為next方法的引數只可以作為上一個yield表示式的返回值,所以對所有的生成器函式而言,都無法獲取到第一次的next()方法的返回值,具體的執行過程如下:
1. curring.next()空轉時,執行到yield items.slice()暫停,請記住,此時還沒有返回值,而且value的賦值操作還沒有執行,請記住賦值語句的右側程式碼先執行
2. 繼續執行curring.next(1),返回值為1,進行賦值操作,並繼續剩餘的迴圈程式碼,直到遇到yield才終止本次執行;
3. 繼續執行第二步,直到方法結束為止;

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

{ value: 6, done: false }

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

//  進行求值
let sum = 0
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(1)
curring.next(2)
curring.next(3)

總結

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