你不知道的Javascript(上卷)-闭包笔记

认识闭包

当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。

function foo(){
	var a = 2;
	function bar(){
		console.log(a);
	}
	return bar;
}
var baz = foo();
baz(); //2

函数bar()的词法作用域能够访问foo()的内部作用域,在foo()执行后,通常会期待foo()的整个内部作用域都被销毁,但是闭包可以阻止其发生,因为bar()仍然在使用内部作用域。

bar()依然持有对该作用域的引用,而这个引用就叫做闭包。

无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。

function foo(){
	var a = 2;
	function bar(){
		console.log(a);
	}
	bar(baz);
}

function bar(fn){
	fn();
}

这个例子中,内部函数baz传递给bar,当调用这个内部函数时,它涵盖的foo()内部作用域的闭包就可以观察到了,因为它能够访问a。

间接传递函数

var fn;
function foo(){
	var a = 2;
	function baz(){
		console.log(a);
	}
	fn = baz;
}

function bar(){
	fn();
}
foo();
bar();//2

总结:上述产生闭包的三种手段:1.通过return返回内部函数;2.将内部函数当做实参传递;3.将内部函数赋值到另一个变量中

循环和闭包

想一想下面这段代码

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

这段代码在运行时会以每秒一次的频率输出五次6。

这是为什么呢?

实际上,当定时器运行时即使每个迭代中执行的是setTimeout(),所有的回调函数依然是在循环结束后才会被执行,因此会每次输出一个6出来。

这里的缺陷是我们试图假设循环中每个迭代在运行时都会给自己“捕获”一个i的副本。但是实际情况却是,五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际只有一个i。所以我们需要更多的闭包作用域,特别是在循环过程中每个迭代都需要一个闭包作用域。

我们可以对这段代码进行改进

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

在迭代内使用IIFE会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们使用。

使用let劫持块作用域
function(var i = 1; i <=5 ;i++){
	let j = i;
	setTimeout(function timer(){
		console.log(j);
	},j*1000)
}

需要注意的是,for循环头部的let声明还会有一个特殊的行为,这个行为指出变量在循环过程分钟不止被声明一次,每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值