zoukankan      html  css  js  c++  java
  • 关于闭包的学习

    首先说明下...闭包是js高级特性之一...但并非js独有...perl, python, php(5.3以上版本) 都是支持闭包的..

       


            官方解释: 所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分

            john resig解释: 闭包就是内部函数可以访问外部函数中所定义的变量,即使该函数已经执行结束。
         

           看..不愧是jquery大牛.. 一语中地.. 
        
           如果你还是不能明白上面那句话...那么我就换句话来说:
        
           在js中...执行一个函数A...当函数A执行完后...理论上来讲...改函数A内所有被定义的 临时变量都将被 当成可回收的垃圾等待垃圾回收....然而在这个过程..有一种临时变量是无法被垃圾回收的...当A函数中有一个内部函数a时.a函数内引用了A中定义的临时变量...并且a函数在A函数执行完后..仍然可以被外部访问到时...被a函数所引用的临时变量就无法被当成垃圾等待垃圾回收.. 而a函数可以被外部访问的同时..就生成了一个闭包...


           举个例子吧..也是比较经典的例子
    //函数A 执行完后 它将返回一个函数a
    function A(){
        //定义一个临时变量
        var x = 1;
        
        //返回一个内部函数a
        //执行时打印临时变量x
        return function a(){
            console.log( x );    
        };
    }

    //执行A  得到内部函数a
    //此时内部函数a被返回...它引用了临时变量x
    //理论上A执行后 x做为临时变量将被当成垃圾等待垃圾回收
    //但是由于内部函数a引用了x  所以此时就生成了一个闭包
    var a = A();

    //执行a 打印1
    a();    //1                   

        闭包并非定义函数时就生成的...而是在执行过程中 当a函数被当成一个返回值被返回时 才会生成一个闭包..

        闭包容易误解的地方:


        1。 闭包总是在匿名函数中生成的


              闭包并非都是在匿名函数中生成的..比如上一段代码中...被返回的函数有命名-a


       

        2。 闭包在定义时产生的...

              闭包并非是在定义时产生的...而是在内部函数可被外部访问到时才会产生...

        3。 闭包很强大..用的越多就越牛A(==!)


              不否认闭包很强大.....但是并非用的越多就是越好的...使用闭包..会造成调试困难..所以要习惯做标识..另外...使用闭包会涉及到 增长函数作用域的 造成内部函数访问全局变量变慢的问题...


       

        PHP中的闭包

       


        php-5.3 以上版本其中一个更新就是使php支持了简单的闭包
    <?php
    /**
     * 一个curry的加法函数
     * @param unknown_type $start    起始值
     * @return unknown    返回一个匿名函数
     */
    function add( $start = 0 ){
        $sum = $start;
        
        //该函数接受n个值..执行后返回值为n值和$sum的总和
        return function () use ( &$sum ){
            
            //获取所有参数
            $args = func_get_args();
            
            for( $i = 0; $i < count($args); $i++ ){
                $sum += (int)$args[$i];
            }
            
            return $sum;
        };
    }

    //初始化值为1
    $add = add( 1 );

    //在初始化值的基础上加上1 2 3
    $add( 1, 2, 3 );

    //再加一次3 输出
    echo $add( 3 );            //10
    ?>     这段代码的作用是 每调用一次add函数都会生成一个相应的$sum 每个函数执行后不冲突 可避免使用static变量  而且sum不会随函数执行结束而消失  从而实现函数柯里化


            闭包的使用


           

            1. 函数柯里化


            闭包在js中经常会被用过函数柯里化

            比如上面php的那段代码中 改成js则是:

    //add函数 返回一个匿名函数
    function add( start ){
        var sum = start || 0;
        
        //该函数接受n个参数 返回值为n个参数的和+sum的值
        return function(){
            for( var i = 0, j = arguments.length; i < j; i++ ){
                sum += Number( arguments[i] );    
            }
            
            return sum;
        }
    }


    var a = add( 1 );

    a( 1, 2, 3 );

    console.log( a( 3 ) );        
    玩个有意思的函数  这个是别人曾经给我出的一道题目 当时我也没想出来...(压根把tostring这方法给忘了.)
    题目需求要求可以这样调用(当时的需求只要求传一个参数)

    //获取curry后的函数
    var a = add( 1 );

    //调用多次相加
    a( 1, 2, 3 )( 1, 2, 3 )( 1, 2, 3 );

    //直接输出函数
    console.log( a );    //19

    //继续相加后输出
    console.log( a( 1, 2, 3 )( 1, 2, 3 ) );        //31
            
        实现如下
    //add函数 返回一个匿名函数
    function add( start ){
        var sum = start || 0;
        
        //该函数接受n个参数 返回值为函数本身
        //直接输出函数时 打印sum的值
        return function(){
            //参数相加
            for( var i = 0, j = arguments.length; i < j; i++ ){
                sum += Number( arguments[i] );    
            }
            
            //获取函数本身
            var func = arguments.callee;
            
            //重写函数tostring方法 用于打印函数
            func.toString = function(){
                return sum;    
            };
            
            //返回函数本身
            return func;
        }    
    }

            2。模拟对象中的私有属性和方法

            写之前先解释下 js非一门OO语言 它是一门基于对象的语言


            如 var i = 0;   则i是一个数值型对象 转成对象写法则是 var i = new Number(1); 前一种叫过直接量表示法 同JSON(js对象字面量,表示js中对象的直接量表示方法) 直接量表示的速度要比 new 快

           (1)模拟私有属性和私有方法


               

    //smarty模板引擎 模拟
    function Smarty(){
        //公有属性 可被外部直接访问
        //左标签
        this.leftLimiter = '{';
        
        //右标签
        this.rightLimiter = '}';
        
        
        //私有属性 不可被外部直接访问
        //缓存assign方法调用后的赋值
        var cacheData = {};
        
        //公用方法 assign        
        //准确来讲..它叫做一个特权方法 可访问内部私有属性的方法叫做特权方法
        //实例化smarty构造函数时  由于它是一个公用方法 可被外部访问  
        //并且引用了cacheData临时变量 所以cacheData不会垃圾回收 此时生成一个闭包
        this.assign = function( name, value ){
            //缓存赋值
            cacheData[name] = value;    
        }
        
        //私有方法 fetch  编译解析模板内容 返回结果 不输出
        //假设它是一个私有方法 不能被外部直接访问
        function fetch( tpl ){
            //do something
            return tpl;
        }
        
        //公用方法 输出
        this.display = function( tpl ){
            //调用内部私有方法 直接输出
            console.log( fetch( tpl ) );
        }
    }

    //实例化smarty
    var template = new Smarty();

    //设置左标签
    template.leftLimiter = '<{';

    //设置右标签
    template.rightLimiter = '}>';

    //赋值
    template.assign( 'name', 'jsyczhanghao' );

    //赋值
    template.assign( 'age', 23 );

    //输出最终编译结果
    template.display( document.getElementById( 'test' ).innerHTML );      
          (2)模拟私有静态方法(单例模式-Zend framework 模拟前端控制器 phper你懂的..)


    //模拟Zend framework 前端控制器
    //定义一个匿名函数 定义完立即执行(function( window ){
        //Zend_Controller主构造函数    //在js中无法设置私有的构造函数
        //所以必须将构造函数设置为 非公开 才可以不让外部调用的程序直接实例化构造函数 在公开对象中提供一个公开方法 间接去调用 
        var Zend_Controller = function(){
            //设置控制器的路径
            this.setControllerDirectory = function(){};
            
            //分发路由
            this.dispatch = function(){
                console.log( 1 );    
            };
        };
        
        //前端控制器的私有静态属性 外部不可直接访问
        //它为一个Zend_Controller的实例
        var intance;
        
        //公开类 前端控制器
        var Zend_Controller_Front = function(){};
        
        //获取实例 一个共有静态方法
        //可被外部调用的方法 生成闭包 临时变量instance和Zend_Controller不会消失
        Zend_Controller_Front.getInstance = function(){
                //返回如果已存在实例 则直接返回  
                //否则 先创建再返回
                return instance || ( instance = new Zend_Controller() );
        };
        
        //实际的js中习惯会把单例模式会这么写
        //将Zend_Controller_Front直接写成一个对象  getinstance自然就成了一个公用方法 可直接调用
        //window.Zend_Controller_Front = {
        //    getInstance: function(){
        //        return instance || ( instance = new Zend_Controller() );    
        //    }    
        //};
        
        window.Zend_Controller_Front = Zend_Controller_Front;
    })( this );

    var zend_instance = Zend_Controller_Front.getInstance();

    zend_instance.setControllerDirectory( '/root' );

    zend_instance.dispatch();       

            3。事件回调函数中的使用


       


           

    //更新元素内容 ajax
    //第一个参数为dom元素
    //第二个参数发送的url
    function updateElement( elem, url ){
        //jquery中ajax的get方法
        //在 #js的异步机制和大数据量的处理方案# 中有说到 
        //实际上在get方法过后...该函数已执行后 
        //get方法第2个参数的匿名函数 将会被丢到 UI队列的最后面等待合适的机会触发 
        //该机会就是ajax成功发送并且成功返回状态值时触发
        //由于匿名函数并非立即执行 且依赖于elem参数 所以elem不会被当垃圾进行回收  并在此生成一个闭包
        //必须等到 匿名函数成功执行后才会被释放..
        $.get( url, function( data ){
            //ajax发送成功后 将返回的值 写到元素中
            elem.innerHTML = data;
        });
    }    以上是闭包绝大部分会出现的场景

    #############################################################################################################


       来看个问题吧:针对 #js的异步机制和大数据量的处理方案# 中的一段代码段


        
    for( var i = 0; i < 10; i++ ){
        //为test0-test9绑定click事件
        document.getElementById( 'test' + i ).onclick = function(){
            //打印对应的i
            console.log( i );    
        };    
    }
       

        这段代码执行后 点击test0-test9并非象预期那样.. 依次打印出0-9 而是每一个元素点击后都打印了10

        造成的原因就是 绑定click事件时 回调函数并未执行  当回调函数执行时 i已经变成了10 所以打印的结果都会变成10

        解决方法:


        思路: 如果能找到一种方式可以将每一次的i都缓存起来 并且一直到click事件触发的时候 它都一直不会消失 不就完了么


        我们都知道 一个函数作用域内执行完后..作用域中的所有临时变量都会消失 但是有一种不让临时变量消失的方式就是使用闭包。。而上面讲闭包的使用场景时 其中有一条就是事件回调函数 当一个事件回调函数位于一个作用域内的时候...作用域执行外后 由于回调函数并未马上执行..而是等到相应事件触发时才执行...当回调函数依赖该作用域内的临时变量时...导致该作用域内部使用的临时变量无法马上被当垃圾回收(意味着该临时变量不会消失)


        目前我们拥有一个事件回调函数 要做的就是需要让这个事件回调函数位于一个函数作用域内


        代码:
    for( var i = 0; i < 10; i++ ){
        //为test0-test9绑定click事件
        function(){
            document.getElementById( 'test' + i ).onclick = function(){
                //打印对应的i
                console.log( i );    
            };            
        };
    }
        这样 事件绑定就位于一个匿名函数中了...但是这样肯定不行...因为函数都没有执行...函数内的代码肯定不会起作用....也就是说..这段代码能够正常执行 不报错..但是不会为每一个元素绑定一个事件..因为它的外部函数没有执行


        继续修改:
    for( var i = 0; i < 10; i++ ){
        //为test0-test9绑定click事件
        (function(){
            document.getElementById( 'test' + i ).onclick = function(){
                //打印对应的i
                console.log( i );    
            };            
        })();
    }
    恩 这次看起来差不多了....绑定事件的行为位于一个匿名函数中..并且匿名函数定义后立即执行....

    但是目前 绑定事件内的变量i并不是 匿名函数中所产生的临时变量  i是一个全局变量  i不会因为匿名函数的执行而一直保持 你所希望的值


    所以我们需要在匿名函数内定义一个临时变量 该临时变量的值和当前相应的i值相等即可  将i直接赋值给该临时变量就可以了..


    最终修改代码:


       

    for( var i = 0; i < 10; i++ ){
        //为test0-test9绑定click事件
        (function(){
            var j = i;
            document.getElementById( 'test' + j ).onclick = function(){
                //打印对应的i
                console.log( j );    
            };            
        })();
    }其实不一定要直接赋值 当一个参数传进去也行代码如下(执行结果一样..过程也没什么区别..只是写法不同)for( var i = 0; i < 10; i++ ){
        //为test0-test9绑定click事件
        (function( j ){
            document.getElementById( 'test' + j ).onclick = function(){
                //打印对应的i
                console.log( j );    
            };            
        })( i );
    }
    其实还有一种不使用闭包的方式...在事件的回调函数中直接引用 dom对象的一个属性即可 因为dom对象是一直存在的 而指向当前的dom对象使用this即可for( var i = 0; i < 10; i++ ){
        //为test0-test9绑定click事件
        var elem = document.getElementById( 'test' + i );
        
        elem.index = i;
        
        elem.onclick = function(){
                //打印对应的i
                console.log( this.index );
        };            
    }

  • 相关阅读:
    Golang 爬虫02
    Golang使用正则
    gin框架对接快递100 查询快递跟踪记录 Golang实现快递查询
    Jetbrains系列产品2019.3.4最新激活方法[持续更新]
    Linux下安装Fiddler
    Golang 爬虫01
    Github进行fork后如何与原仓库同步
    Pr 的导出视频
    Linux-平均负载指数
    Linux-进程管理命令
  • 原文地址:https://www.cnblogs.com/ranran/p/4103477.html
Copyright © 2011-2022 走看看