• 微信号:wumiao_357234902
您当前的位置:首页>web前端开发>JavaScript

js作用域、作用域链、闭包

作者:Miao 阅读:3057次

作用域可以分为函数作用域和全局作用域:

  • 全局作用域:代码在程序任何地方都能访问,window对象的内置属性都属于全局作用域

  • 函数作用域:在固定的代码片段才能被访问

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。一般情况下,变量会到创建这个变量的函数的作用域中取值,如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

function fn() {
	console.log(x);
}
function show(f) {
	var x = 20;
	f();
}
show(fn);

上面的代码中,x现在fn作用域中查找,没有找到值,所以向上级作用域(全局作用域)中查找,找到x=10,所以这里会输出10。

闭包是一种特殊的对象。它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。因此,闭包就是指函数作用域中的内部变量被另一个函数访问。我们通过一个例子来详细了解:

var fn = null;
function foo() {
	var a = 2;
	function innnerFoo() {
		console.log(a);
	}
	fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}

function bar() {
	fn(); // 此处保留innerFoo的引用
}

foo();
bar(); // 2

在上面的例子中,foo()执行完毕之后,按照常理,其执行环境生命周期会结束,JavaScript拥有自动的垃圾回收机制,它所占内存被垃圾收集器释放。但是通过fn = innerFoo,函数innerFoo的引用被保留了下来,复制给了全局变量fn。这个行为,导致了foo的变量对象,也被保留了下来。于是,函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象,所以此刻仍然能够访问到变量a的值。这样,我们就可以称foo形成了一个闭包。

虽然例子中的闭包被保存在了全局变量中,但是闭包的作用域链并不会发生任何改变。在闭包中,能访问到的变量,仍然是作用域链上能够查询到的变量。我们将上面的例子稍作调整,如果我们在函数bar中声明一个变量c,并在fn中试图访问该变量,运行结果会抛出错误,因为变量c没有在innnerFoo的作用域链上。

var fn = null;
function foo() {
	var a = 2;
	function innnerFoo() {
		console.log(c); // 在这里,试图访问函数bar中的c变量,会抛出错误
		console.log(a);
	}
	fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}

function bar() {
	const c = 100;
	fn(); // 此处保留innerFoo的引用
}

foo();
bar(); // 2

闭包有如下使用场景:

  • 循环中使用异步事件,在事件执行机制中我们说过,异步事件会进入Event Table并注册函数,等主线程执行完后才会调用此事件队列,所以需要通过闭包保存循环中的某些变量以供异步事件使用。

  • setTimeout,原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。

这里我们通过一个循环闭包的经典题目来讲解以上两种场景:

// 利用闭包,修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5
for (var i = 1; i <= 5; i++) { 
	setTimeout(function timer() { 
		console.log(i); 
	}, i * 1000); 
}

我们知道在函数中闭包判定的准则,即执行时是否在内部定义的函数中访问了上层作用域的变量,因此我们只需要2个操作就可以完成题目需求,一是使用自执行函数提供闭包条件,二是传入i值并保存在闭包中。

for (var i = 1; i <= 5; i++) { 
	(function (i) { 
		setTimeout(function timer() { 
			console.log(i); 
		}, i * 1000); 
	})(i) 
}

前面我们说到原生的setTimeout传递的第一个函数不能带参数的问题,所以这里我们也可以在setTimeout的第一个参数处利用闭包。

for (var i = 1; i <= 5; i++) { 
	setTimeout((function (i) { 
		return function () { 
			console.log(i); 
		} 
	})(i), i * 1000); 
}

此外,闭包还可运用于封装私有变量、模块化与柯里化。

本站部分文章、数据、素材收集于网络,所有版权均归源网站或原作者所有!

如果侵犯了您的权益,请来信告知我们下线删除,邮箱:357234902@qq.com

标签:JavaScript