Understanding Scope in JavaScript
Aug 31, 2023 pm 08:17 PMScope, or a set of rules that determines where a variable should be located, is one of the most fundamental concepts of any programming language. In fact, it's so basic that we can easily forget how subtle these rules are!
Understanding exactly how the JavaScript engine "thinks" scope will allow you to avoid common mistakes that can result from writing hoisting, prepare you to focus on closures, and get you one step closer to never writing them again again.
...Anyway, it will help you understand lifting and closing.
In this article we will learn about:
- Basic knowledge of scope in JavaScript
- How the interpreter decides which variables belong to which scope
- The actual working principle of hoisting How the
- ES6 keywords
let
andconst
change the game
Let’s dig into it.
If you are interested in learning more about ES6 and how to take advantage of syntax and features to improve and simplify your JavaScript code, why not check out these two courses:
lexical scope
If you've ever written a line of JavaScript before, you'll know that where you define variables determines where you can use > them. The fact that a variable's visibility depends on the structure of the source code is called lexical scope.
There are three ways to create a scope in JavaScript:
- Create function. Variables declared inside a function are only visible inside that function, including within nested functions.
-
Use
let
orconst
Declare variables within a code block. Such declarations are only visible within blocks. - Create catch
"use strict"; var mr_global = "Mr Global"; function foo () { var mrs_local = "Mrs Local"; console.log("I can see " + mr_global + " and " + mrs_local + "."); function bar () { console.log("I can also see " + mr_global + " and " + mrs_local + "."); } } foo(); // Works as expected try { console.log("But /I/ can't see " + mrs_local + "."); } catch (err) { console.log("You just got a " + err + "."); } { let foo = "foo"; const bar = "bar"; console.log("I can use " + foo + bar + " in its block..."); } try { console.log("But not outside of it."); } catch (err) { console.log("You just got another " + err + "."); } // Throws ReferenceError! console.log("Note that " + err + " doesn't exist outside of 'catch'!")
let yet.
Compilation Process: Bird’s Eye View
When you run a piece of JavaScript, two things happen to make it work properly.
- First, compile your source code.
- Then, the compiled code will be executed.
compilation step , the JavaScript engine:
- Write down all variable names
- Register them to the appropriate scope
- Keep space for your own values
execution does the JavaScript engine actually set the value of a variable reference to be equal to its assigned value. Until then, they are undefined.
// I can use first_name anywhere in this program
var first_name = "Peleke";
function popup (first_name) {
// I can only use last_name inside of this function
var last_name = "Sengstacke";
alert(first_name + ' ' + last_name);
}
popup(first_name);
Let's understand the role of the compiler step by step. First, it reads the line
var first_name = "Peleke". Next, it determines the
scope into which the variable is saved. Because we are at the top level of the script, it realizes that we are in global scope. It then saves the variable first_name to the global scope and initializes its value to
undefined.
function popup (first_name). Because the
function keyword is the first thing on the line, it creates a new scope for the function, registers the function's definition with the global scope, and looks inside to find the variable declaration.
var last_name = "Sengstacke" in the first line of our function, the compiler will save the variable
last_name to the scope of <code
class=" inline">popup</em> —
not to the global scope — and set its value to undefined.
Note that we haven't actually run anything yet.
The compiler's job at this point is just to make sure it knows everyone's name; it doesn't care what they do.
At this point, our program knows:
- 全局范圍內(nèi)有一個名為
first_name
的變量。 - 全局范圍內(nèi)有一個名為
popup
的函數(shù)。 - 在
popup
范圍內(nèi)有一個名為last_name
的變量。 first_name
和last_name
的值均為undefined
。
它并不關(guān)心我們是否在代碼中的其他地方分配了這些變量值。引擎在執(zhí)行時會處理這個問題。
第 2 步:執(zhí)行
在下一步中,引擎再次讀取我們的代碼,但這一次,執(zhí)行它。
首先,它讀取行 var first_name = "Peleke"
。為此,引擎會查找名為 first_name
的變量。由于編譯器已經(jīng)用該名稱注冊了一個變量,引擎會找到它,并將其值設(shè)置為 "Peleke"
。
接下來,它讀取行 function popup (first_name)
。由于我們沒有在這里執(zhí)行該函數(shù),因此引擎不感興趣并跳過它。
最后,它讀取行 popup(first_name)
。由于我們在這里執(zhí)行一個函數(shù),因此引擎:
- 查找
popup
的值 - 查找
first_name
的值 - 將
popup
作為函數(shù)執(zhí)行,并傳遞first_name
的值作為參數(shù)
當執(zhí)行 popup
時,它會經(jīng)歷相同的過程,但這次是在函數(shù) popup
內(nèi)。它:
- 查找名為
last_name
的變量 - 將
last_name
的值設(shè)置為等于"Sengstacke"
- 查找
alert
,將其作為函數(shù)執(zhí)行,并以"Peleke Sengstacke"
作為參數(shù)
事實證明,幕后發(fā)生的事情比我們想象的要多得多!
既然您已經(jīng)了解了 JavaScript 如何讀取和運行您編寫的代碼,我們就準備好解決一些更貼近實際的問題:提升的工作原理。
顯微鏡下的吊裝
讓我們從一些代碼開始。
bar(); function bar () { if (!foo) { alert(foo + "? This is strange..."); } var foo = "bar"; } broken(); // TypeError! var broken = function () { alert("This alert won't show up!"); }
如果運行此代碼,您會注意到三件事:
- 在分配之前,您可以引用
foo
,但其值為undefined
。 - 您可以在定義之前調(diào)用
已損壞的
,但您會收到TypeError
。 - 您可以在定義之前調(diào)用
bar
,它會按需要工作。
提升是指 JavaScript 使我們聲明的所有變量名稱在其作用域內(nèi)的任何地方都可用,包括在我們分配給它們之前 .
代碼段中的三種情況是您在自己的代碼中需要注意的三種情況,因此我們將一一逐步介紹它們。
提升變量聲明
請記住,當 JavaScript 編譯器讀取像 var foo = "bar"
這樣的行時,它會:
- 將名稱
foo
注冊到最近的范圍 - 將
foo
的值設(shè)置為未定義
我們可以在賦值之前使用 foo
的原因是,當引擎查找具有該名稱的變量時,它確實存在。這就是為什么它不會拋出 ReferenceError
。
相反,它獲取值 undefined
,并嘗試使用該值執(zhí)行您要求的任何操作。通常,這是一個錯誤。
記住這一點,我們可能會想象 JavaScript 在我們的函數(shù) bar
中看到的更像是這樣:
function bar () { var foo; // undefined if (!foo) { // !undefined is true, so alert alert(foo + "? This is strange..."); } foo = "bar"; }
如果您愿意的話,這是提升的第一條規(guī)則:變量在其整個范圍內(nèi)都可用,但其值為 undefined
,直到您代碼分配給他們。
常見的 JavaScript 習(xí)慣用法是將所有 var
聲明寫入其作用域的頂部,而不是首次使用它們的位置。用 Doug Crockford 的話來說,這可以幫助您的代碼閱讀更像它運行。
仔細想想,這是有道理的。當我們以 JavaScript 讀取代碼的方式編寫代碼時,為什么 bar
的行為方式非常清楚,不是嗎?那么為什么不一直這樣寫呢?
提升函數(shù)表達式
事實上,當我們在定義之前嘗試執(zhí)行 broken
時,我們得到了 TypeError
,這只是第一條提升規(guī)則的一個特例。
我們定義了一個名為 broken
的變量,編譯器會在全局范圍內(nèi)注冊該變量,并將其設(shè)置為等于 undefined
。當我們嘗試運行它時,引擎會查找 broken
的值,發(fā)現(xiàn)它是 undefined
,并嘗試將 undefined
作為函數(shù)執(zhí)行.
顯然,undefined
不是一個函數(shù),這就是為什么我們得到 TypeError
!
提升函數(shù)聲明
最后,回想一下,在定義 bar
之前,我們可以調(diào)用它。這是由于第二條提升規(guī)則:當 JavaScript 編譯器找到函數(shù)聲明時,它會使其名稱和定義在其作用域的頂部可用。再次重寫我們的代碼:
function bar () { if (!foo) { alert(foo + "? This is strange..."); } var foo = "bar"; } var broken; // undefined bar(); // bar is already defined, executes fine broken(); // Can't execute undefined! broken = function () { alert("This alert won't show up!"); }
同樣,當您編寫作為JavaScript讀取時,它更有意義,您不覺得嗎?
查看:
- 變量聲明和函數(shù)表達式的名稱在其整個范圍內(nèi)都可用,但它們的值在賦值之前為
undefined
。 - 函數(shù)聲明的名稱??和定義在其整個范圍內(nèi)都可用,甚至在其定義之前。
現(xiàn)在讓我們來看看兩個工作方式稍有不同的新工具:let
和 const
。
<strong>let</strong>
、<strong></strong>const
和臨時死區(qū)強><強>強>
與 var
聲明不同,使用 let
和 const
聲明的變量不?被編譯器提升。
至少,不完全是。
還記得我們?nèi)绾握{(diào)用 已損壞的
,但卻因為嘗試執(zhí)行 undefined
而收到 TypeError
嗎?如果我們使用 let
定義 broken
,我們就會得到 ReferenceError
,而不是:
"use strict"; // You have to "use strict" to try this in Node broken(); // ReferenceError! let broken = function () { alert("This alert won't show up!"); }
當 JavaScript 編譯器在第一遍中將變量注冊到其作用域時,它對待 let
和 const
的方式與處理 var
的方式不同。
當它找到 var
聲明時,我們將該變量的名稱注冊到其范圍,并立即將其值初始化為 undefined
。
但是,使用 let
,編譯器會將變量注冊到其作用域,但不會初始化其值為 undefined
。相反,它會使變量保持未初始化狀態(tài),直到引擎執(zhí)行您的賦值語句。訪問未初始化變量的值會拋出 ReferenceError
,這解釋了為什么上面的代碼片段在運行時會拋出異常。
let
聲明和賦值語句的頂部開頭之間的空間稱為臨時死區(qū)。該名稱源自以下事實:即使引擎知道名為 foo
的變量(位于 bar
范圍的頂部),該變量是“死的”,因為它沒有值。
...還因為如果您嘗試盡早使用它,它會殺死您的程序。
const
關(guān)鍵字的工作方式與 let
相同,但有兩個主要區(qū)別:
- 使用
const
聲明時,必須分配一個值。 - 您不能為使用
const
聲明的變量重新賦值。
這可以保證 const
將始終擁有您最初分配給它的值。
// This is legal const React = require('react'); // This is totally not legal const crypto; crypto = require('crypto');
塊范圍
let
和 const
與 var
在另一方面有所不同:它們的范圍大小。
當您使用 var
聲明變量時,它在作用域鏈的盡可能高的位置可見 - 通常是在最近的函數(shù)聲明的頂部,或者在全局范圍,如果您在頂層聲明它。
但是,當您使用 let
或 const
聲明變量時,它會盡可能本地可見 -僅 >?在最近的街區(qū)內(nèi)。
塊是由大括號分隔的一段代碼,如 if
/else
塊、for
?循環(huán),以及顯式“阻止”的代碼塊,如本段代碼所示。
"use strict"; { let foo = "foo"; if (foo) { const bar = "bar"; var foobar = foo + bar; console.log("I can see " + bar + " in this bloc."); } try { console.log("I can see " + foo + " in this block, but not " + bar + "."); } catch (err) { console.log("You got a " + err + "."); } } try { console.log( foo + bar ); // Throws because of 'foo', but both are undefined } catch (err) { console.log( "You just got a " + err + "."); } console.log( foobar ); // Works fine
如果您在塊內(nèi)使用 const
或 let
聲明變量,則該變量僅在塊內(nèi)可見,且僅< /em> 分配后。
但是,使用 var
聲明的變量在盡可能遠的地方可見 - 在本例中是在全局范圍內(nèi)。
如果您對 let
和 const
的具體細節(jié)感興趣,請查看 Rauschmayer 博士在《探索 ES6:變量和范圍》中對它們的介紹,并查看有關(guān)它們的 MDN 文檔。
詞法 <strong>this</strong>
和箭頭函數(shù)
從表面上看,this
似乎與范圍沒有太大關(guān)系。事實上,JavaScript 并沒有根據(jù)我們在這里討論的范圍規(guī)則來解析 this
的含義。
至少,通常不會。眾所周知,JavaScript 不會根據(jù)您使用該關(guān)鍵字的位置來解析 this
關(guān)鍵字的含義:
var foo = { name: 'Foo', languages: ['Spanish', 'French', 'Italian'], speak : function speak () { this.languages.forEach(function(language) { console.log(this.name + " speaks " + language + "."); }) } }; foo.speak();
我們大多數(shù)人都認為 this
表示 foo
在 forEach
循環(huán)內(nèi),因為這就是它在循環(huán)之外的含義。換句話說,我們期望 JavaScript 能夠解析 this
詞法的含義。
但事實并非如此。
相反,它會在您定義的每個函數(shù)中創(chuàng)建一個新 this
,并根據(jù)您如何調(diào)用該函數(shù)來決定其含義 -不是您定義它的位置。
第一點類似于在子作用域中重新定義任何變量的情況:
function foo () { var bar = "bar"; function baz () { // Reusing variable names like this is called "shadowing" var bar = "BAR"; console.log(bar); // BAR } baz(); } foo(); // BAR
將 bar
替換為 this
,整個事情應(yīng)該立即清楚!
傳統(tǒng)上,要讓 this
按照我們期望的普通舊詞法范圍變量的方式工作,需要以下兩種解決方法之一:
var foo = { name: 'Foo', languages: ['Spanish', 'French', 'Italian'], speak_self : function speak_s () { var self = this; self.languages.forEach(function(language) { console.log(self.name + " speaks " + language + "."); }) }, speak_bound : function speak_b () { this.languages.forEach(function(language) { console.log(this.name + " speaks " + language + "."); }.bind(foo)); // More commonly:.bind(this); } };
在 speak_self
中,我們將 this
的含義保存到變量 self
中,并使用該變量來得到我們想要的參考。在 speak_bound
中,我們使用 bind
來永久將 this
指向給定對象。
ES2015 為我們帶來了一種新的選擇:箭頭函數(shù)。
與“普通”函數(shù)不同,箭頭函數(shù)不會通過設(shè)置自己的值來隱藏其父作用域的 this
值。相反,他們從詞匯上解析其含義。
換句話說,如果您在箭頭函數(shù)中使用 this
,JavaScript 會像查找任何其他變量一樣查找其值。
首先,它檢查本地范圍內(nèi)的 this
值。由于箭頭函數(shù)沒有設(shè)置一個,因此它不會找到一個。接下來,它檢查 this
值的父范圍。如果找到,它將使用它。
這讓我們可以像這樣重寫上面的代碼:
var foo = { name: 'Foo', languages: ['Spanish', 'French', 'Italian'], speak : function speak () { this.languages.forEach((language) => { console.log(this.name + " speaks " + language + "."); }) } };???
如果您想了解有關(guān)箭頭函數(shù)的更多詳細信息,請查看 Envato Tuts+ 講師 Dan Wellman 的有關(guān) JavaScript ES6 基礎(chǔ)知識的精彩課程,以及有關(guān)箭頭函數(shù)的 MDN 文檔。
結(jié)論
到目前為止,我們已經(jīng)了解了很多內(nèi)容!在本文中,您了解到:
- 變量在編譯期間注冊到其作用域,并在執(zhí)行期間與其賦值相關(guān)聯(lián)。
- 在賦值之前引用使用?
let
或 class="inline">const 聲明的變量會引發(fā)ReferenceError
,并且此類變量是范圍到最近的塊。 - 箭頭函數(shù) 允許我們實現(xiàn)
this
的詞法綁定,并繞過傳統(tǒng)的動態(tài)綁定。
您還了解了提升的兩條規(guī)則:
-
第一條提升規(guī)則:函數(shù)表達式和
var
聲明在其定義的整個范圍內(nèi)都可用,但其值為undefined
?直到您的賦值語句執(zhí)行。 - 第二條提升規(guī)則:函數(shù)聲明的名稱及其主體在定義它們的范圍內(nèi)可用。
下一步最好是利用 JavaScript 作用域的新知識來理解閉包。為此,請查看 Kyle Simpson 的 Scopes & Closures。
最后,關(guān)于 this
有很多話要說,我無法在此介紹。如果該關(guān)鍵字看起來仍然像是黑魔法,請查看此和對象原型來了解它。
In the meantime, take advantage of what you've learned and make fewer writing mistakes!
Learn JavaScript: The Complete Guide
We've built a complete guide to help you learn JavaScript, whether you're just starting out as a web developer or want to explore more advanced topics.
The above is the detailed content of Understanding Scope in JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Use WordPress testing environments to ensure the security and compatibility of new features, plug-ins or themes before they are officially launched, and avoid affecting real websites. The steps to build a test environment include: downloading and installing local server software (such as LocalWP, XAMPP), creating a site, setting up a database and administrator account, installing themes and plug-ins for testing; the method of copying a formal website to a test environment is to export the site through the plug-in, import the test environment and replace the domain name; when using it, you should pay attention to not using real user data, regularly cleaning useless data, backing up the test status, resetting the environment in time, and unifying the team configuration to reduce differences.

When managing WordPress projects with Git, you should only include themes, custom plugins, and configuration files in version control; set up .gitignore files to ignore upload directories, caches, and sensitive configurations; use webhooks or CI tools to achieve automatic deployment and pay attention to database processing; use two-branch policies (main/develop) for collaborative development. Doing so can avoid conflicts, ensure security, and improve collaboration and deployment efficiency.

The key to creating a Gutenberg block is to understand its basic structure and correctly connect front and back end resources. 1. Prepare the development environment: install local WordPress, Node.js and @wordpress/scripts; 2. Use PHP to register blocks and define the editing and display logic of blocks with JavaScript; 3. Build JS files through npm to make changes take effect; 4. Check whether the path and icons are correct when encountering problems or use real-time listening to build to avoid repeated manual compilation. Following these steps, a simple Gutenberg block can be implemented step by step.

TosetupredirectsinWordPressusingthe.htaccessfile,locatethefileinyoursite’srootdirectoryandaddredirectrulesabovethe#BEGINWordPresssection.Forbasic301redirects,usetheformatRedirect301/old-pagehttps://example.com/new-page.Forpattern-basedredirects,enabl

UsingSMTPforWordPressemailsimprovesdeliverabilityandreliabilitycomparedtothedefaultPHPmail()function.1.SMTPauthenticateswithyouremailserver,reducingspamplacement.2.SomehostsdisablePHPmail(),makingSMTPnecessary.3.SetupiseasywithpluginslikeWPMailSMTPby

In WordPress, when adding a custom article type or modifying the fixed link structure, you need to manually refresh the rewrite rules. At this time, you can call the flush_rewrite_rules() function through the code to implement it. 1. This function can be added to the theme or plug-in activation hook to automatically refresh; 2. Execute only once when necessary, such as adding CPT, taxonomy or modifying the link structure; 3. Avoid frequent calls to avoid affecting performance; 4. In a multi-site environment, refresh each site separately as appropriate; 5. Some hosting environments may restrict the storage of rules. In addition, clicking Save to access the "Settings>Pinned Links" page can also trigger refresh, suitable for non-automated scenarios.

To implement responsive WordPress theme design, first, use HTML5 and mobile-first Meta tags, add viewport settings in header.php to ensure that the mobile terminal is displayed correctly, and organize the layout with HTML5 structure tags; second, use CSS media query to achieve style adaptation under different screen widths, write styles according to the mobile-first principle, and commonly used breakpoints include 480px, 768px and 1024px; third, elastically process pictures and layouts, set max-width:100% for the picture and use Flexbox or Grid layout instead of fixed width; finally, fully test through browser developer tools and real devices, optimize loading performance, and ensure response

Tointegratethird-partyAPIsintoWordPress,followthesesteps:1.SelectasuitableAPIandobtaincredentialslikeAPIkeysorOAuthtokensbyregisteringandkeepingthemsecure.2.Choosebetweenpluginsforsimplicityorcustomcodeusingfunctionslikewp_remote_get()forflexibility.
