JavaScript 作用域 作用域链 变量的生命周期

全局变量和局部变量

对于程序员来说,作用域提供了一套十分重要的内存管理。作用域控制着变量和参数的可见性和生命周期。

倘若一个变量的作用域不是全局,则我们只可以在一个变量的作用域内访问到这个变量,而不能在作用域外访问到它(ps:除非把它返回出来)。如下:

<script>
	var num1 = 123;
	function fn(){
		var num2 = 456;
		console.log(num1);
		console.log(num2)}
	fn(789); //123  456
	console.log(num1); //123
	console.log(num2);//Uncaught ReferenceError: num2 is not defined
</script>

前面提到,如果一个变量的作用域不是全局才会有这种情况,实际上,全局变量在JavaScript代码中的任何地方都是有定义的,即全局作用域就是最外层的作用域。像上面这段代码中的变量num1就是在全局环境中声明的变量,故属于全局变量,没有更外层的作用域,在代码中任何地方都能访问。

另一方面,在函数内声明的变量只在函数体内有定义,如上面代码中的变量num2就是局部变量,它的作用域是局部性的,所以在函数外无法访问。

小结:全局变量在JavaScript代码中的任何地方都能访问,局部变量只能在局部访问。

JavaScript的作用域

和C语言不同的是,C语言有十分方便的块级作用域,即在花括号代码块中声明的变量,对于括号外是不可见,不可访问的。而JavaScript不同,虽然js也支持写在花括号中的代码块,但js却不支持这种块级作用域。

取而代之的,是js使用的函数作用域,变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都有定义。即对js来说,想要形成局部作用域,就要使用函数;除了函数体中定义的变量之外,都是全局作用域。

对于很多现代语言来说,都推荐尽可能迟地声明变量。而对于js语言来说,由于缺少块级作用域,花括号{}代码块只具有分组的作用,没有其他用途,块中声明的变量对外部来说是完全可访问可见的,声明提升可能会导致块内的变量覆盖外层变量,如下:

<script>
	var a = 1;
	function fn(){
		console.log(a);
		if(true){
			var a = 2;
		}
	}
	fn(); //undefined
</script>

所以,尽可能迟地声明变量这种建议不太适合在js中运用。相对好的做法是提前在函数体的顶部声明函数中可能会用到的所有变量。

小结:函数体外声明的变量即全局变量,函数体内声明的变量即局部变量。书写函数体内的代码时最好把可能用到的变量声明在函数的顶部。

作用域链

书面地说,每一段js代码都有一个与之关联的作用域链,这个作用域链是一个对象列或者链表。当作用域中需要访问(使用)变量的时候,会现在当前作用域中查找,如果找到那么就直接用,如果没有找到那么会顺着作用域链查找上层作用域,找到则直接使用,如果还是没有找到那么就重复这个过程,直到作用域链的末端为止,若还是找不到则抛出一个引用错误(ReferenceErro)。
在js最顶层的代码中(script标签里且不包含在任何函数块中的代码),作用域链只有一个全局对象;在没有嵌套函数的函数体内,作用域链上有两个对象,第一个是定义该函数参数和局部变量的对象,第二个是全局对象;在被嵌套的函数体内,作用域链上至少有三个对象定义。每当我们定义一个函数,实际上保存了一个作用域链,而每次调用它时,则创建了一个新对象来储存它的局部变量,并将这个对象添加到定义这个函数时保存的那个作用域链上,同时创建一个新的更长的表示函数调用作用域的链,即用链式的方式创建一个函数执行期上下文(可理解为函数执行时的环境)的集合,每次函数调用时所对应的执行上下文(这个储存局部变量的对象)都是独一无二的,多次调用同一个函数也会创建多个储存局部变量的对象

通俗的说,作用域一层包一层,最外层是我们的全局作用域。这就是作用域链。当我们执行局部作用域内的代码时,总是先从该局部作用域中寻找局部变量。如果找不到,才会向声明这个函数的作用域寻找,依次向外。如果最后到了全局作用域都还没找到这个变量,则报错。如下:

<script>
	function outFn(){
		var myName = "旦旦";
		console.log(myName);
		function miFn(){
			var myName = "旦旦boom";
			console.log(myName);
			function inFn(){
				console.log(myName);
			}
			inFn();
		}
		miFn();
	}
	outFn(); // 旦旦  旦旦boom  旦旦boom
</script>

可知,在函数调用的时候,若要访问的局部变量与外层变量重名,则访问存在该变量的且距离自己最近的那个作用域的该变量。

小结:存在多个作用域嵌套的情况下,若要访问变量x,则优先访问自己所在作用域的x,若自己所在作用域没有x,则访问上一层作用域的x,以此类推。

补充:变量的生命周期

  1. 全局变量的声明周期:
    我们在声明了全局变量之后,可以在代码的任何地方访问到全局变量。实际上我们是将全局变量存在了全局对象GO里,而这个对象是从打开页面就存在,直到页面关闭才会释放的。我们的全局变量也一样,直到页面关闭才会释放。
  2. 局部变量的生命周期:
    和全局变量不同,局部变量只在局部作用域内有定义。函数每次执行都会重新创建函数内所有定义的变量(ps:函数其实也是一种变量,因为它是变量名指向一个函数方法),所有的变量创建后加入到自身AO对象中,这个AO对象就是函数执行的上下文,当函数执行完之后,AO对象会被销毁,局部变量就会被释放。除非形成了闭包使这个局部变量一直被储存在内存中。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值