详解JavaScript作用域及作用域链

  一、js没有块级作用域

  在强类型语言中都有块级作用域,例如,有如下C#代码

for(int i = 0; i < 10; i++)
{
      //do something 
}
Console.WriteLine(i);   

  这样的代码是无法通过编译的,因为循环变量i是一个局部变量,它的作用域范围只在for循环的大括号{}之内,出了这个大括号就不是i的作用域了,所以Console.WriteLine(i); 这条语句就不能编译通过了。

  但如果这样类似的代码是js写的就另当别论了。比如:

for(var i = 0; i < 10; i++){
     //do something;  
}
console.log(i);//输出10

  这个代码是没有问题的,运行结果是10 。这说明了js没有传统意义上的块级作用域。

  但这不代表js没有局部作用域。js的作用域只有全局作用域和函数作用域,js也只有函数能产生局部作用域。

  一、全局作用域

  只要是在一对<script>标签之内直接声明的对象,它的作用域就是全局的。

  如果一个对象是在function内部声明的,那么它的作用域就是局部的,函数内部的作用域。

   比如:

<script>
    var a = 123; 
    console.log(a); //输出123
    function f1(){ 
       console.log(a);
    } 
    f1();//输出123
</script>

  此例中,a就是直接声明在了一对<script>标签内的,不是声明在某一个函数之内的,那么它是个全局作用域,所以不管是在f1函数内部,还是在外部,都可以访问到变量a

  二、函数作用域

  把上边的例子稍微改改

<script>
    var a = 123; 
    console.log(a); //输出123
    function f1(){ 
       var num = 10;
       console.log(a);
    } 
    f1();//输出123
    console.log(num); // 报错,无法访问到num
</script>

  这个代码会报错,因为num是在f1函数内部声明的,它的作用域仅限于f1函数声明的内部,也就是f1那个大括号{}内部,所以在函数声明的外部访问num肯定是不可以的。

  我们可能会看到一个词,说js中的作用域是:词法作用域。那么怎么理解这个所谓的“词法作用域”呢?

  看这个例子

<script>
    var a = 123; 
    console.log(a); //输出123
    function f1(){ 
       console.log(a);
    } 
    function f2(){ 
       var a = 456;
       f1(a);
    } 
    f2();//输出123
</script>

  这个例子就很值得思考了。

  1.有一个全局作用域的变量a

  2.f1函数调用了a

  3.f2函数先声明了一个局部变量a,并且与全局变量同名

  4.f2函数声明了这个同名的局部变量a之后,又调用了f1函数

  这个程序执行完之后的结果是 : 123 , 而不是456。原因是js中不存在块级作用域,只存在函数作用域。

  而函数作用域又被称之为词法作用域,说白了就是:你的函数时在哪儿声明的,那它就属于哪儿,也就意味着它只能访问到这个区间的成员。

  在这个例子中,f1函数是声明在全局作用域下的,那么它就属于全局作用域,也就意味着它(函数体内部)只能访问到全局作用域下声明的对象。显然,当前全局作用域下的a是等于123的,而不是f2内部声明的那个等于456的a,所以,不管是在何时调用f1这个函数,它访问到的a必然是那个值等于123的全局变量a。

  那究竟该如何清晰的描述出对象的作用域范围?这就要使用一个叫做“作用域链”的东西了。

  三、作用域链

  看这个例子就明白什么是作用域链了。

<script>
    var num = 1;

    function f1(){
        var num = 2;
        function f2(){
            var num = 3;
            function f3(){
                console.log(num);
            }
            f3();
        }
        f2();
    }

    f1(); //输出 3
    // f2();//报错,
    // f3();//报错

</script>

  这个例子就是函数嵌套声明,并且每个函数内部还都又声明了一个跟全局变量num同名的局部变量。相信通过刚才的解释,大家已经能清晰的得出程序执行结果了。

  简单说明一下:

  1.在全局作用域下,不能直接调用f2和f3函数,因为它们都是在f1内部嵌套声明的,它们都是局部作用域声明的对象,所以不能在全局作用域中访问。

  2.在局部作用域中,如果出现于更高级的作用域同名的对象,那么优先访问本作用域范围之内的对象

  3.如果在本作用域之内找不到,那么就往“上一级”作用域中去找,知道全局作用域,如果还没有就报错。

  那么这里所谓的“上一级”作用域,可以使用作用域链清晰的描绘出来。这个例子的作用域链我们可以用图画出来。

       

  首先这里的全局作用域我们称之为 0 级作用域,f1函数时在全局作用域下声明的,所以f1自己形成一个小的作用域,称之为 1 级。以此类推。

  当我们在外部调用f2和f3是不可能的。因为从图上我们清晰的看到,在0级作用域的横线上根本没有声明一个叫f2或者f3的对象。

  当我们在全局(0级)作用域下,调用f1函数,然后逐级的调用到f3的时候,f3内部有一条console.log(num)。此时,程序会沿着我们画的线,现在f3的作用域,也就是3级作用下找有没有声明一个叫num的对象,如果有就直接拿来打印,如果没有,则沿着这个作用域链往上递推,首先推到离他最近的上一级作用域,也就是f2的作用域,它是2级作用域,这里声明了一个叫做num的变量,并且赋了初值为3,于是,这条打印语句,就立刻把这个=3的num拿来使用,而不再理会更高级的作用域了。因为它已经找到了一个可以访问到的num。

  我们把这个图描绘的作用域之间的关系,就形象的称之为作用域链。

  在作用域链中搜索对象,是只能逐级往上查找的,而不能往低级的作用域中查找。

猜你喜欢

转载自www.cnblogs.com/ldq678/p/9703368.html
今日推荐