js什么是闭包?简单理解

闭包
作用域链和执行上下文

理解闭包前,先引入一个概念,作用域链

用我自己理解的讲:在一段程序中,程序内的变量、函数等都被串在这条链上,当我们使用这些变量、函数时,程序就会在这条链中搜索,如果没有找到你调用的变量或函数,就会出现问题

举个例子:

var a = 0,b = 1
function add(a, b){
	return a + b
}
//上面的a,b还有add就被串在了这个程序的作用域链中
console.log(add(a, b))
//调用add(a, b),程序就会在作用域链上搜索,找到add方法和a、b变量

这里我们的变量和函数都是定义在全局中(最外层)的,如果定义在函数内呢?

var a = 0
function foo(){
	var bar = 0
	console.log(a)//0    
}
//在foo外层访问bar变量
console.log(bar)//报错Uncaught ReferenceError: bar is not defined

为什么在这里报错说bar没有定义呢?

按照我的理解,作用域链分为子作用域链和父作用域链,本例变量a和函数foo串在外层作用域链中,而bar串在foo函数内的作用域链中,两条链的关系就和父子一样
在这里插入图片描述

当我们在全局(最外层)中访问a变量(console.log(a)),程序就会优先在全局作用域链(也就是上面的父作用域链)中查找a变量

当我们在foo函数中访问bar变量(console.log(bar),程序就会优先在foo作用域链中查找bar变量

到这里一切安好,直到我们在全局中访问foo函数中的bar变量,就出了问题,bar定义在foo作用域链中,程序在父作用域链中进行搜索,并不会进入子作用域链中,自然是搜索不到的,程序就会认为我们没有定义这个变量,就会报错

但我们在foo方法中访问a变量,为什么又可以了呢?

原来程序不仅仅会在子作用域链中搜索,还会上升到父作用域链,这时候foo链中找不到,就会去全局链中查找,自然能访问到变量a

到这里我们了解了程序在作用域链上的搜索是有范围的,它的范围就称作JavaScript中的执行上下文(简称上下文)

变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为。(《JavaScript高级程序设计(第四版)》)

到这里就很明确啦,js中的函数有属于自己的作用域,在它自有的作用域中可以访问外部,但外部不能反向访问函数内部的变量

明白了作用域链和执行上下文的概念,接下来就看看闭包是什么

闭包

匿名函数经常被人误认为是闭包(closure)。闭包指的是那些引用了另一个函数作用域中变量的函数。(《JavaScript高级程序设计(第四版)》)

function foo(){
	var a = 0
	return function(){
		return a++
	}
}
var bar = foo()
bar()
console.log(a)//1
bar()
console.log(a)//2

这个例子乍一看有些奇怪,且等我为你一一道来

第一步很简单,我们定义了一个foo函数,在foo函数里定义了一个a变量

第二步,我们return了一个返回值,与往常不同的是,这次返回的值是一个匿名函数,匿名函数中实现了a++的功能

第三步,在下方我们定义了bar,调用foo函数,让它等于foo函数的返回值,也就是我们在第二步中定义的匿名函数,现在我们就可以认为,bar是一个函数,是foo中返回的匿名函数

第四步,我们就可以调用bar方法,实现a++的操作

现在你可能不明白我想要做什么,不要急,思考一个问题:这个例子的作用域链是什么样的呢?试着画一画

答案:

bar被赋值了foo中的匿名函数,bar自然就连接在了foo函数的作用域链上,根据我们上面提到的,作用域链内部可以访问外部,也就是说,bar可以访问foo中的变量a,也可以访问外部的全局变量

那我们为什么不直接在全局中定义a变量,然后对它操作呢?

实际上,如果我们将每个要使用的变量都定义为全局变量,就会导致全局变量过多,这对维护与优化来说并不是好事情

使用了闭包,我们将要使用的变量放在函数作用域中,使用函数作用域返回的函数来操作这个变量,就避免了将变量定义在全局中,在我们不需要它的时候直接为bar赋值为null,没办法再调用到那个匿名函数,程序就会自动检测到并回收foo作用域链,为我们清理出内存

这里对作用域链与闭包只做了简单介绍,像更深入理解请移步红宝书(《JavaScript高级程序设计(第四版)》)

扩展

使用自执函数优化闭包:

var bar = (function(){
	var a = 0
	return function(){
		a++;
	}
})
bar()
console.log(a)//1
bar()
console.log(a)//2

与前一个例子中不同的是,我没有定义foo函数,直接用一个自执行函数替代了foo,我们需要的只是foo对应的函数作用域链,它本身我们并不关心,所以我们可以像本例这样进行简写

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

幸运的小何

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

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

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

打赏作者

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

抵扣说明:

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

余额充值