详解JavaScript中的作用域链与闭包
大眼睛图图 人气:0作用域链
首先来看看这段代码:
var a = '喜羊羊'; function A(){ console.log(a); a = '美羊羊'; function B(){ console.log(a); } B(); } A();
在这里毫无疑问结果肯定是我们想到的先打印喜羊羊,再打印美羊羊。因为作用域链嘛,如果当前层没找到,那么就去当前层的上一级找。
那么再看这道
function bar() { console.log(myName) } function foo() { var myName = "极客邦" bar() } var myName = "极客时间" foo()
是不是感觉是打印极客邦?如果是的话,那么恭喜你,掉坑里了。(还不赶快爬起来,补一补作用域链的知识)。
为什么打印不是极客邦而是极客时间呢?
既然问题出现在了对作用域链的理解上,那么就再回到作用域链的定义上吧。
其实在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用称为 outer。
比如上面那段代码在查找 myName
变量时,如果在当前的变量环境中没有查找到,那么 JavaScript 引擎会继续在 outer 所指向的执行上下文中查找
为了直观理解,你可以看下面这张图:
看到这张图我猜你又纳闷了,为什么bar
函数创建的执行上下文中的outer会指向全局??
哈哈哈,这里就要涉及到了词法作用域了
词法作用域
词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
这么讲可能不太好理解,你可以看下面这张图:
从图中可以看出,词法作用域就是根据代码的位置来决定的,其中 main
函数包含了 bar
函数,bar
函数中包含了 foo
函数,因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:foo 函数作用域—>bar 函数作用域—>main 函数作用域—> 全局作用域。
明白了词法作用域,那么我们再回到刚刚的问题。
为什么bar函数创建的执行上下文中的outer会指向全局
这是因为根据词法作用域,而词法作用域又是根据代码的位置,而bar函数代码的位置就是包裹在全局下,而喜羊羊那个例子中的B函数是在A函数的环境下,所以会造成它们的词法作用域链不同,也就导致函数作用域链不同了。
所以我们才有那句话词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系。
也就是只和代码位置有关,和函数直接如何调用没关系
闭包
老生常谈的问题,这次再从一个更深入的角度来理解一下。
看下面这段代码:
function foo() { var myName = "极客时间" let test1 = 1 const test2 = 2 var innerBar = { getName:function(){ console.log(test1) return myName }, setName:function(newName){ myName = newName } } return innerBar } var bar = foo() bar.setName("极客邦") bar.getName() console.log(bar.getName())
这段代码乍一看没有什么问题,但是这里有一个细节很多人会忽视。
在foo()执行完将返回值给bar
时,这里foo函数会从调用栈中弹出,变量都会被回收。既然变量都被回收了,那么bar.setName()
这些调用方法从何而来??
foo执行完后的情况可以参考下图:
从上图可以看出,foo
函数执行完成之后,其执行上下文从栈顶弹出了,但是由于返回的 setName
和 getName
方法中使用了 foo 函数内部的变量 myName
和 test1
,所以这两个变量依然保存在内存中。这像极了 setName
和 getName
方法背的一个专属背包,无论在哪里调用了 setName
和 getName
方法,它们都会背着这个foo
函数的专属背包。
之所以是专属背包,是因为除了 setName
和 getName
函数之外,其他任何地方都是无法访问该背包的,我们就可以把这个背包称为 foo 函数的闭包。
好了,现在我们终于可以给闭包一个正式的定义了。在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包 比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
用一句话概括就是
能够访问其他函数内部变量的函数,被称为 闭包。
(我们理解可以这么理解,但是和面试官说的当然可以把这个例子说一下,这直接上升到了一个理解什么是闭包的新高度了)
加载全部内容