引言
在JavaScript编程的世界中,经常会听到一个神秘的词汇------"闭包"。对于初学者来说,闭包可能听起来非常复杂和难以理解。但实际上,闭包是一个非常强大却不那么复杂的概念。在这篇文章中,我们将揭开闭包的神秘面纱,让你明白闭包的工作原理,以及如何简单地使用它~
在学习闭包之前我们需要先了解什么是js的预编译以及调用栈是怎么样的,如果你还不了解这一点请先来复习一下吧~ var的变量提升底层的原理你知道吗?------带你深入底层了解预编译
闭包
接下来我们会根据这一小段实验代码来先简单认识一下闭包~
js
function foo () {
var myName = "旭旭"
let test1 = 1
let test2 = 2
var innerBar = {
getName: function () {
console.log(test1)
return myName
},
setName: function (newName) {
myName = newName
}
}
return innerBar
}
var bar = foo()
console.log(bar.getName())
bar.setName('杰哥')
console.log(bar.getName())
大家可以先猜一下这一段代码的运行结果会是什么~
接下来我们通过调用栈的方法直接开始分析:
我们先编译全局,生成全局执行上下文
并压入栈底
然后从上到下执行全局,执行到bar
处时我们调用了foo
函数,编译foo执行上下文
压入栈底:
然后我们的bar获取了foo的返回值对象,此时bar = {},此时foo的调用结束,foo执行上下文应该被出栈并销毁:
然后我们继续向下执行,bar调用了自身对象中的函数,并且输出了1 '旭旭'
,此时你会发现,不对呀?foo
已经被销毁了,其中的innerBar
也应该被销毁了,怎么还会有text1 = 1
存在呢?难道我们又生成了一个foo
执行上下文吗?
并不是这样的,事实上是由于foo
出栈销毁的时候,我们发现foo
的内部函数和数据存在被外部调用的可能,于是调用栈中的一部分空间为了避免之后找不到被外部调用的函数,保留了一部分空间储存它们:
没错!这个就像调用栈背了一个小背包一样的黄色方块就是闭包!
让我们规范化的描述一下:
在js中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量的, 当内部函数被返回到外部函数之外时,即使外部函数执行结束了,但是内部函数引用了外部函数的变量,那么这些变量依旧会被保存在内存中,我们把这些变量的集合成为闭包。
内存泄露
我们每次形成闭包我们都会发现,调用栈中的一部分内存被它偷偷占用了,"泄露"
并不是说内存被泄露出去了的意思,其实就是因为这一部分内存被占用没有被及时释放,导致不再使用的内存资源无法被回收,最终耗尽了可用内存。
我们要知道调用栈的内存是有上限的,一旦储存的执行上下文过多,或者说都被闭包把空间都"偷走了"
,那就会导致爆栈
!
所以我们在使用闭包的时候一定注意小心内存泄露的问题,我们只在需要使用闭包来实现某些必要的内容的时候使用闭包,千万不要滥用哦~
闭包的应用
如果在未来你需要参加面试,很幸运的是你被问到了闭包,并且给了你这样一道小题目:
js
var arr = []
for(var i = 0; i < 10; i++) {
arr[i] = function() {
console.log(i)
}
}
for(let j = 0; j < 10; j++) {
arr[j]()
}
现在你来想一想,我们第二个for循环调用arr[j]
的时候,你觉得会输出什么呢?
答案是:10 10 10 10 10 10 10 10 10 10
你是不是以为会输出 0到9
? 那么我们的题目就是怎么让它输出 0到9
如果你会作用域有所理解的话,你会知道之所以输出十个十,是因为var
在for
中会声明一个全局变量i,所以在之后调用arr[j]
的时候,我们会输出此时已经变成了10的i。
所以有一个最简单的解法就是把var i
换成let i
从而形成块级作用域,这样每次调用i时都是对应块中的不同i。
但是假如面试官问你你会另外一种方法吗?你该如何应对~
不过我们已经掌握了闭包!(疯狂暗示)
来吧展示:
假如我们使用闭包的思维,是不是也可以把每一次的i值都储存在闭包里呢?想法存在实践开始:
js
var arr = []
for(var i = 0; i < 10; i++) {
function a(i){
arr[i] = function() {
console.log(i)
}
}
a(i)
}
for(let j = 0; j < 10; j++) {
arr[j]()
}
我们来理解一下,我们采用了闭包的方式,我们每次调用函数a
的时候都有一个arr[i]
赋值为了一个函数,而arr
是声明在全局的,也就是说是函数a
的外部对吧,那么每次都会有一个闭包储存了一个形参i
没有被销毁,因为arr函数
输出了i
,它们是存在被外部调用的可能的,所以形成了对应的闭包
!
那么此时,我们在第二个for循环
调用arr[j]
的时候,成功输出了闭包中对应的i
值,也就是0 1 2 3 4 5 6 7 8 9!
到这里你如果全都明白了的话,恭喜你已经真正明白了什么是闭包,闭包真的可以这么简单!
总结
在js中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量的, 当内部函数被返回到外部函数之外时,即使外部函数执行结束了,但是内部函数引用了外部函数的变量, 那么这些变量依旧会被保存在内存中,我们把这些变量的集合成为闭包
那么文章到这里就结束啦~
如果你想了解更多这类文章,点赞关注作者更新更多后续~