• Javascript的作用域、作用域链以及闭包


    一、javascript中的作用域

       ①全局变量-函数体外部进行声明

       ②局部变量-函数体内部进行声明

    1)函数级作用域

    javascript语言中局部变量不同于C#Java等高级语言,在这些高级语言内部,采用的块级作用域中会声明新的变量,这些变量不会影响到外部作用域。

    javascript则采用的是函数级作用域,也就是说js创建作用域的单位是函数。

    例如:

    C#当中我们写如下代码:

    static void Main(string[] args)
    {
         for (var x = 1; x < 10; x++)
         {
               Console.WriteLine(x.ToString());
         }
         Console.WriteLine(x.ToString());
    }

    上面代码会出现如下的编译错误:

    The name 'x' does not exist in the current context        

    同样在javascript当中写如下代码:

    <script>
        function main() {
            for (var x = 1; x < 10; x++) {
                 console.log(x.toString());
             }
             console.log(x.toString());
         }
         main();
    </script>

    输出结果如下:

    [Web浏览器] "1"        

    [Web浏览器] "2"        

    [Web浏览器] "3"        

    [Web浏览器] "4"        

    [Web浏览器] "5"        

    [Web浏览器] "6"        

    [Web浏览器] "7"        

    [Web浏览器] "8"        

    [Web浏览器] "9"        

    [Web浏览器] "10"        

    这就说明了,“块级作用域”和“函数级作用域”的区别,块级作用域当离开作用域后,外部就不能用了,就像上面的C#例子,"x"离开for循环后就不能用了,但是javascript中不一样,它还可以进行访问。

     

     2)变量提升

    再看javascript中作用域的一个特性,例子如下:

    function func(){
        console.log(x);
        var x = 1;
        console.log(x);
    }        
    func();

    输出结果:

    [Web浏览器] "undefined"        

    [Web浏览器] "1"                 

     

    如上面的结果:有意思的是,为什么第一个输出是“undefined”呢?这就涉及到javascript中的“变量提升”,其实我感觉叫“声明提升”更好,因为这个机制就是把变量的声明提前到函数的前面。并不会把值也同样提升,也就是为什么第一次没有输出“1”的原因。

    上面的代码就相当于:

    function func(){
        var x;
        console.log(x);
        var x = 1;
        console.log(x);
    }        
    func();

    3)函数内部用不用“var”对程序的影响

    这是个值得注意的地方:

    var x = 1;
    function addVar(){
        var x = 2;
        console.log(x);            
    }
    addVar();
    console.log(x);

    输出:

    [Web浏览器] "2"        

    [Web浏览器] "1"        

    当在函数内部去掉var之后,再执行:

     var x = 1;
     function delVar(){
           x = 2;
           console.log(x);            
      }
     delVar();
     console.log(x);

    [Web浏览器] "2"        

    [Web浏览器] "2"        

    上面的例子说明了,在函数内部如果在声明变量没有使用var,那么声明的变量就是全局变量。

    二、javascript的作用域链

    先看如下的代码:

    var name="Global";
    function t(){
            var name="t_Local";
            
            function s1(){
                var name="s1_Local";
                    console.log(name);
            }
            function s2(){
                    console.log(name);
            }
            s1();
            s2(); 
    }
    t(); 

    输出结果:

    [Web浏览器] "s1_Local"

    [Web浏览器] "t_Local"

    那么就有几个问题:

    1.为什么第二次输出的不是s1_Local?

    2.为什么不是Global

    解决这个两个问题就在于作用域链

    下面就解析一下这个过程,

     

    例如当上面程序创建完成的时候,会形成上图中的链状结构(假想的),所以每次调用s1()函数的时候,console.log(name);先会在他自己的作用域中寻找name这个变量,这里它找到name=s1_Local”,所以就输出了s1_Local,而每次调用s2()的时候,它和s1()函数过程一样,只不过在自身的作用域没有找到,所以向上层查找,在t()函数中找到了,于是就输出了"t_Local"

    同样如果我们可以验证一下,如果把t中的name删除,可以看看输出是不是“Global

    结果如下:

    [Web浏览器] "s1_Local"

    [Web浏览器] "Global"        

    当然具体每一个作用域直接是如何连接的,请参考

    http://www.cnblogs.com/lhb25/archive/2011/09/06/javascript-scope-chain.html

    其中也说明了为什么JS当中应该尽量减少with关键字的使用。

    三、闭包

    了解了作用域和作用域链的概念之后,再去理解闭包就相对容易了。

    1.闭包第一个作用

    还是先看例子:

    function s1() {
        var x = 123;
        return s2();
    }
    
    function s2() {
        return x;
    }
    
    alert(s1());

    这里我想弹出x的值,但是却发生了错误 "Uncaught ReferenceError: x is not defined",根据作用域链的知识,了解到因为s2中没有x的定义,并且向上找全局也没有x定义,所以就报错了。也就是说s1s2不能够共享x的值。

    那么问题来了,我想要访问s1当中的x,怎么弄?

    修改代码如下:

    function s1() {
        var x = 123;
        return function(){
            return x;
        };
    }
        
    var test = s1();
    console.log(test());

    结果为:

    [Web浏览器] "123"        

    解释:因为function本质也是数据,所以它和x的作用域相同,可以访问x。这样就相当于对外部开放了一个可以访问内部变量的接口。

    还可以怎么玩,稍微修改一下代码

    var func;
    function f(){
    var x='123';
    func=function(){
        return x;
        };
    }
    f();
    alert(func());

    定义一个全局的变量,然后在函数内部让其等于一个函数,这样就可以通过这个全局变量来进行访问x了。

    综上:闭包是啥?闭包就相当于函数外部和内部的桥梁,通过闭包可以在外部访问函数内部的变量。这也是闭包的第一个作用。

    2.闭包的第二个作用

    先看代码:

    function f1(){
        var n=1;
        add=function(){
            n+=1;
        };
        function f2(){
            console.log(n);
            return '输出完成';
        }
        return f2;
    }
    var res=f1();
    console.log(res());
    add();
    console.log(res());

    输出结果:

    [Web浏览器] "1"

    [Web浏览器] "输出完成"

    [Web浏览器] "2"

    [Web浏览器] "输出完成"

     

    问题为什么第二次输出的结果n变成了2,没有被清除?

    我的理解:res是一个全局变量,一直驻留在内存当中,它就相当于f2函数,f2函数又要依赖于f1函数,所以f1也驻留在内存当中,保证不被GC所回收,每一次调用add函数的时候,就相当于把f1中的n重新赋值了,这样n的值就变化了。这也是闭包的第二个作用,可以让变量的值始终保存在内存当中

    3.闭包的应用

    ①可以做访问控制(相当于C#当中的属性)

    //定义两个变量,用于存储取值和存值
    var get,set; 
    //定义一个自调用函数,设定set和get方法
    (function(){
        //设定x的默认值
        var x = 0;
        set = function(n){
            x = n;
        }
        get = function(){
            return x;
        }
    })();
    
    console.log(get());
    set(5);
    console.log(get());

    输出结果:

    [Web浏览器] "0"

    [Web浏览器] "5"

    ②可以用做迭代器

    //定义一个函数,里面使用了闭包
    function foo(myArray){
        var i=0;
        //闭包迭代器
        next=function(){
            //每次返回数组的当前值,下标+1
            return myArray[i++];
        }
    }
    //调用foo,参数为一个数组
    foo(['a','b','c','d']);
    //每次调用next都会打印数组的一个值
    console.log(next());
    console.log(next());
    console.log(next());
    console.log(next());

    输出结果:

    [Web浏览器] "a"

    [Web浏览器] "b"

    [Web浏览器] "c"

    [Web浏览器] "d"

    ③闭包在循环中的使用

    例1
    function f(){
        var a=[];
        var i;
        for(i=0;i<3;i++){
            a[i]=function(){
                return i;
            };
        }
        return a;
    }
    var test=f();
    console.log(test[0]());
    console.log(test[1]());
    console.log(test[2]());

    输出结果:

    [Web浏览器] "3"

    [Web浏览器] "3"

    [Web浏览器] "3"

    为什么结果不是012

    这里我们使用了闭包,每次循环都指向了同一个局部变量i,但是闭包不会记录每一次循环的值,只保存了变量的引用地址,所以当我们在调用test[0]()test[1]()test[2]()的时候都返回的是for循环最后的值,也就是3的时候跳出了循环。

    2:我想输出0,1,2怎么搞?

    function f(){
        var a=[];
        var i;
        for(i=0;i<3;i++){
            a[i]=(function(x){
                return function(){
                    return x;
                }
            })(i);
        }
        return a;
    }
    var test=f();
    console.log(test[0]());
    console.log(test[1]());
    console.log(test[2]());

    结果:

    [Web浏览器] "0"

    [Web浏览器] "1"

    [Web浏览器] "2"

    关于这个为什么和上面不一样,我在知乎上找到了一篇文章,总结的非常好,

    ★★★★★引用自:http://www.zhihu.com/question/33468703

  • 相关阅读:
    RabbitMQ第一次不能正常读取第二次正常的问题
    ng跳转映射,被阿里云的云盾拦截,提示备案问题分析
    Java读取excel表,getPhysicalNumberOfCells()和getLastCellNum区别
    IDEA同步上传lua代码,方便开发。
    redis安装
    面试感悟----一名3年工作经验的程序员应该具备的技能
    SQL Server的case when用法
    SQL Server常用函数使用方法(学习)
    Openresty编写Lua代码一例
    Nginx的配置文件nginx.conf解析
  • 原文地址:https://www.cnblogs.com/dcz2015/p/5379814.html
Copyright © 2020-2023  润新知