[Node.js] 閉包和高階函數


原文地址:http://www.moye.me/2014/12/29/closure_higher-order-function/ 

 

引子

最近發現一個問題:一部分寫JS的人,其實對於函數式編程的概念並不是太了解。如下的代碼片斷,常常讓他們覺得不可思議:

OAuth2Server.prototype.authCodeGrant = function (check) {
  var self = this;

  return function (req, res, next) {
    new AuthCodeGrant(self, req, res, next, check);
  };
};

上述片斷來自開源項目node-oauth2-server,這個authCodeGrant原型函數涉及到JS編程中經常用到的兩個概念:閉包 和 高階函數(check變量在這個函數中被閉包,authCodeGrant能返回函數,因此是一個高階函數。

閉包

閉包就是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。

如何來理解這個自由變量呢?

自由變量是指在函數中使用的,但既不是函數參數也不是函數的局部變量的變量

什么樣的變量是自由變量呢?如下片斷中的freeVar對inner()來說就是個自由變量:

function wrapper() {
    var freeVar = 42;
    function inner() {
        return 2 * freeVar;
    }
    return inner;
}

自由變量在閉包生成之前,並不是函數的一部分。在函數被調用時,閉包才會形成,函數將這個自由變量納入自己的作用域,也就是說,自由變量從此與定義它的容器無關,以函數被調用那一刻為時間點,成為函數Context中的成員。

來看一個困惑前端的示例,循環添加事件:

    <button>第1條記錄</button>
    <button>第2條記錄</button>
    <button>第3條記錄</button>
    <button>第4條記錄</button>
    <button>第5條記錄</button>
    <button>第6條記錄</button>
<script type="text/javascript">  
    var buttonst_obj = document.getElementsByTagName("button"); 
    for (var i = 0, len = buttonst_obj.length; i < len; i++) {
        buttonst_obj[i].onclick = function() {   
            alert(i);   
        };
    }
</script>

上述片斷的結果是:每個Button彈出的都是6。因為沒有形成有效的閉包,因為閉包是有延遲求值特性的,所以在函數得到執行時,i === 6。 

如果我們將它改成這樣,i 做為外層函數的參數而被內層函數閉包,結果也是我們想要的:

var buttonst_obj = document.getElementsByTagName("button");
for (var i = 0, len = buttonst_obj.length; i < len; i++) {
    buttonst_obj[i].onclick = clickEvent(i);
}
function clickEvent(i){
    return function () {
        console.log(i);
    }
}

Why? 因為這個clickEvent(i) 高階函數,它將 i 作為自由變量(注意:i 並不是內函數的參數,也不是內函數的一部分)傳遞,在 click 時閉包已經形成並被傳遞。

閉包的作用域

雖然自由變量從閉包時起 “將和這個函數一同存在,即使已經離開了創造它的環境也不例外”,但我們必須搞清楚,閉包產生時的作用域,看個例子:

var scope = 'global';
function echo(){
    console.log(scope);
}
function wrapper(){
    var scope = 'inner';
    echo();
}
echo();    // 輸出global
wrapper(); // 輸出global  

為什么在wrapper內部的echo()調用,會輸出全局scope?因為:echo定義的位置,只能閉包到全局的scope,它的外層作用域就是全局空間,即便是延遲求值也如此。

把這段代碼稍加改造,就能看得更清楚:

var scope = 'global';
function echo(){
    console.log(scope);
}
function wrapper(){
    var scope = 'inner';
    function echo(){
        console.log(scope);
    }
    echo();
}
echo();    //輸出global
wrapper(); //輸出inner

閉包的自由變量來自何處,和它的外層作用域(被定義的位置)也是有關系的。

高階函數

上述循環事件片斷中的 clickEvent(i) 即為一個高階函數。

高階函數滿足:要么接受一個或多個函數作為輸入;要么輸出一個函數

為什么會用到高階函數?粗糙的說,就是為了閉包。

接受函數作為輸入的高階函數

這種高階函數可作為一種模式的構造器,比如:我有快速排序/堆排序/希爾排序 等若干個排序函數,那么我只需要提供一個高階函數,就能生成基於這若干種排序函數的排序器:

//排序器
var sortingGenerator = function(sortFunc){
    return function(args){
        var arguments = [].slice.call(args);
        return sortFunc(arguments);
    }
};
//引入排序算法
var heapSort = require('heapSort');
var heapSorter = sortingGenerator(heapSort);
//使用算法
heapSorter(4, 22, 44, 66, 77);

當然,其實這個高階函數也輸出了函數

輸出函數的高階函數

和上例一樣,高階函數輸出一個函數也很好理解:先閉包自由變量,根據它在將來調用時產生不一樣的輸出。

比如,我需要一個函數,既可以算平方,也可以算立方,最好什么方都能算,這時我就需要一個如下片斷的高階函數:

//計算m的N次方
var powerOfN = function(n){
    return function(m){
        var res = 1;
        for(var i = 0; i < n; ++i){
            res *= m;
        }
        return res;
    } ;
};
//按需生成
var powerOf2 = powerOfN(2);
var powerOf3 = powerOfN(3);
//調用傳參
console.log(powerOf2(3));
console.log(powerOf3(2)); 

小結

通過閉包和高階函數的組合運用,我們可以提煉出這樣一種編程模式:通過分離>=2次的參數傳遞,以最少的代碼實現動態的算法生成器。

 

更多文章請移步我的blog新地址: http://www.moye.me/  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM