You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
leta='global';functionouter(){letb='outer';functioninner(){letc='inner'console.log(c);// prints 'inner'console.log(b);// prints 'outer'console.log(a);// prints 'global'}console.log(a);// prints 'global'console.log(b);// prints 'outer'inner();}outer();console.log(a);// prints 'global'
理解 JavaScript 中的闭包
英文原文
https://blog.bitsrc.io/a-beginners-guide-to-closures-in-javascript-97d372284dda
闭包是 JavaScript 开发者应该知道和了解的基本概念。然而,它还是会使许多新手开发者感到困惑。
正确的理解闭包能够帮助你编写更好、更高效、更整洁的代码。相反地,它将会帮助你成为更好的 JavaScript 开发者。
所以,在这篇文章中,我将会深层的解析闭包以及它是如何在 JavaScript 中运行的。
现在,让我们开始吧。:)
什么是闭包
闭包是能够访问到它外层函数作用域的一个函数,甚至在外层函数已经返回之后也是可以的。这就意味着:闭包能够记录和访问到它外层函数的变量和参数,即使在外层函数已经运行结束。
在我们深入了解闭包之前,让我们先理解词法作用域。
什么是词法作用域
在 JavaScript 中,词法作用域或静态作用域指的是变量、函数和对象基于源代码中物理位置的可访问性。例如:
这里的
inner
函数能够访问到定义在它内部作用域的变量,outer
函数的作用域,以及全局作用域。并且,outer
函数能够访问到定义在它内部的变量以及全局作用域。所以,上段代码的作用域链如下:
注意到:
inner
函数被outer
函数的词法作用域所包围,同样地,outer
函数被全局作用域所包围。这就是为什么inner
函数能够访问到outer
函数定义的变量以及全局作用域。闭包的练习
让我们在深入闭包如何运行之前看一些例子。
Example 1
在这段代码中,我们调用
person
函数,其返回了内部的display
函数并且赋值给peter
变量。当我们调用peter
函数时(实际是display
函数的引用),'Peter' 将会打印在控制台上。但是在
display
函数内部,并没有任何变量名为name
的变量,所以该函数能够访问到它外层函数的变量,即使该函数已经被返回。所以,display
函数实际上就是一个闭包。Example 2
我们再一次地将
getCounter
函数返回的匿名内部函数赋值给count
变量。count
函数现在是一个闭包,它能够访问到getCounter
函数的counter
变量,即使在getCounter()
返回之后。但是注意到,在每一次
count
函数被调用时,counter
的值没有像通常那样被重置为0
。这是因为,在每一次调用
count()
时,就会创建一个新的作用域,但是getCounter
函数只创建了一个作用域。因为counter
变量是定义在getCounter
函数作用域内部的,它将会在每一次调用count
函数时进行自增,而不是重置为0
.闭包是如何运行的
到目前为止,我们已经讨论了什么是闭包以及闭包的例子。现在,让我们理解在 JavaScript 中闭包是如何运行的。
为了真正的理解 JavaScript 中闭包的工作机制,我们不得不理解两个最重要的概念。1)执行上下文、2)词法环境。
执行上下文
执行上下文是一个 JavsScript 代码解析和运行的抽象环境。当全局代码被执行时,它是运行在全局的执行上下文中,并且函数代码是在函数内部执行上下文中运行的。
目前只能有一个正在运行的执行上下文(因为 JavaScript 是单线程语言),执行上下文是通过栈的数据结构来进行管理的,我们称之为执行栈或调用栈。
执行栈本质上就是一个栈,其拥有着 LIFO 的数据结构(后进先出),栈中的元素只能通过栈顶来添加和删除。
当前运行的执行上下文永远是在栈顶,当函数完全运行完,它的执行上下文将会从栈顶移除,并且控制达到它在堆栈中下一个的执行上下文。
来看一个代码片段以便更好的理解执行上下文和堆栈:
当代码执行时,JavaScript 引擎创建了一个全局的执行上下文来执行全局的代码,当代码执行到
first()
函数时,它会为该函数创建一个新的执行上下文并且将其压栈到执行堆栈当中。所以,上段代码的执行堆栈如下所示:
当
first()
函数完成时,它的执行上下文从堆栈中移除,并且控制其下一个的执行堆栈---全局执行上下文。所以在全局作用域中剩余的代码将会执行。词法环境
每次 JavaScript 引擎创建一个执行上下文来执行函数或全局代码,它也会在函数运行时创建一个新的词法环境来存储定义在函数内部的变量。
词法环境是拥有 identifier-variable mapping 的数据结构。这里的, identifier 指的是变量和函数的名称,variable 指的是实际的对象(包含函数类型对象)或原始值。
词法环境拥有两个组件:(1)the environment record、(2)a reference to the outer environment.
environment record 是一个存储变量和函数声明的地方。
reference to the outer environment 意味着它能够访问到它父级的词法环境。这个组件对于理解闭包的运行原理有着非常重要的作用。
词法环境的概念如下所示:
现在,让我们再看看上段代码:
当 JavaScript 引擎创建了全局的执行上下文来执行全局代码,同时它也会创建一个新的词法环境来存储全局作用域中的变量和函数声明。所以,全局作用域的词法环境如下所示:
这里的外层词法环境为
null
, 是因为全局作用域没有外层的词法环境。当引擎创建了
first()
函数的执行上下文,同时它也创建了词法环境用来存储在函数执行时在函数内部定义的变量。所以,该函数的词法环境如下所示:该函数的外层词法环境被设置为全局的词法环境,是因为在源代码中,该函数被全局作用域所包裹。
注意 --- 当函数执行完,它的执行上下文从执行堆栈中移除,但是它的词法环境可能会被移除、也可能不会被移除,这取决于它的词法环境是否被其外层的词法环境中的属性里的任何其他词法环境所引用。
详细的闭包例子
现在我们理解了执行上下文以及词法环境,让我们回到闭包。
Example 1
看看下面的代码片段:
当运行
person
方法时, JavaScript 引擎为该方法创建了一个新的执行上下文和词法环境,在该方法运行结束后,他将displayName
函数返回并将其赋值给peter
变量。所以,它的词法环境如下所以:
当
person
函数运行完时,它的执行上下文会同时从执行堆栈中移除。但是它的词法环境依旧在内存中,这是因为,它的词法环境被它内部的displayName
函数的词法环境引用了,所以,它的词法环境在内存中依旧可用。当
peter
函数运行时(实际上就是displayName
函数的引用),JavaScript 引擎为该函数创建了一个新的执行上下文和词法环境。所以,它的词法环境如下所示:
因为
displayName
函数中没有变量,所以它的 environment record 为空。在该方法执行中,JavaScript 引擎将会在函数的词法环境中寻找变量name
.因为
displayName
函数中的词法环境内没有变量,所以它将会寻找它外层的词法环境,也就是person
函数的词法环境依旧在内存当中。JavaScript 引擎找到了变量并将name
打印在控制台上。Example 2
getCounter
函数的词法环境如下所示:该函数返回一个匿名函数并将其赋值给
count
变量。当
count
函数执行时,它的词法环境如下所示:当
count
函数被调用,JavaScript 引擎将会找寻该函数的词法环境内的counter
变量。因为它的 environment record 为空,所以 JavaScript 引擎将会找寻其外层函数的词法环境。JavaScript 引擎找到了变量,将其打印在控制台并会增加
getCounter
函数词法环境内的counter
变量。所以,在第一次调用
getCounter
函数之后,其词法环境如下所示:每一次调用
count
函数,JavaScript 引擎都会为count
函数创建一个新的词法环境,增加counter
变量值并且更新getCounter
函数的词法环境来反映变化。结论
目前,我们已经学习了闭包是如何运行工作的。闭包是 JavaScript 中基本概念,每一个 JavaScript 开发者都应该理解它。熟练掌握这些概念有助于你成为更好、更高效的 JavaScript 开发者。
The text was updated successfully, but these errors were encountered: