zoukankan      html  css  js  c++  java
  • [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/  

  • 相关阅读:
    IIS7中的几种身份鉴别方式(一)Basic身份验证
    IIS7中的几种身份鉴别方式(二)集成身份验证
    java集合
    SharePoint 2010中welcome page的设置细节
    SharePoint中使用Linq出现未将对象引用到实例化的解决方法
    SharePoint 2010中关于An error was encountered while retrieving the user profile的处理方式记录
    The Need for an Architectural Body of Knowledge
    The Softer Side of the Architect
    Event Receivers 学习小结
    使用SmtpClient发送带图片的邮件的代码实现
  • 原文地址:https://www.cnblogs.com/moye/p/Closure_Higher-order-function.html
Copyright © 2011-2022 走看看