彻底理解js闭包并应用,吊打面试官!

目录

闭包

我的理解分为3点:

比较一下例1和例2

其实我们经常使用闭包

 循环中闭包的使用

下面稍作修改就可以达到我们想要的效果

应用

欢迎指正!!


闭包

给人的感觉是很熟悉却又抓不到的感觉,都知道它很神奇,用了这么多年闭包却也没有真正意义上去分析他、总结它,今天就来总结一下!

书中的定义:当函数可以记住并访问所在的作用域时,就产生了闭包(参考于“你不知道的JavaScript”)。

我的理解分为3点

  1. 存在函数嵌套
  2. 内部函数存在对外部函数的引用
  3. 在当前作用域以外依然能够执行

前两点我认为是形成闭包的条件,第3点是闭包存在的意义或者说应用;下面用例1 分析一下:

		function fruits(){
			var n = 1;
			function apple(){
				console.log(n);
			}
			
			apple();//执行apple
		}
		
		fruits();//执行fruits

执行这段代码的话,会输出 1 

看看这个是闭包吗?

首先apple函数嵌套在fruits里面,满足条件1存在函数嵌套;

其次在apple函数里面对fruits作用域里面的n进行了引用,满足条件2,并且最终的输出正确值1。

但我们注意apple函数的执行却是在fruits函数的作用域里面,我的理解是有闭包的条件,却没有闭包的应用或者说闭包的作用。

修改一下代码,就可以变成闭包,例2

		function fruits(){
			var n = 1;
			function apple(){
				console.log(n);
			}
			//返回这个函数apple
			return apple;
		}
		
		var a = fruits();//执行fruits
		//执行a 实际就是执行apple函数
		a();

这里对例1稍微做了一些修改,就变成了例2,这例2的代码也同时满足之前讲的闭包条件1和2,存在函数嵌套,apple函数也对n进行了引用,注意:apple函数在fruits函数的最后返回了,那么var a = fruits(); 其实就是让a指向了apple函数,执行a就等于执行apple函数。

执行例2的代码,结果是 1,与例1结果是一样的,我们看语句 a() ,说明apple函数是在fruits函数作用域之外的作用域执行的(这里就是全局window),却能正确的引用到n,这就是闭包的效果。

比较一下例1和例2

在例1中fruits()执行完以后,fruits整个内部作用域会被回收,因为引擎有垃圾回收器来释放不在使用的内存空间。

在例2中fruits()执行完以后,内部作用域会一直存在,因为apple本身在使用这个内部作用域,依然持有对该作用域的引用,所以不会被回收。

 

其实我们经常使用闭包

只是可能没有太注意而已,在我们写过的代码中肯定有很多闭包的身影,比如定时器、事件监听、ajax请求等很多使用了回调函数的地方,实际上就是在使用闭包,如下例:

例3

function sayHello(name){
	setTimeout(function(){
		console.log('hello '+name);
	},1000)
}
sayHello('大帅哥');

//延时1秒后输出结果:hello 大帅哥

 循环中闭包的使用

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

看到这个段代码,我们的预期是每秒一次,分别输出1-10,但实际上是每秒一次输出11,总共10次11,为什么?

因为setTimeout是延时函数,是会在循环结束以后再执行,循环结束后 i 的值是11,那么每次当然就输出11了。

在循环中的10个函数都对 i 进行了引用,并且他们都用的是同一个 i,比如第一次循环的时候 console.log(i) 此时对 i 的引用是1,但是的当第2次循环的时候i被修改为2了,那么第1次循环对i的引用也是指向这个i的自己也是2,以此类推终止循环终止的时候i是11,而setTimeout里面的回调函数是在循环终止后才执行,所以每次都会是11了。

 

下面稍作修改就可以达到我们想要的效果

第1种方式(副本的方式):

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

就是将循环里面的函数用自执行函数包裹起来,形成自己的作用域,然后在此作用域里面用copy一个副本 j,用来存储 i 的值,这样就可以达到想要的效果:

 

第2种方式(传参的方式):

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

将 i 做为实参传入自执行函数中,此时 i 的值会被形参 j 引用,在这个新的作用域里面封闭起来,被回调函数引用,执行代码的话也能得到同样的结果。

第3中方式(let):

for(let i=1;i<=10;i++){
		setTimeout(function(){
			console.log(i);
		},i*1000);
}

因为let在循环的头部,每次迭代都会声明,let有自己的块级作用域。

应用

function phone(name){
	function call(friend){
		console.log(name+" 打电话给 "+friend)
	}
	function msg(friend){
		console.log(name+ " 发短信给 "+friend)
	}

	return {
		call:call,
		msg:msg
	}
}
var f1 =  phone('张三');

f1.call('王五');
f1.msg('赵六');

var f2 =  phone('李四');
f2.call('王五');
f2.msg('赵六');

执行结果:

可以看到当执行phone()函数,会返回一个对象,此对象对外暴露了两个引用 call和msg,这个对象我们可以看做是API,在内部作用域里面的定义自己想要的变量等将会对外封闭,只有暴露的才可以被使用;所以我们当然可以在phone里面再添加其他的变量和函数,然后通过暴露的方式来进行添加、修改、删除等,而且在实际工作做我们也会经常这么做。

欢迎指正!!

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程界小明哥

请博主喝瓶水,博主持续输出!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值