每次你随口抱怨一句“昨晚梦到好吃的了”,我都能在第二天精准地把那款零食摆在你面前——你肯定觉得我会读心术,但其实,我只是在心里悄悄给你建了个“专属备忘录”。JavaScript 里的闭包也是干这事的:当一个函数在内部诞生时,它也会偷偷带上一个记满外部世界信息的“小本本”,哪怕创造它的外部函数早就下班离场了,它依然能靠着这个本本,随时找回那些本该消失的变量。它不是魔法,而是一种深情的机制——今天,就让我用最日常的方式,把这个“永远记得你喜好”的代码秘密讲给你听。
一、 作用域链
在讲闭包之前我们先来了解作用域链的概念,我们先用一段代码和 V8 引擎执行的流程图来理解
function bar (){
console.log(myname);
}
function foo (){
var myname = '猪猪侠'
bar ()
}
var myname = '小呆呆'
foo()
根据图中所画, outer 这个指针指向的是全局执行上下文 GO,为什么指向这?那我们需要了解词法作用域。
作用域:变量的查找规则与可访问范围。
词法作用域:函数的作用域在定义时就已确定,而非调用时。
当函数寻找变量时:先在当前词法环境找 -> 找不到则顺着 Outer 往上找 -> 直到全局环境
注意:词法环境也是一个栈结构,依旧遵循先进后出的规则。
作用域链:每一个执行上下文的变量环境中都存在一个 outer 指针,用来指向外部的执行上下文,当 V8 在查找一个变量时,当前执行上下文中没有找到,就会顺着 outer 所指向的那个执行上下文查找,以此类推直到找到全局为止,我们把这个查找的链条称为作用域链。
二、闭包
在聊闭包是什么之前我们先要了解 V8 引擎执行的机制
1.一个函数执行完毕后,它的执行上下文就会被销毁
2.根据作用域的查找规则,内部函数一定可以访问外部函数中的变量
function foo() {
var myname = '猪猪侠'
var age = 18
function bar() {
console.log(myname);
}
return bar
}
var baz = foo()
baz ()
根据上述代码我们来画一下它执行时调用栈的形式
因为 V8 引擎的机制1, foo 函数已经执行完,然后被销毁接着生成 bar 函数的全局执行 AO,又因为 bar 函数的作用域在 foo 函数作用域里面,而 bar 需要访问的值在自己的全局执行 AO 里面找不到,所以需要去 foo 里面找,而 foo 的全局执行 AO 已经销毁,无法访问,这和机制2互相矛盾 ,所以此时 V8 引擎会创建一个 closure (闭包)来存放 bar 需要访问的变量。
ok,这是闭包的形成的过程我们现在来进行总结一下什么是闭包?
闭包:当一个外部函数中的内部函数被拿到外部函数之外来执行,哪怕外部函数已经执行完毕了,被内部函数引用的那部分变量依然需要被保留,我们把这部分变量集合称为闭包。
了解清楚闭包是干什么的之后,我们将下面的代码用闭包的方法改为输出的值是 1,2,3,4,5
var arr = []
for (var i = 1; i <= 5; i++) {
arr.push(function() {
console.log(i);
})
}
for (let n = 0; n < arr.length; n++) {
arr[n]() // function() {console.log(i)} ()
}
在未更改之前,这个输出的是 6,6,6,6,6,这是因为我们全局定义了一个 i 在执行的过程中 i最终一直加到了 6 然后循环结束然后开始输出 i 的值。而要输出 1,2,3,4,5 我们需要将 i 每次执行结束的值保留不随着函数执行完毕之后 i 的值被销毁,所以这时我们只需要生成一个闭包就能解决
function fn(j) {
arr.push(function() {
console.log(j);
})
}
fn(i)
闭包的优缺点:
1.优点:定义私有模块,防止全局变量污染
2.缺点:内存泄漏,闭包会导致函数中的变量一直被占用,不能被垃圾回收机制回收
三、总结
说到底,闭包就像是代码世界里的一场“久别重逢”。普通变量的宿命,是随函数的出栈而灰飞烟灭,仿佛从未来过;但闭包却打破了这生死界限,它让内部函数在诞生之际,就把外部世界的温柔打包成了一份专属备忘录,贴身珍藏。所以,哪怕外面的函数早已下班离场,只要内部函数还在,那些本该消散的变量就始终有处可逃。它记住的不仅仅是数据,更是一段曾经的上下文与连接,让状态在函数的传递中得以延续,这就是为什么它能帮你实现数据的私有化,也能让防抖节流里的定时器精准记住上一次的呼吸。当然,深情是有代价的,这份念念不忘意味着内存的常驻,所以别当囤积狂,不需要的时候记得主动把备忘录清空(置为 null),把空间还给世界。闭包,就是 JS 赋予代码的一种深情:只要你还被需要,我就绝不消失。