首页 > web前端 > js教程 > 正文

JS — 理解 JavaScript 中的词法环境 — 深入探究 — 第 1 部分

王林
发布: 2024-08-30 18:38:31
原创
339 人浏览过

作为一名开发人员,我经常遇到“词法环境”这个术语,但我从未真正花时间去全面深入地探索它。因此,我决定深入研究并在这篇文章中记录我的发现 - 因为“分享就是关怀;)”。在这篇文章的结尾,我希望我们都能对词法环境有一个深入的了解,并且我们还将探索内存中发生的情况、数据结构是什么以及调用堆栈如何工作。别担心 - 我会保持简单明了!

词汇环境

在深入了解细节之前,让我先简要概述一下。如果有些概念一开始看起来很复杂,请不要担心 - 我会将它们分解并使用类比来使它们更容易理解。

词法环境是 JavaScript 中的一种特殊数据结构,用于跟踪代码中特定点的变量和函数的范围。

数据结构是在计算机中组织和存储信息以便有效使用信息的方法。常见示例包括数组、对象、列表和树。查看更多:数据结构教程 - GeeksforGeeks

术语“词法”意味着变量和函数的范围和可访问性取决于它们在代码中的编写位置,而不是程序的运行方式。

词汇环境的关键角色:

  • 它存储特定范围(例如函数或块)内的所有变量和函数声明。
  • 它使这些存储的项目可以在代码中的特定点访问。

这是一个简单的代码示例,其中包含三种不同的词法环境:

var sheep = 1;  // Global sheep

function drinkWater() {
    let waterTemperature = "cold"; 
    console.log("The sheep is drinking " + waterTemperature + " water.");
}

var sheepHouseInArea = true;  // Indicates whether the sheep house is present

if (sheepHouseInArea) {
    let lambs = 2;  // Lambs inside the house
    console.log("There are " + sheep + " sheep in the total area and " 
                 + lambs + " lambs!");
}

// This will result in an error because 'lambs'
// is only accessible inside the if block!
console.log("How many lambs are there? " + lambs);
登录后复制

此代码中的三个词法环境是:全局作用域、drinkWater 函数作用域和 if 块作用域。为了让这些概念更容易理解,让我们用羊做一个简单的比喻:

羊的比喻:

本周在外面散步时,我在围栏区域内遇到了一些羊,我想,“嘿,这就像一个词汇环境!”

让我解释一下:想象一个有围栏的区域,里面有羊。羊只能在围栏内做事,比如吃草。现在,想象一下栅栏内有一个小羊舍,小羊可以在那里停留。屋内的羊不能出去,但屋外的羊可以进去。

JS — Understanding Lexical Environments in JavaScript — Deep Dive — Part 1

打破类比:

栅栏代表一切存在的整个区域 - 绵羊、小羊、房子和草。这个围栏区域就是我们所说的全局范围。在这个围栏区域内,羊舍是一个较小的、独立的部分,代表一个街区范围。最后,羊吃的草(yumyum)就像全局范围内的一个函数,是羊可以在该空间内执行的特定活动或动作。

在代码块中,全局作用域由红色框表示,drinkWater函数作用域由蓝色框表示,if块作用域由绿色框表示。这是三个词汇环境。

JS — Understanding Lexical Environments in JavaScript — Deep Dive — Part 1

全球范围(围栏区域):

羊(用varsheep = 1;表示)象征着全局范围内的变量,在围栏区域自由漫游。它可以在 DrinkWater 函数和 if 块的外部和内部使用。

功能范围(喝水):

drinkWater 函数代表羊可以在围栏区域内执行的动作。我们可以从全局范围内的任何地方调用 DrinkWater 函数。然而,函数本身在定义时会创建一个新的词法环境。在这个函数内部,变量(比如 let waterTemperature = 'cold';)只能在函数内部访问。

块范围(如果是块):

if 块创建了一个新的、更小的作用域。在以羊舍为代表的这个范围内,有 2 只羔羊(设羔羊 = 2)。在此作用域内,console.log 语句记录羔羊变量以及全局绵羊变量的值。 Lambs 变量特定于块作用域,而 Sheep 变量是从父环境(全局作用域)获取的。这是通过外部环境引用实现的,它允许 JavaScript 查找作用域链并解析当前环境中找不到的变量。

外部环境引用是词法环境中的引用或指针。它指向父词法环境,允许 JavaScript 通过查找作用域链来解析在当前环境中找不到的变量。

提问时间!

您可以修改 DrinkWater() 函数,以便它记录全局范围内定义的可以喝水的羊总数吗?在评论区分享你的答案吧!

理解多重词汇环境

因此,我们看到这段代码中存在三个词法环境:全局作用域、函数作用域和块作用域。当存在多个词汇环境时,我们将其称为多个词汇环境。理解单段代码中可以存在多个词法环境非常重要。每次创建新作用域(例如,函数或块)时,都会生成新的词法环境,这意味着代码的不同部分可以拥有自己单独的环境。

环境记录

现在我们了解了词法环境的工作原理,让我们更深入地了解环境记录的概念。

每当创建词法环境时 - 无论是全局范围、函数还是块 - JavaScript 都会自动为其生成环境记录。

此环境记录是一种数据结构,用于跟踪该特定范围内可访问的所有变量、函数和其他绑定。本质上,它充当该环境中定义的所有内容的内部存储,确保在代码执行期间需要时提供正确的数据。

词汇环境和环境记录之间的区别

词法环境和环境记录之间的主要区别:

词法环境是 JavaScript 代码运行的地方。将其视为代码所在的“设置”或“上下文”。此上下文包括变量和函数的范围,确定哪些变量和函数在代码中的任何点可用或可访问。例如,在我们的代码中,羔羊变量只能在绿色边框环境(块作用域)内访问。词法环境还包括外部环境引用(我们已经描述过),允许访问父环境中的变量。

环境记录是词法环境中的特定存储区域,它保存该环境中使用的实际变量、函数声明和其他标识符。虽然词法环境是更广泛的上下文,但环境记录是存储代码数据(例如变量值和函数定义)的位置。每当 JavaScript 需要访问变量或函数时,它都会查找当前词法环境的环境记录。

让我们使用我们的代码示例再次解释一下词法环境和环境记录:

JS — Understanding Lexical Environments in JavaScript — Deep Dive — Part 1

共有三个词法环境,每个都有自己的环境记录:

  1. 全局范围(红框),词法环境 1: 这个词汇环境是在全局级别创建的。该环境中的环境记录包含:
    • 可变的羊。
    • 函数喝水。
    • 变量sheepHouseInArea。

这些声明可以在整个代码中访问。全局环境还引用了在此环境中定义的函数 DrinkWater 以及 if 语句,该语句在执行时会创建自己的块作用域。

  1. 函数范围(drinkWater,蓝色框),词法环境 2:
    该环境中的环境记录包含变量 waterTemperature,该变量在 DrinkWater 函数中使用 let 进行声明。该变量只能在函数内访问。然而,该函数也可以像sheep一样访问全局环境中的变量。

  2. 块范围(如果是块,则为绿色框​​),词法环境 3:
    该环境中的环境记录包含变量羔羊,在 if 块内使用 let 进行声明。该变量只能在该特定块作用域内访问。该块还可以访问其父环境中的变量,例如sheep和sheepHouseInArea。

记忆中幕后发生的事情

深入研究词法环境和环境记录后,我们现在准备好了解 JavaScript 在代码执行期间如何管理内存和变量访问。

当您的代码运行时,JavaScript 会为每个函数或代码块创建一个新的词法环境。每个环境都有自己的环境记录,存储在该范围内定义的所有变量和函数。正如我们所讨论的,此设置可确保高效的内存使用。

Behind the scenes, the JavaScript engine handles these lexical environments in memory. The call stack is used for tracking function calls, while block scopes create new lexical environments linked to their outer environments. However, unlike functions, these block scopes aren't pushed onto the call stack.

What is the call stack?

The call stack is a fundamental concept in how Javascript executes code.

The call stack is a data structure that keeps track of function calls in a program. It works on a Last-In-First-Out (LIFO) principle. Here's how it works:

  • When a function is called, it's added (pushed) to the top of the stack.
  • When a function finishes executing, it's removed (popped) from the top of the stack.
  • The stack also keeps track of the current position in the code.

Key points about the call stack:

  • It records where in the program we are.
  • If we step into a function, we put it on the top of the stack.
  • If we return from a function, we pop it off the stack.
  • The stack has a maximum size, and if that limit is exceeded, it results in a "stack overflow" error.

JS — Understanding Lexical Environments in JavaScript — Deep Dive — Part 1

Now you know why its called stack overflow haha!

Here's a simple example to illustrate:

function greet(name) {
    console.log('Hello, ' + name);
}

function processUser(user) {
    greet(user);
}

processUser('Alice');
登录后复制
  1. main() (global execution context)
  2. processUser('Alice')
  3. greet('Alice')

As each function completes, it's popped off the stack until we return to the global context.

The last final question!

In our sheep code example, can you identify if anything is placed on the call stack during execution? Share your thoughts in the comments section!


Conclusion

That's it for Part 1! I hope this post has helped you gain a solid understanding of how JavaScript handles lexical environments, environment records, and what happens behind the scenes in memory. I've learned a lot in the process, and I hope you have too. If you have any questions or feedback, I'd love to hear from you - let's learn and improve together!

I titled this post 'Part 1' because I plan to follow up with 'Part 2,' where I'll dive into three major concepts that are closely linked to lexical environments:

  1. Closures: Think of them as magical boxes that let functions remember and access variables from their outer environment, even after the outer function has finished executing.
  2. Scope Chains: We'll explore how JavaScript navigates through nested environments to find variables, like a treasure hunt in your code.
  3. Hoisting: This explains why some variables and functions seem to "float" to the top of their scope, which can be tricky to understand but is crucial for writing predictable code.

These concepts are super important because they directly impact how your JavaScript code behaves. Understanding them will help you write cleaner, more efficient code and avoid some common pitfalls.

Stay tuned!

--
Please also follow me on my Medium: https://medium.com/@ensing89

以上是JS — 理解 JavaScript 中的词法环境 — 深入探究 — 第 1 部分的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责声明 Sitemap
PHP中文网:公益在线PHP培训,帮助PHP学习者快速成长!