js闭包详解

首先抛出两个问题:js的闭包是什么?闭包的提出有什么作用?

闭包是什么?

闭包是由返回函数或者自执行函数以及声明该函数的词法环境组合而成的,个人认为这个解释非常助于理解,字面去解释这句话,就是闭包是一个集合体,包括一个函数,还有所有这个函数用到的东西(变量等等)。说到这里可能还是有些模糊,没事,我们从具体的例子里去看

function d(){
 return function(){}
}
var x = d
var y = d 
console.log(x === y)

将函数d赋值给变量x和y,理所当然,函数是引用传递,于是乎函数d的地址被赋值给了x和y,两个变量都是相同的指针,所以结果是true
对上述例子修改一下

function d(){
 return function(){}
}
var x = d()
var y = d()
console.log(x === y)

这回结果就变成false了,为什么呢?我们可以看到,x和y是用函数d的执行返回值赋值的,函数d内部返回了一个空的匿名函数,既然都是一样的函数,为什么结果会不一样呢?答案就在于,x和y的值其实是函数d返回的两个闭包,这两个闭包有相同的空匿名函数,但是它们的词法环境不一样(哪怕对于这个例子不存在其他变量),于是这两个闭包是完全不等的。这就是闭包!
我们再来看看词法环境有变量的例子

function c() {
	var x = 1
	return function b() {
		console.log(x)
	}
}
var d = c()
d()

第一眼看去,函数c返回一个函数b,并把这个函数赋值给了变量d,然后d执行,按照其他语言的经历,这里应该是会出错的,因为x是在函数c内定义的,其声明周期只存在于函数c的执行过程才对,在执行var d = c()时,函数c就执行完毕了,变量x应该从堆栈中销毁了,后续执行函数d(也就是返回的函数b),应该是找不到x的。但是情况并不是这样,前面我们说过,闭包包括返回的函数,还有这个函数的词法环境,于是函数b携带着其词法环境(比如这里的变量x)组成一个闭包赋值给d,这样,d执行的时候变量x是依然存在并且可以访问的(仅限于该闭包),这些都是比较通俗的解释,详细的可以看看这篇文章

闭包的作用

1.对于一些只有一些参数不同,但是功能相同的函数,我们不需要单独去声明,那样会非常繁琐,利用闭包可以声明一次然后多次调用,传入不同的参数即可,例如

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12,size14 和 size16 三个函数将分别把 body 文本调整为 12,14,16 像素。我们可以将它们分别添加到按钮的点击事件上。如下所示:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

2.实现对于某些变量或者函数的私有化封装。js并不像那些面向对象语言一样有封装好的私有成员,但是借助闭包,我们模拟私有变量和私有方法

function a(){
	var x = 1 
	function b(val) {
		x = x + val
	}
	return {
		value: function() {
			return x
		},
		add: function(val) {
			b(val)
		},
		sub: function(val) {
			b(-val)
		}
	}
}
var c = a()
console.log(c.value()) //1
console.log(c.add(3))
console.log(c.value()) //4
console.log(c.sub(1))
console.log(c.value()) //3

上面的例子中,我们利用闭包返回了一个包含三个函数的对象,但是这三个函数共享一个变量和函数,但是它们的词法环境还是不一样的,只不过三个词法空间中对于变量还有函数b的作用链的引用是相同的。这样,我们可以通过函数a得到一个对象,对象内有三个闭包,通过这三个闭包我们可以访问和修改a的局部变量x,并且通过其他途径我们访问不到x,这就使得x成为了一个私有变量,函数b成为了私有函数。

循环中常见的闭包问题

在es6引入let和const之前,一个在循环中常见的错误是

function a() {
	var arr = []
	for(var x = 1;x < 4;x++) {
		console.log(x)
		arr.push(function (){
				return x*x
			}
		)
	}
	return arr
}
var array = a()
console.log(array[0]())
console.log(array[1]())
console.log(array[2]())

结果是在这里插入图片描述
为什么呢?不是说闭包会使得变量生命周期延长,并且存在于词法空间吗?没错,从答案来看,变量x确实在函数a执行完之后仍然能够被访问到,那为什么结果不像我们预期的那样是1,4,9呢?我们先来分析一下这个例子。第一步:函数a返回了一个数组,数组里面有三个匿名函数,这三个函数分别与自己的词法环境构成三个闭包,由之前学到的var的变量提升我们知道,变量x是在函数a内的变量,这个x在三个闭包的词法环境作用链中存在(并且三个作用链中的活动变量x是同一个引用),而且闭包中的函数用到了x。第二步:在a执行过程中,仅仅是把三个函数推到了数组中,并没有执行函数,在函数a执行完之后,变量x仍然存在于内存空间中,并且在for循环中以及累加到了4。第三步:后续打印的过程中才是正式执行数组中闭包函数的过程,在这个过程中,js引擎解析代码行x*x,在自己的作用域链中找到了变量x(也就是之前提到的词法环境中的x),但是此时x的值因为第二步已经变成了4,所以最后三个闭包函数执行结果都是16.

那么如何解决上述的问题呢?几个方向:引入let使得变量变成块级作用域的变量,在形成闭包时对于作用域链中的活动变量x就是三个不同的引用了(因为循环三次,每次都会重新声明一个变量x);再次使用闭包,将每次循环的x的值绑定到新闭包中;

闭包的优缺点

闭包的优点

1.可以重复使用变量,并且不会造成变量污染

2.可以用来定义私有属性和私有方法。

闭包的缺点

比普通函数更占用内存,会导致网页性能变差,在IE下容易造成内存泄露。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SuperHaker~

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值