首頁 >web前端 >js教程 >JS — 理解 JavaScript 中的詞法環境 — 深入探究 — 第 1 部分

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

王林
王林原創
2024-08-30 18:38:31363瀏覽

身為開發人員,我經常遇到「詞法環境」這個術語,但我從未真正花時間去全面深入地探索它。因此,我決定深入研究並在這篇文章中記錄我的發現 - 因為「分享就是關懷;)」。在這篇文章的結尾,我希望我們都能對詞法環境有一個深入的了解,並且我們還將探索記憶體中發生的情況、資料結構是什麼以及呼叫堆疊如何運作。別擔心 - 我會保持簡單明了!

詞彙環境

在深入了解細節之前,先簡單概述一下。如果有些概念一開始看起來很複雜,請不要擔心 - 我會將它們分解並使用類比來使它們更容易理解。

詞法環境是 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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn