• [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/  

  • 相关阅读:
    第009题 智猜年龄——问经理三女儿年龄各多少
    第008题 求最大值——10个1加乘的最大数字
    第007题 天平称物——最少砝码称出最多质量
    第006题 天平找次——至少称几次找到次品
    第005题 青蛙过河——十只青蛙如何顺利过去
    第004题 过河问题——如何过河用时最短
    第003题 过河问题——三对老虎如何安全过河
    第002题 打水问题——9升和4L的桶如何打6升水
    基于博弈论分析在线教育网站和慕课的产生
    C++左右括号匹配问题(并给出括号的位置 并且允许非括号字符插入)修改版
  • 原文地址:https://www.cnblogs.com/moye/p/Closure_Higher-order-function.html
Copyright © 2020-2023  润新知