为什么在Javascript中声明之前可以访问变量?
为什么在Javascript中声明之前可以访问变量?
毫无疑问,当今最常用和最著名的语言之一是 JavaScript,如今它无处不在,我们可以创建 Web 应用程序和系统,在后端开发 API 并创建移动应用程序。
尽管它很受欢迎,但很多人不喜欢 JavaScript,主要是因为一些 特殊性 的语言与其他语言非常不同。
当我开始使用 JavaScript 时,让我感到困惑的一件事是 在声明之前使用变量或函数的可能性 而我相信很多人也觉得这有点奇怪。
在这篇文章中,我将尝试解释这是如何发生的。
执行上下文
在讨论变量和函数之前,我们需要了解一些关于 JavaScript 语言的概念,其中首先是上下文执行。
在 JavaScript 中,一个基本的执行单元是函数,我们一直使用它们来计算某些东西,执行副作用(如更改 UI),重用代码或使代码更易于理解。我们也知道一个函数可以调用另一个函数,而后者又可以调用另一个函数,以此类推……
当一个函数调用另一个函数时,代码执行必须回到调用它的位置,即:
当调用 对话
函数我们将调用另一个函数(在这种情况下 你好
), 当。。。的时候 你好
函数执行完毕,我们需要回到调用它的地方,即在 对话
功能并继续执行。
但是,你有没有想过 JavaScript 引擎如何跟踪所有这些函数的运行并返回到代码中的特定位置?
在 JavaScript 中有两种主要类型的代码:全局代码和函数代码。
全球代码
这段代码是在所有函数之外定义的,即它们在我们的 JavaScript 中是松散的。
功能码
此代码在函数内部定义。
当我们的代码被 JavaScript 引擎执行时,每个语句都在特定的执行上下文中执行,并且由于我们有两种类型的代码,我们也有两种类型的上下文: 全局执行上下文 和 函数执行上下文 .
它们之间最显着的区别是只有一个全局执行上下文,它是在 JavaScript 开始执行时创建的。 而对于每个函数调用,都会创建一个新的函数执行上下文。
因此,JavaScript 正是通过这些上下文来处理暂停、执行和回调。
我们知道 JavaScript 是基于 单线程执行模型 ,即一次只能执行一段代码。因此,每次调用函数时,都会暂停当前的执行上下文,并创建一个新的函数执行上下文,从中评估代码。在函数执行完它的任务后,也就是它的代码已经执行完毕,通常会丢弃该函数的执行上下文,并恢复之前的执行上下文。
所以有必要同时跟踪这两个上下文,也就是说,我们需要一个正在运行的执行上下文和另一个暂停的上下文。实现此功能的最简单方法是通过堆栈,称为 执行上下文堆栈 .
词汇环境
现在我们对执行上下文的工作原理有了更多了解,让我们来看看词法环境。
考虑以下示例:
在这种情况下,我们知道通过调用 控制台日志
函数会创建一个新的执行上下文,但是日志函数如何获取变量名的值呢?
这个过程称为 标识符解析 , 基本上的想法是 找出给定标识符引用的变量 ,执行上下文是通过词法环境来完成的。
词法环境是一种内部 JavaScript 机制 跟踪标识符到特定变量的映射 ,回到之前的代码:
访问变量名时会参考词法环境,即在 控制台日志
宣言。
词法环境是 JavaScript 作用域机制的内部实现,人们通常将它们称为作用域。
通常,词法环境与特定的代码结构相关联,它可以与函数、代码块或 catch(try/catch 的一部分)相关联,并且每个结构都可以有自己的标识符映射。
Javascript 中的变量类型
在 JavaScript 中,我们可以使用三个保留字来定义变量: 曾是
, 让
和 常量
.它们在两个方面有所不同:可变性以及它们与词汇环境的关系。
可变性
如果我们按照可变性方面对变量声明进行分类,我们可以把 常量
在一侧和 曾是
/ 让
在另一。
所有变量定义为 常量
是不可变的,也就是说,它们的值只能设置一次。另一方面,所有变量定义为 曾是
或者 让
可以根据需要多次更改它们的值。
词汇环境
三种类型的变量定义( 曾是
, 让
和 常量
) 也可以通过它们与词汇环境的关系(通过它们的范围)来分类,我们可以把 曾是
在一侧和 让
/ 常量
在另一。
使用 var
当我们使用 曾是
定义类型,变量在最近的函数或全局词法环境中定义(块被忽略)。让我们看一下下面的例子:
JavaScript 的奇怪之处在于,让很多来自其他语言的人感到困惑的是,我们可以访问在这些块之外的块代码中定义的变量。
这是因为当我们用保留字声明变量时 曾是
他们是 在最近的函数或全局词法环境中注册 ,无论块范围如何。
使用 let 和 const
由于这种奇怪的行为,在 JavaScript 的 ES6 版本中,有两个新的变量声明类型 让
和 常量
已添加。
不像 曾是
, 他们在 最近的词汇环境 (它可以是块、循环、函数或全局)。
对前面的代码进行一些更改:
在此示例中,您无法访问 文本
for 循环之外的变量,您无法访问 信息
函数外的变量 你好
.因为,在这两种情况下,它都不在他们的词汇环境中。
在词法环境中注册标识符
JavaScript 语言的一个原则就是易于使用,所以我们不指定函数返回类型、参数类型、变量类型等等……而且你已经知道 JavaScript 代码是逐行执行的,那么让我们看看下面的例子:
如果代码是逐行执行的,我们如何调用 你好
声明之前的功能? JavaScript 代码的执行分两个阶段:
- 第一阶段在创建新的词法环境时激活,在此阶段不执行任何代码,但是 JavaScript 引擎会访问并注册在当前词法环境中声明的所有变量和函数。
- 在第二阶段,JavaScript 执行发生在第一阶段执行之后,此行为取决于变量声明类型(var、let 和 const)和环境类型(全局、函数或块)。
让我们看另一个例子:
在本例中,它将被记录 不明确的
在我们的控制台中,因为第一步将扫描并注册初始值未定义的每个变量的标识符。实际执行代码时,变量的值将设置为第二个。
这是因为变量类型 曾是
可以将它们的值未定义并在声明之前访问。
笔记: 如果变量是用 let 或 const 定义的,JavaScript 会抛出一个 ReferenceError 说我们不能在变量被声明之前访问它们。
结论
在这篇文章中,我们看到了为什么类型变量 曾是
可以在声明之前访问,以及如何在定义之前调用声明函数。
我们还看到了一些有趣的 JavaScript 概念,例如:执行上下文、词法环境、作用域、标识符解析和标识符。
谢谢阅读!在本平台关注我,阅读更多开发内容。祝你有美好的一天,很快再见!
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明