JavaScript高级之词法做用域和做用域链

主要内容:数组

  • 分析JavaScript的词法做用域的含义
  • 解析变量的做用域链
  • 变量名提高时什么

1、关于块级做用域
        说到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中没有块级做用域的概念.
2、JavaScript中的做用域
        在JavaScript中,下面代码:spa

  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没有定义. 也就是说,定义在函数中的变量没法
在函数外使用,固然在函数内能够随意的使用, 即便在赋值以前. 看下面代码:ip

  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. 能够看到上文所说的变量访问状况. 可是在子域中访问父域的
代码也是有条件的. 以下面代码:string

  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解释器首先在当前做
用域中搜索是否有该变量的定义,若是有,就是用这个变量;若是没有就到父域中寻找该变量.
以此类推,直到最顶级做用域,仍然没有找到就抛出异常"变量未定义". 看下面代码:it

  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",那么就会出现未定义的错误.
3、做用域链
        有了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特性的时候就会很是容易(至少我是这样哦).
4、变量名提高与函数名提高
        有了做用域链与变量的访问规则,那么就有一个很是棘手的问题. 先看下面
的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等.

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

 

转自传智播客,讲的不错,容易理解!

相关文章
相关标签/搜索