[JavaScript]JavaScript高级之词法作用域和作用域链

主要内容:

  • 分析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的词法作用域是怎么一回事儿,以及解释
如何分析作用域链,和变量的访问情况,最后留再一个练习收尾吧!!!

        看下面代码执行结果是什么:

  1. if(! "a" in window) {
  2.         var a = "定义变量";
  3. }
  4. alert(a);

复制代码

时间: 2024-11-02 00:10:04

[JavaScript]JavaScript高级之词法作用域和作用域链的相关文章

深入理解JavaScript高级之词法作用域和作用域链_基础知识

主要内容:1.分析JavaScript的词法作用域的含义 2.解析变量的作用域链 3.变量名提升时什么 最近在传智播客讲解JavaScript的课程,有不少朋友觉得JavaScript是如此的简单,但是又如此的不知如何使用,因此我准备了一些内容给大家分享一下. 这个系列主要讲解JavaScript的高级部分的内容,包括作用域链.闭包.函数调用模式.原型以及面向对象的一些东西. 在这里不包含JavaScript的基本语法,如果需要了解基础的同学可以到http://net.itcast.cn里面去下

聊一聊JavaScript作用域和作用域链_基础知识

每种编程语言,其变量都有一定的有效范围,超过这个范围之后,变量就失效了,这就是变量的作用域.从数学的角度来看,就是自变量的域. 作用域是变量的可访问范围,即作用域控制着变量与函数的可见性和生命周期.在 JavaScript 中, 对象和函数同样也是变量,变量在声明他们的函数体以及这个函数体嵌套的任意函数体内部都是有定义的. 一.静态作用域和动态作用域 静态作用域 是指声明的作用域是根据程序正文在编译时就确定的,也称为词法作用域.大多数现代程序设计语言都是采用静态作用域规则,JavaScript就

javascript 作用域、作用域链理解

JavaScript作用域就是变量和函数的可访问范围. 1.变量作用域    在JavaScript中,变量作用域分为全局作用域和局部作用域.     全局作用域       任何地方都可以定义拥有全局作用域的变量       1.没有用var声明的变量(除去函数的参数)都具有全局作用域,成为全局变量,所以声明局部变量必须要用var       2.window的所有属性都具有全局作用域       3.最外层函数体外声明的变量也具有全局作用域 var globalScope="globalSc

【javascript基础】3、变量和作用域

原文:[javascript基础]3.变量和作用域 前言 这篇和大家说一下javascript中的变量和作用域,由于是将基础嘛,主要给大家捋一下知识,不想翻开书复习的道友可以看一下,打算刚开始学习javascript的同学可以扫一眼. PS:jQuery源码交流群( 239147101)等你来,群里高手云集,让我受益匪浅,尽量少灌水. 变量 javascript中有两种变量,分别是基本类型和引用类型,基本类型是Null,Undefined,String,Boolean,Number这五种,前面简

深入理解JavaScript作用域和作用域链_javascript技巧

作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript. JavaScript作用域 任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.在JavaScript中,变量的作用域有全局作用域和局部作用域两种. 1. 全局作用域(Global Sc

JavaScript作用域与作用域链深入解析_javascript技巧

作用域是JavaScript最重要的概念之一,想要学好JavaScript就需要理解JavaScript作用域和作用域链的工作原理.今天这篇文章对JavaScript作用域和作用域链作简单的介绍,希望能帮助大家更好的学习JavaScript. JavaScript作用域 任何程序设计语言都有作用域的概念,简单的说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.在JavaScript中,变量的作用域有全局作用域和局部作用域两种. 1. 全局作用域(Global Sc

javascript用匿名函数模仿块级作用域

JavaScript中没有块级作用域的概念.这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的. function outputNumbers(count){     for(var i = 0; i < count; i++){         alert(i);     }     console.log(i); } outputNumbers(2);//弹出0,1输出2 //变量i是定义在outputNumbers()的活动对象中的,因此从它有定义开始,就可以在函数内部随时

【javascript基础】4、原型与原型链

原文:[javascript基础]4.原型与原型链 前言 荒废了好几天,在宿舍闷了几天了,一直想着回家放松,什么也没搞,论文就让老师催吧.不过,闲的没事干的感觉真是不好,还是看看书,写写博客吧,今天和大家说说函数的原型. 原型是什么 第一次看到这个的时候,没太理解这个概念,其实也就是一个概念呗,没啥神秘的.书上说每个函数都有一个prototype属性(原型属性),这个属性是一个指针,指向一个对象(原型对象),这个对象包含这个函数创建的实例的共享属性和方法.也就是说原型对象中的属性和方法是所有实例

结合代码图文讲解JavaScript中的作用域与作用域链_基础知识

先上三段说明作用域的代码 //==========例1========== var scope='global'; function fn(){ alert(scope); var scope='local'; alert(scope); } fn(); //输出结果? alert(scope);//输出结果? //===========例2========== var scope='global'; function fn(){ alert(scope); scope='local'; ale