深入理解JavaScript高级之词法作用域和作用域链


Posted in Javascript onDecember 10, 2013

主要内容:
1、分析JavaScript的词法作用域的含义

2、解析变量的作用域链

3、变量名提升时什么

最近在传智播客讲解JavaScript的课程,有不少朋友觉得JavaScript是如此的简单,但是又如此的不知如何使用,因此我准备了一些内容给大家分享一下.
这个系列主要讲解JavaScript的高级部分的内容,包括作用域链、闭包、函数调用模式、原型以及面向对象的一些东西. 在这里不包含JavaScript的基本语法,如果需要了解基础的同学可以到http://net.itcast.cn里面去下载免费的视频进行学习. 好了,废话不多说,直接进入我们的正题.

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

static void Main(string[] args)
{
 if(true)
 {
  int num = 10;
 }
 System.Console.WriteLine(num);
}

这段代码如果进行编译,是无法通过的,因为"当前上下文中不存在名称num". 因为这里
变量的作用域是由花括号限定的,称为块级作用域.

在块级作用域下,所有的变量都在定义的花括号内,从定义开始到花括号结束这个
范围内可以使用. 出了这个范围就无法访问. 也就是说代码

if(true)
{
 int num = 10;
 System.Console.WriteLine(num);
}

这里可以访问,因为变量的定义与使用在同一个花括号内.

但是在JavaScript中就不一样,JavaScript中没有块级作用域的概念.

二、JavaScript中的作用域
在JavaScript中,下面代码:

if(true) {
 var num = 10;
}
alert(num);

运行的结果是弹窗10. 那么在JavaScript中变量的作用范围是怎么限定的呢?

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

var func = function() {
 var num = 10;
};
try {
 alert(num);
} catch ( e ) {
 alert( e );
}

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

var func = function() {
 alert(num);
 var num = 10;
 alert(num);
};
try {
 func();
} catch ( e ) {
 alert( e );
}

这段代码运行后,不会抛出错误,弹窗两次,分别是 undefined 和 10(至于为什么,下文解释).

从这里可以看得出,变量只有在函数中可以被访问. 同理在该函数中的函数也可以访问.

2.2 子域访问父域
前面说了,函数可以限定变量的作用域,那么在函数中的函数就成为该作用域的子域. 在子域
中的代码可以访问到父域中的变量. 看下面代码:

var func = function() {
 var num = 10;
 var sub_func = function() {
  alert(num);
 };
 sub_func();
};
func();

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

var func = function() {
 var num = 10;
 var sub_func = function() {
  var num = 20;
  alert(num);
 };
 sub_func();
};
func();

这段代码比前面就多了一个"var num = 20;",这句代码在子域中,那么子域访问父域的情况就发
生了变化,这段代码打印的结果是 20. 即此时子域访问的num是子域中的变量,而不是父域中的.

由此可见访问有一定规则可言. 在JavaScript中使用变量,JavaScript解释器首先在当前作
用域中搜索是否有该变量的定义,如果有,就是用这个变量;如果没有就到父域中寻找该变量.
以此类推,直到最顶级作用域,仍然没有找到就抛出异常"变量未定义". 看下面代码:

(function() {
 var num = 10;
 (function() {
  var num = 20;
  (function(){
   alert(num);
  })()
 })();
})();

这段代码执行后打印出20. 如果将"var num = 20;"去掉,那么打印的就是10. 同样,如果再去掉
"var num = 10",那么就会出现未定义的错误.

三、作用域链
有了JavaScript的作用域的划分,那么可以将JavaScript的访问作用域连成一个链式树状结构.
JavaScript的作用域链一旦能清晰的了解,那么对于JavaScript的变量与闭包就是非常清晰的了.
下面采用绘图的办法,绘制作用域链.

3.1 绘制规则:
1) 作用域链就是对象的数组
2) 全部script是0级链,每个对象占一个位置
3) 凡是看到函数延伸一个链出来,一级级展开
4) 访问首先看当前函数,如果没有定义往上一级链检查
5) 如此往复,直到0级链

3.2 举例
看下面代码:

var num = 10;
var func1 = function() {
 var num = 20;
 var func2 = function() {
  var num = 30;
  alert(num);
 };
 func2();
};
var func2 = function() {
 var num = 20;
 var func3 = function() {
  alert(num);
 };
 func3();
};
func1();
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 : [] }
 ]}
];

-> 用图像表示为

深入理解JavaScript高级之词法作用域和作用域链

图:01_01作用域链.bmp

注意:将链式的图用js代码表现出来,再有高亮显示的时候就非常清晰了.

有了这个作用域链的图,那么就可以非常清晰的了解访问变量是如何进行的:
在需要使用变量时,首先在当前的链上寻找变量,如果找到就直接使用,不会
向上再找;如果没有找到,那么就向上一级作用域链寻找,直到0级作用域链.

如果能非常清晰的确定变量所属的作用域链的级别,那么在分析JavaScript
代码与使用闭包等高级JavaScript特性的时候就会非常容易(至少我是这样哦).

三、变量名提升与函数名提升

有了作用域链与变量的访问规则,那么就有一个非常棘手的问题. 先看下面
的JavaScript代码:

var num = 10;
var func = function() {
 alert(num);
 var num = 20;
 alert(num);
};
func();

执行结果会是什么呢?你可以想一想,我先不揭晓答案.

先来分析一下这段代码.
这段代码中有一条0级作用域链,里面有成员num和func. 在func下是1级作用
域链,里面有成员num. 因此在调用函数func的时候,就会检测到在当前作用域中
变量num是定义过的,所以就会使用这个变量. 但是此时num并没有赋值,因为代
码是从上往下运行的. 因此第一次打印的是 undefined,而第二次打印的便是20.
你答对了么?

像这样将代码定义在后面,而在前面使用的情况在JavaScript中也是常见的
问题. 这时就好像变量在一开始就定义了一样,结果就如同下面代码:

var num = 10;
var func = function() {
 var num; // 感觉就是这里已经定义了,但是没有赋值一样
 alert(num);
 var num = 20;
 alert(num);
};
func();

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

var func = function() {
 alert("调用外面的函数");
};
var foo = function() {
 func(); var func = function() {
  alert("调用内部的函数");
 };
 func();
};

好了,这段代码结果如何?或则应该有什么不一样,我先不说没留着读者思考吧!
下一篇再做解答.

由于有了这些不同,因此在实际开发的时候,推荐将变量都写在开始的地方,
也就是在函数的开头将变量就定义好,类似于C语言的规定一样. 这个在js库中也
是这么完成的,如jQuery等.

四、小结

好了这篇文章主要是说明JavaScript的词法作用域是怎么一回事儿,以及解释
如何分析作用域链,和变量的访问情况,最后留再一个练习收尾吧!!!

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

if( ! "a" in window ) {
 var a = "定义变量";
}
alert(a);
Javascript 相关文章推荐
学习JS面向对象成果 借国庆发布个最新作品与大家交流
Oct 03 Javascript
如何获取JQUERY AJAX返回的JSON结果集实现代码
Dec 10 Javascript
js日期对象兼容性的处理方法
Jan 28 Javascript
使用CoffeeScrip优美方式编写javascript代码
Oct 28 Javascript
js操作数组函数实例小结
Dec 10 Javascript
Angularjs中的页面访问权限怎么设置
Nov 11 Javascript
ES6新特性之字符串的扩展实例分析
Apr 01 Javascript
JS简单判断滚动条的滚动方向实现方法
Apr 28 Javascript
jQuery完成表单验证的实例代码(纯代码)
Sep 30 jQuery
Angular7.2.7路由使用初体验
Mar 01 Javascript
Vue 开发必须知道的36个技巧(小结)
Oct 09 Javascript
html5 录制mp3音频支持采样率和比特率设置
Jul 15 Javascript
javascript四舍五入函数代码分享(保留后几位)
Dec 10 #Javascript
javascript如何创建表格(javascript绘制表格的二种方法)
Dec 10 #Javascript
javascript修改表格背景色实例代码分享
Dec 10 #Javascript
jquery全选checkBox功能实现代码(取消全选功能)
Dec 10 #Javascript
javascript中文本框中输入法切换的问题
Dec 10 #Javascript
javascript 数字格式化输出的实现代码
Dec 10 #Javascript
多个jquery.datatable共存,checkbox全选异常的快速解决方法
Dec 10 #Javascript
You might like
php urlencode()与urldecode()函数字符编码原理详解
2011/12/06 PHP
PHP下打开phpMyAdmin出现403错误的问题解决方法
2013/05/23 PHP
ThinkPHP连接数据库及主从数据库的设置教程
2014/08/22 PHP
PHP实现linux命令tail -f
2016/02/22 PHP
10个值得深思的PHP面试题
2016/11/14 PHP
快速解决PHP调用Word组件DCOM权限的问题
2017/12/27 PHP
Laravel框架基于ajax和layer.js实现无刷新删除功能示例
2019/01/17 PHP
通过实例解析PHP数据类型转换方法
2020/07/11 PHP
在JavaScript中监听IME键盘输入事件
2011/05/29 Javascript
原生js实现fadein 和 fadeout淡入淡出效果
2014/06/05 Javascript
防止登录页面出现在frame中js代码
2014/07/22 Javascript
JavaScript中Cookie操作实例
2015/01/09 Javascript
JS运动相关知识点小结(附弹性运动示例)
2016/01/08 Javascript
js实现rem自动匹配计算font-size的示例
2017/11/18 Javascript
vue2.0使用swiper组件实现轮播效果
2017/11/27 Javascript
JavaScript实现的DOM树遍历方法详解【二叉DOM树、多叉DOM树】
2018/05/07 Javascript
vue项目从node8.x升级到12.x后的问题解决
2019/10/25 Javascript
JavaScript中CreateTextFile函数
2020/08/30 Javascript
Python程序设计入门(2)变量类型简介
2014/06/16 Python
详解Python中列表和元祖的使用方法
2015/04/25 Python
Python连接SQLServer2000的方法详解
2017/04/19 Python
python matplotlib绘图,修改坐标轴刻度为文字的实例
2018/05/25 Python
django初始化数据库的实例
2018/05/27 Python
Windows 8.1 64bit下搭建 Scrapy 0.22 环境
2018/11/18 Python
Python中三元表达式的几种写法介绍
2019/03/04 Python
零基础使用Python读写处理Excel表格的方法
2019/05/02 Python
python 利用jieba.analyse进行 关键词提取
2020/12/17 Python
python pygame 愤怒的小鸟游戏示例代码
2021/02/25 Python
详解css3使用transform出现字体模糊的解决办法
2020/10/16 HTML / CSS
美国性感内衣店:Yandy
2018/06/12 全球购物
材料化学应届生求职信
2013/10/09 职场文书
购房意向书
2014/08/30 职场文书
2015社区六五普法工作总结
2015/04/21 职场文书
幼师自荐信范文(2016推荐篇)
2016/01/28 职场文书
Vue中使用import进行路由懒加载的原理分析
2022/04/01 Vue.js
frg-100简单操作(设置)说明
2022/04/05 无线电