zoukankan      html  css  js  c++  java
  • 理解js中的作用域,作用域链以及闭包

    作用域
    变量作用域的类型:全局变量和局部变量
    全局作用域
    对于最外层函数定义的变量拥有全局作用域,即对任何内部函数来说,都是可以访问的

    <script>
    var outerVar = "outer";
    function fn(){
    console.log(outerVar);
    }
    fn();//result:outer
    </script>

    局部作用域
    和全局用域相反,局部作用域一般只在固定的代码片段内可访问到,对于函数外部是无法访问的

    <script>
    function fn(){
    var innerVar = "inner";
    }
    fn();
    console.log(innerVar);// ReferenceError: innerVar is not defined
    </script>


    注意
    需要注意的是,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

    <script>
    function fn(){
    innerVar = "inner";
    }
    fn();
    console.log(innerVar);// result:inner
    </script>

    作用域链
    我的理解就是,根据在内部函数可以访问外部函数变量的这种机制,用链式查找哪些数据能被内部函数访问
    执行环境
    每个函数在运行时都会产生一个执行环境。js为每个执行环境关联了一个变量对象。环境中定义的所有变量和函数都保存在这个对象中
    作用域链理解

    <script>
    var scope = "global"; 
    function fn1(){
    return scope; 
    }
    function fn2(){
    return scope;
    }
    fn1();
    fn2();
    </script>

    当某个函数第一次被调用时,就会创建一个执行环境(execution context)以及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([scope])。然后使用this,arguments(arguments在全局环境中不存在)和其他命名参数的值来初始化函数的活动对象(activation object)。当前执行环境的变量对象始终在作用域链的第0位。
    以上面的代码为例,当第一次调用fn1()时的作用域链如下图所示:

     可以看到fn1活动对象里并没有scope变量,于是沿着作用域链(scope chain)向后寻找,结果在全局变量对象里找到了scope,所以就返回全局变量对象里的scope值。

    闭包

    闭包有两个作用: 
    第一个就是可以读取自身函数外部的变量(沿着作用域链寻找) 
    第二个就是让这些外部变量始终保存在内存中 

    关于第二点,来看一下以下的代码:

    <script>
          function outer(){
             var result = new Array();
             for(var i = 0; i < 2; i++){//注:i是outer()的局部变量
                result[i] = function(){
                   return i;
                }
             }
             return result;//返回一个函数对象数组
             //这个时候会初始化result.length个关于内部函数的作用域链
          }
          var fn = outer();
          console.log(fn[0]());//result:2
          console.log(fn[1]());//result:2
       </script>

     返回结果很出乎意料吧,你肯定以为依次返回0,1,但事实并非如此 
    来看一下调用fn[0]()的作用域链图: 

    可以看到result[0]函数的活动对象里并没有定义i这个变量,于是沿着作用域链去找i变量,结果在父函数outer的活动对象里找到变量i(值为2),而这个变量i是父函数执行结束后将最终值保存在内存里的结果。 
    由此也可以得出,js函数内的变量值不是在编译的时候就确定的,而是等在运行时期再去寻找的。

    那怎么才能让result数组函数返回我们所期望的值呢? 

    <script>
          function outer(){
             var result = new Array();
             for(var i = 0; i < 2; i++){
                //定义一个带参函数
                function arg(num){
                   function innerarg(){
                      return num;
                   }
                   return innerarg;
                }
                //把i当成参数传进去
                result[i] = arg(i);
             }
             return result;
          }
          var fn = outer();
          console.log(fn[0]());
          console.log(fn[1]());
       </script>

    由上图可知,当调用innerarg()时,它会沿作用域链找到父函数arg()活动对象里的arguments参数num=0. 
    上面代码中,函数arg在outer函数内预先被调用执行了,对于这种方法,js有一种简洁的写法

    function outer(){
             var result = new Array();
             for(var i = 0; i < 2; i++){
                //定义一个带参函数
                result[i] = function(num){
                   function innerarg(){
                      return num;
                   }
                   return innerarg;
                }(i);//预先执行函数写法
                //把i当成参数传进去
             }
             return result;
          }

    使用闭包的注意点

    1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

    2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。


  • 相关阅读:
    【POJ 3162】 Walking Race (树形DP-求树上最长路径问题,+单调队列)
    【POJ 2152】 Fire (树形DP)
    【POJ 1741】 Tree (树的点分治)
    【POJ 2486】 Apple Tree (树形DP)
    【HDU 3810】 Magina (01背包,优先队列优化,并查集)
    【SGU 390】Tickets (数位DP)
    【SPOJ 2319】 BIGSEQ
    【SPOJ 1182】 SORTBIT
    【HDU 5456】 Matches Puzzle Game (数位DP)
    【HDU 3652】 B-number (数位DP)
  • 原文地址:https://www.cnblogs.com/15fj/p/8416687.html
Copyright © 2011-2022 走看看