zoukankan      html  css  js  c++  java
  • JavaScript作用域和作用域链

    一、关于块级作用域 
            说到JavaScript的变量作用域,与咱们平时使用的类C语言不同. 
    例如C#中下面代码:

    1. static void Main(string[] args)
    2. {
    3.         if(true)
    4.         {
    5.                 int num = 10;
    6.         }
    7.         System.Console.WriteLine(num);
    8. }

    这段代码如果进行编译,是无法通过的,因为"当前上下文中不存在名称num". 因为这里 
    变量的作用域是由花括号限定的,称为块级作用域. 
            在块级作用域下,所有的变量都在定义的花括号内,从定义开始到花括号结束这个 
    范围内可以使用. 出了这个范围就无法访问. 也就是说代码

    1. if(true)
    2. {
    3.         int num = 10;
    4.         System.Console.WriteLine(num);
    5. }

    这里可以访问,因为变量的定义与使用在同一个花括号内. 
            但是在JavaScript中就不一样,JavaScript中没有块级作用域的概念. 
    二、JavaScript中的作用域 
            在JavaScript中,下面代码:

    1. if(true) {
    2.         var num = 10;
    3. }
    4. alert(num);

    运行的结果是弹窗10. 那么在JavaScript中变量的作用范围是怎么限定的呢? 
    2.1 函数限定变量作用域 
            在JavaScript中,只有函数可以限定一个变量的作用范围. 什么意思呢? 
    就是说,在JavaScript中,在函数里面定义的变量,可以在函数里面被访问,但是在函数外 
    无法访问. 看如下代码:

    1. var func = function() {
    2.         var num = 10;
    3. };
    4. try {
    5.         alert(num);
    6. } catch ( e ) {
    7.         alert( e );
    8. }

    这段代码运行时,会抛出一个异常,变量num没有定义. 也就是说,定义在函数中的变量无法 
    在函数外使用,当然在函数内可以随意的使用, 即使在赋值之前. 看下面代码:

    1. var func = function() {
    2.         alert(num);
    3.         var num = 10;
    4.         alert(num);
    5. };
    6. try {
    7.         func();
    8. } catch ( e ) {
    9.         alert( e );
    10. }

    这段代码运行后,不会抛出错误,弹窗两次,分别是 undefined 和 10(至于为什么,下文解释). 
            从这里可以看得出,变量只有在函数中可以被访问. 同理在该函数中的函数也可以访问. 
    2.2 子域访问父域 
            前面说了,函数可以限定变量的作用域,那么在函数中的函数就成为该作用域的子域. 在子域 
    中的代码可以访问到父域中的变量. 看下面代码:

    1. var func = function() {
    2.         var num = 10;
    3.         var sub_func = function() {
    4.                 alert(num);
    5.         };
    6.         sub_func();
    7. };
    8. func();

    复制代码

    这段代码执行得到的结果就是 10. 可以看到上文所说的变量访问情况. 但是在子域中访问父域的 
    代码也是有条件的. 如下面代码:

    1. var func = function() {
    2.         var num = 10;
    3.         var sub_func = function() {
    4.                 var num = 20;
    5.                 alert(num);
    6.         };
    7.         sub_func();
    8. };
    9. func();

    这段代码比前面就多了一个"var num = 20;",这句代码在子域中,那么子域访问父域的情况就发 
    生了变化,这段代码打印的结果是 20. 即此时子域访问的num是子域中的变量,而不是父域中的. 
            由此可见访问有一定规则可言. 在JavaScript中使用变量,JavaScript解释器首先在当前作 
    用域中搜索是否有该变量的定义,如果有,就是用这个变量;如果没有就到父域中寻找该变量. 
    以此类推,直到最顶级作用域,仍然没有找到就抛出异常"变量未定义". 看下面代码:

    1. (function() {
    2.         var num = 10;
    3.         (function() {
    4.                 var num = 20;
    5.                 (function(){
    6.                         alert(num);
    7.                 })()
    8.         })();
    9. })();

    这段代码执行后打印出20. 如果将"var num = 20;"去掉,那么打印的就是10. 同样,如果再去掉 
    "var num = 10",那么就会出现未定义的错误. 
    三、作用域链 
            有了JavaScript的作用域的划分,那么可以将JavaScript的访问作用域连成一个链式树状结构. 
    JavaScript的作用域链一旦能清晰的了解,那么对于JavaScript的变量与闭包就是非常清晰的了. 
    下面采用绘图的办法,绘制作用域链. 
    3.1 绘制规则: 
            1) 作用域链就是对象的数组 
            2) 全部script是0级链,每个对象占一个位置 
            3) 凡是看到函数延伸一个链出来,一级级展开 
            4) 访问首先看当前函数,如果没有定义往上一级链检查 
            5) 如此往复,直到0级链 
    3.2 举例 
            看下面代码:

    1. var num = 10;
    2. var func1 = function() {
    3.         var num = 20;
    4.         var func2 = function() {
    5.                 var num = 30;
    6.                 alert(num);
    7.         };
    8.         func2();
    9. };
    10. var func2 = function() {
    11.         var num = 20;
    12.         var func3 = function() {
    13.                 alert(num);
    14.         };
    15.         func3();
    16. };
    17. func1();
    18. func2();

    下面分析一下这段代码: 
            -> 首先整段代码是一个全局作用域,可以标记为0级作用域链,那么久有一个数组 
                    var link_0 = [ num, func1, func2 ];                // 这里用伪代码描述 
            -> 在这里func1和func2都是函数,因此引出两条1级作用域链,分别为 
                    var link_1 = { func1: [ num, func2 ] };        // 这里用伪代码描述 
                    var link_1 = { func2: [ num, func3 ] };        // 这里用伪代码描述 
            -> 第一条1级链衍生出2级链 
                    var link_2 = { func2: [ num ] };        // 这里用伪代码描述 
            -> 第二条1级链中没有定义变量,是一个空链,就表示为 
                    var link_2 = { func3: [ ] }; 
            -> 将上面代码整合一下,就可以将作用域链表示为: 
                    // 这里用伪代码描述 
                    var link = [ // 0级链 
                            num, 
                            { func1 : [        // 第一条1级链 
                                                    num, 
                                                    { func2 : [        // 2级链 
                                                                            num 
                                                                    ] } 
                                            ]}, 
                            { func2 : [        // 第二条1级链 
                                                    num, 
                                                    { func3 : [] } 
                                            ]} 
                    ]; 
            -> 用图像表示为 
     
                    图:01_01作用域链.gif 
            注意:将链式的图用js代码表现出来,再有高亮显示的时候就非常清晰了. 
    有了这个作用域链的图,那么就可以非常清晰的了解访问变量是如何进行的: 
    在需要使用变量时,首先在当前的链上寻找变量,如果找到就直接使用,不会 
    向上再找;如果没有找到,那么就向上一级作用域链寻找,直到0级作用域链. 
            如果能非常清晰的确定变量所属的作用域链的级别,那么在分析JavaScript 
    代码与使用闭包等高级JavaScript特性的时候就会非常容易(至少我是这样哦). 
    四、变量名提升与函数名提升 
            有了作用域链与变量的访问规则,那么就有一个非常棘手的问题. 先看下面 
    的JavaScript代码:

    1. var num = 10;
    2. var func = function() {
    3.         alert(num);
    4.         var num = 20;
    5.         alert(num);
    6. };
    7. func();

    执行结果会是什么呢?你可以想一想,我先不揭晓答案. 
            先来分析一下这段代码. 
            这段代码中有一条0级作用域链,里面有成员num和func. 在func下是1级作用 
    域链,里面有成员num. 因此在调用函数func的时候,就会检测到在当前作用域中 
    变量num是定义过的,所以就会使用这个变量. 但是此时num并没有赋值,因为代 
    码是从上往下运行的. 因此第一次打印的是 undefined,而第二次打印的便是20. 
            你答对了么? 
            像这样将代码定义在后面,而在前面使用的情况在JavaScript中也是常见的 
    问题. 这时就好像变量在一开始就定义了一样,结果就如同下面代码:

    1. var num = 10;
    2. var func = function() {
    3.         var num;        // 感觉就是这里已经定义了,但是没有赋值一样
    4.         alert(num);
    5.         var num = 20;
    6.         alert(num);
    7. };
    8. func();

    那么这个现象常常称为变量名提升. 同样也有函数名提升这一说. 如下面代码:

    1. var func = function() {
    2.         alert("调用外面的函数");
    3. };
    4. var foo = function() {
    5.         func();
    6.         var func = function() {
    7.                 alert("调用内部的函数");
    8.         };
    9.         func();
    10. };

    好了,这段代码结果如何?或则应该有什么不一样,我先不说没留着读者思考吧! 
    下一篇再做解答. 
            由于有了这些不同,因此在实际开发的时候,推荐将变量都写在开始的地方, 
    也就是在函数的开头将变量就定义好,类似于C语言的规定一样. 这个在js库中也 
    是这么完成的,如jQuery等.

    五、小结 
            好了这篇文章主要是说明JavaScript的词法作用域是怎么一回事儿,以及解释 
    如何分析作用域链,和变量的访问情况。

  • 相关阅读:
    C# 网络斗地主源码开源
    windows 10
    JAVA Eclipse Incorrect line ending found carriage return 怎么办
    JAVA Eclipse中如何简易的实现消息机制
    JAVA Eclipse中的Android程序如何使用线程
    JAVA Eclipse如何重新设置工作空间workspace
    JAVA Eclipse如何重命名包
    JAVA Eclipse如何修改Android程序名称
    JAVA Eclipse如何设置编程环境字体
    JAVA Eclipse如何设置点击按钮切换图片
  • 原文地址:https://www.cnblogs.com/zlz-ling/p/4089921.html
Copyright © 2011-2022 走看看