主要内容:

  • 分析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的词法作用域是怎么一回事儿,以及解释
如何分析作用域链,和变量的访问情况。

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

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

  1. 《JavaScript高级程序设计》 -- 变量、作用域和内存问题(二)

    1.基本类型与引用类型 基本类型:值保存在变量中 (Number.String.Boolean.Undefined.Null).在内存中占据固定大小空间,被保存在栈内存中 引用类型:值是保存在内存中的 ...

  2. Javascript高级程序设计——执行环境与作用域

    Javascript中执行环境是定义了变量或函数有权访问的其他数据,决定了各自的行为,每个执行的环境都有一个与之关联的变量对象,环境中定义的所以变量和函数都保存在这个对象中. 全局执行环境是最外围的一 ...

  3. JavaScript高级程序设计-(3) 变量、作用域和内存问题

    传递参数 ECMAScript所有函数参数都是按值传递的,即使对象在函数内部修改了参数的值,原始的引用任然不变,局部对象在函数执行完毕后被销毁

  4. JavaScript的作用域与作用域链

    作用域 作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.可以说,变量和函数在什么时候可以用,什么时候被摧毁,这都与作用域有关. JavaScript中,变量的作用域有全局 ...

  5. 关于Javascript作用域及作用域链的总结

    本文是根据以下文章以及<Javascript高级程序设计(第三版)>第四章相关内容总结的. 1.Javascript作用域原理,地址:http://www.laruence.com/200 ...

  6. JavaScript闭包之“词法作用域”

    大家应该写过下面类似的代码吧,其实这里我想要表达的是有时候一个方法定义的地方和使用的地方会相隔十万八千里,那方法执行时,它能访问哪些变量,不能访问哪些变量,这个怎么判断呢?这个就是我们这次需要分析的问 ...

  7. 《JavaScript高级程序设计》学习笔记(3)——变量、作用域和内存问题

    欢迎关注本人的微信公众号"前端小填填",专注前端技术的基础和项目开发的学习. 本节内容对应<JavaScript高级程序设计>的第四章内容. 1.函数:通过函数可以封装 ...

  8. javascript作用域和作用域链

    1.作用域 作用域,它是指对某一变量和方法具有访问权限的代码空间.当我们在定义变量的时候,会定义两种变量,一种是在全局环境下定义的变量,叫全局变量,一种是在函数中定义的变量叫局部变量.全局变量的作用域 ...

  9. 读书时间《JavaScript高级程序设计》三:函数,闭包,作用域

    上一次看了第6章,面向对象.这里接着看第7章. 第7章:函数表达式 定义函数有两种方式:函数声明.函数表达式 //函数声明 function functionName(arg0,arg1,arg2){ ...

随机推荐

  1. AngularJs 基础(60分钟入门)

    AngularJS 是一个创建富客户端应用的JavaScript MVC框架.你仍然需要具有服务端后台,但大多数的用户交互逻辑将放到客户端上处理.它可以创建单页的应用程序,一个页面的应用仅仅需要HTM ...

  2. 易出错的C语言题目之一:宏定义与预处理

    1.写出下列代码的运行结果: #include<stdio.h> #include<string.h> #define STRCPY(a,b) strcpy(a##_p,#b) ...

  3. 浅谈压缩感知(二十五):压缩感知重构算法之分段正交匹配追踪(StOMP)

    主要内容: StOMP的算法流程 StOMP的MATLAB实现 一维信号的实验与结果 门限参数Ts.测量数M与重构成功概率关系的实验与结果 一.StOMP的算法流程 分段正交匹配追踪(Stagewis ...

  4. atitit.压缩算法 ZLib ,gzip ,zip 最佳实践 java .net php

    atitit.压缩算法 ZLib ,gzip ,zip   最佳实践  java .net php 1. 压缩算法的归类::: 纯算法,带归档算法 1 2. zlib(适合字符串压缩) 1 3. gz ...

  5. JS open App(未安装就跳转下载页面)

    直接上代码var APPCommon = { downAppURl : "http://**/",//下载APP地址 downWeixin: "http://**&quo ...

  6. SQL Server 批量插入数据的方法

    运行下面的脚本,建立测试数据库和表. --Create DataBase create database BulkTestDB; go use BulkTestDB; go --Create Tabl ...

  7. SqlServer 查看事务锁及执行语句

    一.查看当前锁定的事务 ,) ,用户机器名称,) ,是否被锁住),blocked) ,数据库名称,),cmd 命令,waittype as 等待类型 ,last_batch 最后批处理时间,open_ ...

  8. iptables防火墙原理详解

    1. netfilter与iptables Netfilter是由Rusty Russell提出的Linux 2.4内核防火墙框架,该框架既简洁又灵活,可实现安全策略应用中的许多功能,如数据包过滤.数 ...

  9. 关于iphone6安装了727个应用后,更新app 导致一些app无法更新,无法删除,重启后消失,但是却还是占用空间的解决办法

    我的iphone6 苹果手机,64GB的,存储空间最近一直很吃紧,很捉急,昨天,终于下定决心 解决下这个问题. 由于 空间大,我又随便安装许多APP,现在有727个app,常用的其实就是那个几十个而已 ...

  10. Android自定义相机拍照、图片裁剪的实现

    最近项目里面又要加一个拍照搜题的功能,也就是用户对着不会做的题目拍一张照片,将照片的文字使用ocr识别出来,再调用题库搜索接口搜索出来展示给用户,类似于小猿搜题.学霸君等app. 其实Android提 ...