遥忆前尘,闭包如梦:揭开 JavaScript 执行栈
引子
看到24这个独特的数字,总能想到那道刚毅的身影。触摸以往的照片,犹在耳畔的声音,日后想起ECUT,通往基地的小径,事物虽已远去,仍会有所留恋。闭包所指,亦大同小异。
Ch1 调用栈
闭包从何而来 不妨先深入调用栈
我们先来看一段代码
var a=2
function add( ){
var b =10
return a+b
}
add()
让我们编译一下
-
1 创建全局执行上下文 然后寻找变量声明 函数声明 执行函数 把2赋值给a 然后执行add()又要函数预编译
-
2 创建add执行上下文 推入栈顶 寻找变量声明 函数声明 执行并将10赋值给b
-
3 return a+b 我们先要找到a 从函数体add执行上下文的词法环境开始寻找->变量环境->找不到->?全局执行上下文? 的词法环境(为何是全局呢 下文分解)->变量环境,终于找到了,那么b也同理
-
4 最后返回一个 12 相信大家也都做到了
Ch2 还有一件事:作用域链
既然还有一件事 那我们看看到底怎么个事儿_(:з」∠)_
当一个函数在 JavaScript 中被创建时,它会创建一个作用域链(Scope Chain),这个作用域链决定了函数可以访问哪些变量和函数。作用域链是由当前执行环境的变量对象和所有外层环境的变量对象组成的链表。
让我们更详细地解释一下作用域链的构成和工作原理
-
函数创建和预编译:当函数被定义时,JavaScript 引擎会在函数执行前进行预编译。这个过程包括创建函数对象和确定函数的作用域链。在预编译阶段,引擎会创建一个执行上下文对象(Execution Context),其中包含了函数执行时的环境信息,如变量、函数声明、作用域链等。执行上下文对象中的变量环境会被初始化为一个新的空间,称为活动对象(Activation Object)或者变量对象(Variable Object)。
-
outer 属性:在函数对象的变量环境中会有一个内定的属性 outer,用于指示该函数的外层作用域是什么。这个属性指向了当前函数所处的词法作用域的外层作用域.
-
词法作用域决定 outer 属性:函数的 outer 属性的指向是根据词法作用域来确定的。词法作用域是在函数定义时确定的,它决定了函数能够访问哪些变量和函数。当函数在代码中被定义时,它会捕获其定义时所处的作用域链,因此 outer属性会指向定义时所在的外层作用域。
让我们深入了解一下
在上面代码中 当我们执行到log(a)时 我们就要去寻找a的值了 我们可以看见bar的执行上下文里面并没有看见 a 小子,我们就要去查查这家伙哪来的,跑得了和尚跑不了庙,我们发现bar函数的外层就是全局了,所以我们去全局上下文寻找,(先去)词法环境没有,诶,在(后去)变量环境中找到了,于是乎,log(200)。
Ch3 主角 闭包来了
千呼万唤始出来 还是先不告诉你
先看
我们先直接来分析代码 当我们执行到最后一行baz() 的时候 baz是foo()执行结果的返回值,也就是说baz()==bar(),在我们执行baz()前 我们需要想到 当一个函数执行结束后,它的执行上下文是需要被销毁的,所以此时此刻foo执行上下文应该推出栈(此时baz执行上下文还没入栈),然后执行baz(),预编译,执行,发现不管在baz()还是全局执行上下文都已经找不到count,按照我们逻辑来说此时应该会报错(ReferenceError:count is not defined) 但是我们执行代码的话 却是
我们会发现 明明应该被销出栈的foo执行上下文并没有被销毁?
正是栈里好诈骗 落花时节又逢君
其实呢,它没有完全销毁,它还有个挂念,所以留下了一点残魂?({count:1,age:18})
可是为什么是18呢 baz()不是在全局上下文吗?
实际上,JavaScript 中的执行上下文是在函数调用时创建的,而不是在编译时。当你调用baz()时,会创建一个新的执行上下文来执行 bar函数。这个执行上下文包含了bar函数执行时的环境信息,如变量、函数声明、作用域链等。 所以,当你调用 baz() 时,实际上是调用bar() ,会创建bar 函数的执行上下文,并且在调用栈中推入这个执行上下文。
那么现在问你 闭包是什么呢
- 根据js词法作用域的规则 内部函数总是能访问外部函数中的变量
- 当通过调用一个外部函数返回的一个内部函数后( 也不一定要return 即在函数外部调用该函数内部的函数)
- 即使外部函数执行已经结束了 但是内部函数引用了外部函数中的变量也依旧需要被保存在内存中
- 我们把这些变量的集合叫做闭包
结论
闭包是 JavaScript 中一个强大而重要的特性。它使得函数可以记住并访问其词法作用域中的变量,尽管外部函数已经执行完毕。理解闭包有助于我们更好地掌握 JavaScript 的作用域规则和函数行为,为编写更高效、灵活的代码提供了坚实的基础。
end 人生如闭包
个人看来,某种程度上,每一段的经历与成长,都是人生这段代码中不可或缺的部分。即使某个片段已经完成,它的价值和意义仍然保存在我们的记忆和心灵深处,随时准备在未来某个时刻被重新调用,赋予我们智慧和勇气。
转载自:https://juejin.cn/post/7372523264067649545