zoukankan      html  css  js  c++  java
  • JavaScript 工作必知(九)function 说起 闭包问题

    大纲

    • Function

    • Caller 返回函数调用者

    • Callee 调用自身

    • 作用域

    • 闭包

    function

            函数格式

     function getPrototyNames(o,/*optional*/ a)
            {
                a = a || [];
                for(var p in o)
                {
                    a.push(p);
                }
                return a;
            }
    

      

    caller

          func.caller 返回函数调用者

        

      function  callfunc()
            {
                if(callfunc.caller)
                {
                    alert(callfunc.caller.toString());
                }else
                {
                    alert("没有函数调用");
                }
            }
    
            function handleCaller()
            {
                callfunc();
            }
    
            handleCaller();//返回 handler
            callfunc();//没有函数调用,返回null,执行了《没有函数调用》
    

     

    callee

                匿名方法递归调用

     alert( (function (x) {
               if (x <= 1) return 1;
               return x * arguments.callee(x - 1);
           }(10)));//362800
    

      

    scope

           作用域大家都不陌生,今天就来说说闭包问题,深刻吃透闭包问题。

    <script>
    
            var global = "global scope";//这个是全局的作用域
            function scope()
            {
                var scope = "local scope";//这个是局部的作用域
                return scope;//返回的scope,是局部的变量
            }
          
        </script>
    

      1、:定义的全局变量也能在函数内部访问。当定义的局部变量和全局变量名字相同时,局部变量的就会隐藏全局变量,不会破坏全局变量的值。

     var scope = "global scope";
            function f()
            {
                var scope = "local scope";
                return scope;
            }
            alert(f());//local scope
            alert(scope);//global scope;
    

      上面确实是很容易理解,对吧。

        2、 全局变量可以不用var声明,但是局部变量必须使用var声明,如果局部变量不使用var声明,编译器会默认这个是个全局变量。

      
      scope = "global scope";
            function f()
            {
                 scope = "local scope";
                return scope;
            }
            alert(f());//local scope
            alert(scope);//local scope
    
    
    

      但是全局变量不使用var声明,也仅限非严格模式,如果使用严格模式的话,会报错误

    <script>
            "use strict";
            scope = "global scope";
            function f()
            {
                 scope = "local scope";
                return scope;
            }
            alert(f());//local scope
            alert(scope);//local scope
        </script>
    

      

    所以建议大家声明变量时,千万不要省略var,可以避免不必要的麻烦。

        3、 声明提前,也是可以滴。什么叫什么提前。

           

     <script>
            "use strict";
            scope;
            console.log(scope);
            var scope = "global scope";
            console.log(scope);
           
        </script>
    

      

    这个可能大家看出第一个打印undefined ,是呀还没有给他赋值, 下面赋值可定打印global scope了。

     这样理解并没有错,但是为什么会这样的呢,一个变量不是应该先定义才可以使用的吗?

      这里给大家说下作用域链,JavaScript是基于词法作用域的语言。

           1、作用域链是一个对象或者链表,这组代码中定义了这段代码"作用域中“的变量。当JavaScript需要查找变量scope时,就会从链中的第一个对象开发查找,如果第一个对象为scope,则会直接返回这个对象的值,如果不存在继续第二对象开始查找,直到找到。如果在作用域链上未查到该变量,则会抛出一个错误。

           我们可以这个作用链可以这样表示:查找 scope->window(全局对象)很显然后面是有定义scope的。但是并没有做赋值操作,后面才做赋值操作,所以此时值为undefined.

       4、这个比较具有迷惑性了,大家猜想下打印的值是什么?

         

     <script>
            "use strict";
          
            var scope = "global scope";
            function f() {
                console.log(scope);
                var scope = "local scope";
                console.log(scope);
            }
            f();
           
           
        </script>
    

      

       看到这段代码:如果你粗心大意的话,很有可能会写出错误的答案:

        1、global scope

        2、local scope

         分析: 声明了全局变量,在函数体中时,第一个表示全局变量,所以打印global,第二定义了局部的变量,覆盖掉了全局的scope,所以打印local scope。

                   这样的分析在C# java ,完全正确。但是这里分析确实错误的。

       这说明这个问题前,我们先看一个问题。

        这句话很重要:全局变量在程序中始终都是有定义的。局部变量在声明它的函数体以及其所嵌套的函数内始终是定义的。

         如果你是从事高级语言工作开始接触JavaScript 有点不适应其作用域的定义。我也是这样的。我们来看一个例子:

        

     <script>
            var g = "global scope";
            function f()
            {
                for(var i=0;i<3;i++)
                {
                   
                    for(var j=1;j<2;j++)
                    {
                       ;
                    }
                    console.log(j);
                }
                console.log(i);
            }
            console.log(g);
            f();
        </script>
    

       打印的结果是什么?

                大家看到{} 表示语句块,语句块在一块作用域,所以大家可能猜想,j、i值已经在内存释放掉了,所以结果肯定是undefined.

                真实的结果可能令你失望,

               

              结果为什么会是这样,我开始的表情和你一样。

         这时查看我让你记住的那句话。。。全局变量在程序中始终都是有定义的。局部变量在声明它的函数体以及其所嵌套的函数内始终是定义的。

         确切的说函数的参数 也是属于局部变量的范畴。这句话也很重要!!!

         那句话大概意思说,只要是在函数内部定义的变量,在整个函数内都是有效的。所以结果也就不难理解了。在回过头看我们的那个问题,你理解了吗?

        作用链还有以下定义:

        1、作用链是有一个全局对象组成。

                 2、在不包含嵌套的函数体内,作用链上有两个对象,第一个定义了函数参数和局部变量的对象,第二个是全局对象。

               3、在一个嵌套的函数体内,作用链上至少包含三个对象。

        当定以一个函数的时候,就会保存一个作用域链。

        当调用这个函数时,它就会创建一个新的对象来存储它的局部变量,并将这个对象添加到保存的作用链上。同时创建一个新的更长的表示函数调用的作用链。

        对于嵌套的函数,当调用外部函数时,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用链都是不同的。内部函数在每次定义的时候都有微妙的差别,每次调用的外部函数时,内部函数的代码都是相同的,而且关联的代码的作用域也是不同的。

    闭包

                  搞了这么久终于要讲了,但是再将之前我们在来分析下作用域。

                  

    <script>
            var nameg="global"
            var g = function f() {
    
                console.log(name);
                function demo0()
                {
                    console.log("demo0="+name);
                }
                var name = "1";
                function demo1() {
                    var name = "2";
                    console.log("demo1=" + name);
                }
                function demo2() {
                    console.log("demo2=" + nameg);
                }
                demo0();
                demo1();
                demo2();
            };
            g();
            
        </script>
    

      我们按照作用链来分析:

           调用demo0, demo0()->查找name,未找到->f()查找,返回

           调用demo1,demo1()->查找name,找到,返回

           调用demo2,demo2()->查找nameg,未找到->f()查找nameg,未找到->window查找nameg找到,返回。

         

          看这个例子:

         

    <script>
            function f()
            {
                var count = 0;
                return { counter:function() {
                    return count++;
                },reset:
                function()
                {
                    return count = 0;
                }
            }
               
            }
            var d = f();
            var c = f();
          console.log("d第一次调用:"+  d.counter());//0
          console.log("c第一次调用:"+  c.counter());//0 互不影响
          console.log("d第一次调用:"+  d.reset());//0
          console.log("c第二次调调用"+ c.counter());//1
           
        </script>
    

      这个例子上大家可以看到,我做了一个计数和置零的操作。

          创建了两个f的对象实例d c,各有自己的作用域链,所以其值互不影响。当c第二次调用时,count值被保存了,因为c对象并未被销毁。明白这个例子后,后面的例子才比较好懂。

        

         这个过程,大家应该十分明了了。那么现在我们来看闭包问题。我设置四个按钮,点击每个按钮就返回响应的名字。

        

    <body>
        <script>
            function btnInit()
            {
                for(var i=1;i<5;i++)
                {
                    var btn = document.getElementById("btn" + i);
                    btn.addEventListener("click",function() {
    
                        alert("btn" + i);
                    });
                }
            }
           window.onload= btnInit;
           
        </script>
        <div>
            <button id="btn1">Btn1</button>
            <button id="btn2">Btn2</button>
            <button id="btn3">Btn3</button>
            <button id="btn4">Btn4</button>
         </div>
    </body>
    

      点击运行,结果却是都是btn5;

         我们用刚才的分析在坐下,首先要调用匿名函数->查找i,未找到->btnInit(),找到i在for循环中。找到。我们知道只有函数调用结束才释放,for中的i总是可见的,所以保留了最后的i值。那么如何解决呢。

         解决i值在函数中不是总是可见的,那么我们就要使用函数的嵌套了,然后把i值传进去。

        

    function btnInit()
            {
                for(var i=1;i<5;i++)
                {
                    (function (data_i) {
    
                        var btn = document.getElementById("btn" + data_i);
                        btn.addEventListener("click", function () {
    
                            alert("btn" + data_i);
                        });
    
                    }(i));
                   
                }
            }
    

      看修改后的代码,首先执行第一次for,创建了一个对象,我们首先是执行匿名函数->data_i,未找到->function(data_i)找到,然后再次执行for有创建一个对象,闭包规则说的互不影响。所以能得出正确的结果。

  • 相关阅读:
    Win8杂谈
    ipad还能横行霸道多久
    C++异步编程 for VS2011(二)
    C++异步编程 for VS2011(一)
    互联网催生的新的商业模式
    微信小程序用户评分实例
    即时通讯小程序实现代码
    CDN(内容分发网络)技术原理 枯木
    RHEL6.3下Zabbix监控实践之Zabbix的安装 枯木
    Firefox浏览器完美运行Firefox OS 枯木
  • 原文地址:https://www.cnblogs.com/fandong90/p/5554237.html
Copyright © 2011-2022 走看看