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

前言:随着对JavaScript的学习,发现JavaScript的内容越来越丰富,在这里和大家分享一下JavaScript词法作用域和作用域链。

(一)关于块级作用域

说到JavaScript的变量作用域,与咱们平时使用的类C语言不同.

例如C#中下面代码:
	static void Main(string[] args)
	{
		if(true)
		{
			int num = 10;
		}
		System.Console.WriteLine(num);
	}
	
这段代码如果进行编译,是无法通过的,因为"当前上下文中不存在名称num". 因为这里
变量的作用域是由花括号限定的,称为【块级作用域】. 

在块级作用域下,所有的变量都在定义的花括号内,从定义开始到花括号结束这个范围内可以使用. 出了这个范围就无法访问. 也就是说下列代码这里可以访问,因为变量的定义与使用在同一个花括号内。
但是在JavaScript中就不一样,JavaScript中没有块级作用域的概念.

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

(二)JavaScript中的作用域

在JavaScript中,下面代码:
	if(true) {
		var num = 10;
	}
	alert(num);
	
运行的结果是弹窗10. 

1 函数限定变量作用域

在JavaScript中,只有函数可以限定一个变量的作用范围,那么在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 子域访问父域

函数可以限定变量的作用域,那么在函数中的函数就成为该作用域的子域. 在子域中的代码可以访问到父域中的变量.

代码示例:

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的变量与闭包就是非常清晰的了。

下面采用绘图的办法,绘制作用域链.

1 .绘制规则

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

2.图示:
在这里插入图片描述

有了这个作用域链的图,那么就可以非常清晰的了解访问变量是如何进行的:

在需要使用变量时,首先在当前的链上寻找变量,如果找到就直接使用,不会向上再找;

如果没有找到,那么就向上一级作用域链寻找,直到0级作用域链.

3. 举例

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代码与使用闭包等高级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的词法作用域和作用域链看起来很麻烦,但是结合图示和代码就很好理解了,多学习多总结,总会有收获的。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值