为什么在Javascript中声明之前可以访问变量?

为什么在Javascript中声明之前可以访问变量?

毫无疑问,当今最常用和最著名的语言之一是 JavaScript,如今它无处不在,我们可以创建 Web 应用程序和系统,在后端开发 API 并创建移动应用程序。

尽管它很受欢迎,但很多人不喜欢 JavaScript,主要是因为一些 特殊性 的语言与其他语言非常不同。

当我开始使用 JavaScript 时,让我感到困惑的一件事是 在声明之前使用变量或函数的可能性 而我相信很多人也觉得这有点奇怪。

在这篇文章中,我将尝试解释这是如何发生的。

Photo by 都铎·巴丘 on 不飞溅

执行上下文

在讨论变量和函数之前,我们需要了解一些关于 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 代码的执行分两个阶段:

  1. 第一阶段在创建新的词法环境时激活,在此阶段不执行任何代码,但是 JavaScript 引擎会访问并注册在当前词法环境中声明的所有变量和函数。
  2. 在第二阶段,JavaScript 执行发生在第一阶段执行之后,此行为取决于变量声明类型(var、let 和 const)和环境类型(全局、函数或块)。

让我们看另一个例子:

在本例中,它将被记录 不明确的 在我们的控制台中,因为第一步将扫描并注册初始值未定义的每个变量的标识符。实际执行代码时,变量的值将设置为第二个。

这是因为变量类型 曾是 可以将它们的值未定义并在声明之前访问。

笔记: 如果变量是用 let 或 const 定义的,JavaScript 会抛出一个 ReferenceError 说我们不能在变量被声明之前访问它们。

结论

在这篇文章中,我们看到了为什么类型变量 曾是 可以在声明之前访问,以及如何在定义之前调用声明函数。

我们还看到了一些有趣的 JavaScript 概念,例如:执行上下文、词法环境、作用域、标识符解析和标识符。

谢谢阅读!在本平台关注我,阅读更多开发内容。祝你有美好的一天,很快再见!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/1358/49302905

posted @ 2022-08-29 05:50  哈哈哈来了啊啊啊  阅读(79)  评论(0编辑  收藏  举报