深入理解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 相关文章推荐
Javascript 实现TreeView CheckBox全选效果
Jan 11 Javascript
找出字符串中出现次数最多的字母和出现次数精简版
Nov 07 Javascript
js禁止回车提交表单的示例代码
Dec 23 Javascript
jQuery中offsetParent()方法用法实例
Jan 19 Javascript
JavaScript设置body高度为浏览器高度的方法
Feb 09 Javascript
原生js实现自由拖拽弹窗代码demo
Jun 29 Javascript
D3.js实现柱状图的方法详解
Sep 21 Javascript
基于jQuery实现滚动刷新效果
Jan 09 Javascript
js模态对话框使用方法详解
Feb 16 Javascript
Angular 4依赖注入学习教程之简介(一)
Jun 04 Javascript
Angular移动端页面input无法输入的解决方法
Nov 14 Javascript
Vue前后端不同端口的实现方法
Sep 19 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
支持数组的ADDSLASHES的php函数
2010/02/16 PHP
php curl批处理实现可控并发异步操作示例
2018/05/09 PHP
thinkPHP框架实现的简单计算器示例
2018/12/07 PHP
javascript jscroll模拟html元素滚动条
2012/12/18 Javascript
Javascript弹出窗口的各种方法总结
2013/11/11 Javascript
JQuery中$.ajax()方法参数详解及应用
2013/12/12 Javascript
js 获取、清空input type="file"的值示例代码
2014/02/19 Javascript
无刷新预览所选择的图片示例代码
2014/04/02 Javascript
教你如何在 Javascript 文件里使用 .Net MVC Razor 语法
2014/07/23 Javascript
JavaScript实现列出数组中最长的连续数
2014/12/29 Javascript
js获取当前日期前七天的方法
2015/02/28 Javascript
js实现从右向左缓缓浮出网页浮动层广告的方法
2015/05/09 Javascript
jQuery实现图片走马灯效果的原理分析
2016/01/16 Javascript
NodeJS远程代码执行
2016/08/28 NodeJs
Vue.js第二天学习笔记(vue-router)
2016/12/01 Javascript
JS图片延迟加载插件LazyImgv1.0用法分析【附demo源码下载】
2017/09/04 Javascript
jQuery实现html双向绑定功能示例
2017/10/09 jQuery
vue实现引入本地json的方法分析
2018/07/12 Javascript
详解vue3.0 的 Composition API 的一种使用方法
2020/10/26 Javascript
vue 中使用print.js导出pdf操作
2020/11/13 Javascript
Python基于回溯法子集树模板解决野人与传教士问题示例
2017/09/11 Python
Django获取应用下的所有models的例子
2019/08/30 Python
Python拼接字符串的7种方式详解
2020/03/19 Python
Pytorch 使用不同版本的cuda的方法步骤
2020/04/02 Python
html5构建触屏网站之touch事件介绍
2013/01/07 HTML / CSS
微软香港官网及网上商店:Microsoft HK
2016/09/01 全球购物
Stuarts London美国/加拿大:世界领先的独立男装零售商之一
2019/03/18 全球购物
Farfetch中文官网:奢侈品牌时尚购物平台
2020/03/15 全球购物
《要下雨了》教学反思
2014/02/17 职场文书
《日月潭》教学反思
2014/02/28 职场文书
大学生毕业个人总结
2015/02/15 职场文书
调研报告的主要写法
2019/04/18 职场文书
2019职场实习报告该怎么写?
2019/07/01 职场文书
SQL基础的查询语句
2021/11/11 MySQL
MySQL常见优化方案汇总
2022/01/18 MySQL
【DOTA2】总决赛血虐~ XTREME GAMING vs MAGMA - OGA DOTA PIT 2022 CN
2022/04/02 DOTA