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

  • 相关阅读:
    古谚、评论与论断、名篇与名言
    重读《西游记》
    重读《西游记》
    命名之法 —— 时间、季节、地点
    命名之法 —— 时间、季节、地点
    文言的理解 —— 古时的称谓、别称、别名
    文言的理解 —— 古时的称谓、别称、别名
    Oracle GoldenGate for Oracle 11g to PostgreSQL 9.2.4 Configuration
    瀑布 敏捷 文档
    POJ 1325 ZOJ 1364 最小覆盖点集
  • 原文地址:https://www.cnblogs.com/dcz2015/p/5379814.html
Copyright © 2011-2022 走看看