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)有一個(gè)名為
first_name
的變量。 - 全局范圍內(nèi)有一個(gè)名為
popup
的函數(shù)。 - 在
popup
范圍內(nèi)有一個(gè)名為last_name
的變量。 first_name
和last_name
的值均為undefined
。
它并不關(guān)心我們是否在代碼中的其他地方分配了這些變量值。引擎在執(zhí)行時(shí)會(huì)處理這個(gè)問(wèn)題。
第 2 步:執(zhí)行
在下一步中,引擎再次讀取我們的代碼,但這一次,執(zhí)行它。
首先,它讀取行 var first_name = "Peleke"
。為此,引擎會(huì)查找名為 first_name
的變量。由于編譯器已經(jīng)用該名稱注冊(cè)了一個(gè)變量,引擎會(huì)找到它,并將其值設(shè)置為 "Peleke"
。
接下來(lái),它讀取行 function popup (first_name)
。由于我們沒(méi)有在這里執(zhí)行該函數(shù),因此引擎不感興趣并跳過(guò)它。
最后,它讀取行 popup(first_name)
。由于我們在這里執(zhí)行一個(gè)函數(shù),因此引擎:
- 查找
popup
的值 - 查找
first_name
的值 - 將
popup
作為函數(shù)執(zhí)行,并傳遞first_name
的值作為參數(shù)
當(dāng)執(zhí)行 popup
時(shí),它會(huì)經(jīng)歷相同的過(guò)程,但這次是在函數(shù) popup
內(nèi)。它:
- 查找名為
last_name
的變量 - 將
last_name
的值設(shè)置為等于"Sengstacke"
- 查找
alert
,將其作為函數(shù)執(zhí)行,并以"Peleke Sengstacke"
作為參數(shù)
事實(shí)證明,幕后發(fā)生的事情比我們想象的要多得多!
既然您已經(jīng)了解了 JavaScript 如何讀取和運(yùn)行您編寫的代碼,我們就準(zhǔn)備好解決一些更貼近實(shí)際的問(wèn)題:提升的工作原理。
顯微鏡下的吊裝
讓我們從一些代碼開始。
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!"); }
如果運(yùn)行此代碼,您會(huì)注意到三件事:
- 在分配之前,您可以引用
foo
,但其值為undefined
。 - 您可以在定義之前調(diào)用
已損壞的
,但您會(huì)收到TypeError
。 - 您可以在定義之前調(diào)用
bar
,它會(huì)按需要工作。
提升是指 JavaScript 使我們聲明的所有變量名稱在其作用域內(nèi)的任何地方都可用,包括在我們分配給它們之前 .
代碼段中的三種情況是您在自己的代碼中需要注意的三種情況,因此我們將一一逐步介紹它們。
提升變量聲明
請(qǐng)記住,當(dāng) JavaScript 編譯器讀取像 var foo = "bar"
這樣的行時(shí),它會(huì):
- 將名稱
foo
注冊(cè)到最近的范圍 - 將
foo
的值設(shè)置為未定義
我們可以在賦值之前使用 foo
的原因是,當(dāng)引擎查找具有該名稱的變量時(shí),它確實(shí)存在。這就是為什么它不會(huì)拋出 ReferenceError
。
相反,它獲取值 undefined
,并嘗試使用該值執(zhí)行您要求的任何操作。通常,這是一個(gè)錯(cuò)誤。
記住這一點(diǎn),我們可能會(huì)想象 JavaScript 在我們的函數(shù) bar
中看到的更像是這樣:
function bar () { var foo; // undefined if (!foo) { // !undefined is true, so alert alert(foo + "? This is strange..."); } foo = "bar"; }
如果您愿意的話,這是提升的第一條規(guī)則:變量在其整個(gè)范圍內(nèi)都可用,但其值為 undefined
,直到您代碼分配給他們。
常見的 JavaScript 習(xí)慣用法是將所有 var
聲明寫入其作用域的頂部,而不是首次使用它們的位置。用 Doug Crockford 的話來(lái)說(shuō),這可以幫助您的代碼閱讀更像它運(yùn)行。
仔細(xì)想想,這是有道理的。當(dāng)我們以 JavaScript 讀取代碼的方式編寫代碼時(shí),為什么 bar
的行為方式非常清楚,不是嗎?那么為什么不一直這樣寫呢?
提升函數(shù)表達(dá)式
事實(shí)上,當(dāng)我們?cè)诙x之前嘗試執(zhí)行 broken
時(shí),我們得到了 TypeError
,這只是第一條提升規(guī)則的一個(gè)特例。
我們定義了一個(gè)名為 broken
的變量,編譯器會(huì)在全局范圍內(nèi)注冊(cè)該變量,并將其設(shè)置為等于 undefined
。當(dāng)我們嘗試運(yùn)行它時(shí),引擎會(huì)查找 broken
的值,發(fā)現(xiàn)它是 undefined
,并嘗試將 undefined
作為函數(shù)執(zhí)行.
顯然,undefined
不是一個(gè)函數(shù),這就是為什么我們得到 TypeError
!
提升函數(shù)聲明
最后,回想一下,在定義 bar
之前,我們可以調(diào)用它。這是由于第二條提升規(guī)則:當(dāng) JavaScript 編譯器找到函數(shù)聲明時(shí),它會(huì)使其名稱和定義在其作用域的頂部可用。再次重寫我們的代碼:
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!"); }
同樣,當(dāng)您編寫作為JavaScript讀取時(shí),它更有意義,您不覺得嗎?
查看:
- 變量聲明和函數(shù)表達(dá)式的名稱在其整個(gè)范圍內(nèi)都可用,但它們的值在賦值之前為
undefined
。 - 函數(shù)聲明的名稱??和定義在其整個(gè)范圍內(nèi)都可用,甚至在其定義之前。
現(xiàn)在讓我們來(lái)看看兩個(gè)工作方式稍有不同的新工具:let
和 const
。
<strong>let</strong>
、<strong></strong>const
和臨時(shí)死區(qū)強(qiáng)><強(qiáng)>強(qiáng)>
與 var
聲明不同,使用 let
和 const
聲明的變量不?被編譯器提升。
至少,不完全是。
還記得我們?nèi)绾握{(diào)用 已損壞的
,但卻因?yàn)閲L試執(zhí)行 undefined
而收到 TypeError
嗎?如果我們使用 let
定義 broken
,我們就會(huì)得到 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!"); }
當(dāng) JavaScript 編譯器在第一遍中將變量注冊(cè)到其作用域時(shí),它對(duì)待 let
和 const
的方式與處理 var
的方式不同。
當(dāng)它找到 var
聲明時(shí),我們將該變量的名稱注冊(cè)到其范圍,并立即將其值初始化為 undefined
。
但是,使用 let
,編譯器會(huì)將變量注冊(cè)到其作用域,但不會(huì)初始化其值為 undefined
。相反,它會(huì)使變量保持未初始化狀態(tài),直到引擎執(zhí)行您的賦值語(yǔ)句。訪問(wèn)未初始化變量的值會(huì)拋出 ReferenceError
,這解釋了為什么上面的代碼片段在運(yùn)行時(shí)會(huì)拋出異常。
let
聲明和賦值語(yǔ)句的頂部開頭之間的空間稱為臨時(shí)死區(qū)。該名稱源自以下事實(shí):即使引擎知道名為 foo
的變量(位于 bar
范圍的頂部),該變量是“死的”,因?yàn)樗鼪](méi)有值。
...還因?yàn)槿绻鷩L試盡早使用它,它會(huì)殺死您的程序。
const
關(guān)鍵字的工作方式與 let
相同,但有兩個(gè)主要區(qū)別:
- 使用
const
聲明時(shí),必須分配一個(gè)值。 - 您不能為使用
const
聲明的變量重新賦值。
這可以保證 const
將始終擁有您最初分配給它的值。
// This is legal const React = require('react'); // This is totally not legal const crypto; crypto = require('crypto');
塊范圍
let
和 const
與 var
在另一方面有所不同:它們的范圍大小。
當(dāng)您使用 var
聲明變量時(shí),它在作用域鏈的盡可能高的位置可見 - 通常是在最近的函數(shù)聲明的頂部,或者在全局范圍,如果您在頂層聲明它。
但是,當(dāng)您使用 let
或 const
聲明變量時(shí),它會(huì)盡可能本地可見 -僅 >?在最近的街區(qū)內(nèi)。
塊是由大括號(hào)分隔的一段代碼,如 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
聲明的變量在盡可能遠(yuǎn)的地方可見 - 在本例中是在全局范圍內(nèi)。
如果您對(duì) let
和 const
的具體細(xì)節(jié)感興趣,請(qǐng)查看 Rauschmayer 博士在《探索 ES6:變量和范圍》中對(duì)它們的介紹,并查看有關(guān)它們的 MDN 文檔。
詞法 <strong>this</strong>
和箭頭函數(shù)
從表面上看,this
似乎與范圍沒(méi)有太大關(guān)系。事實(shí)上,JavaScript 并沒(méi)有根據(jù)我們?cè)谶@里討論的范圍規(guī)則來(lái)解析 this
的含義。
至少,通常不會(huì)。眾所周知,JavaScript 不會(huì)根據(jù)您使用該關(guān)鍵字的位置來(lái)解析 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ù)人都認(rèn)為 this
表示 foo
在 forEach
循環(huán)內(nèi),因?yàn)檫@就是它在循環(huán)之外的含義。換句話說(shuō),我們期望 JavaScript 能夠解析 this
詞法的含義。
但事實(shí)并非如此。
相反,它會(huì)在您定義的每個(gè)函數(shù)中創(chuàng)建一個(gè)新 this
,并根據(jù)您如何調(diào)用該函數(shù)來(lái)決定其含義 -不是您定義它的位置。
第一點(diǎn)類似于在子作用域中重新定義任何變量的情況:
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
,整個(gè)事情應(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
中,并使用該變量來(lái)得到我們想要的參考。在 speak_bound
中,我們使用 bind
來(lái)永久將 this
指向給定對(duì)象。
ES2015 為我們帶來(lái)了一種新的選擇:箭頭函數(shù)。
與“普通”函數(shù)不同,箭頭函數(shù)不會(huì)通過(guò)設(shè)置自己的值來(lái)隱藏其父作用域的 this
值。相反,他們從詞匯上解析其含義。
換句話說(shuō),如果您在箭頭函數(shù)中使用 this
,JavaScript 會(huì)像查找任何其他變量一樣查找其值。
首先,它檢查本地范圍內(nèi)的 this
值。由于箭頭函數(shù)沒(méi)有設(shè)置一個(gè),因此它不會(huì)找到一個(gè)。接下來(lái),它檢查 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ù)的更多詳細(xì)信息,請(qǐng)查看 Envato Tuts+ 講師 Dan Wellman 的有關(guān) JavaScript ES6 基礎(chǔ)知識(shí)的精彩課程,以及有關(guān)箭頭函數(shù)的 MDN 文檔。
結(jié)論
到目前為止,我們已經(jīng)了解了很多內(nèi)容!在本文中,您了解到:
- 變量在編譯期間注冊(cè)到其作用域,并在執(zhí)行期間與其賦值相關(guān)聯(lián)。
- 在賦值之前引用使用?
let
或 class="inline">const 聲明的變量會(huì)引發(fā)ReferenceError
,并且此類變量是范圍到最近的塊。 - 箭頭函數(shù) 允許我們實(shí)現(xiàn)
this
的詞法綁定,并繞過(guò)傳統(tǒng)的動(dòng)態(tài)綁定。
您還了解了提升的兩條規(guī)則:
-
第一條提升規(guī)則:函數(shù)表達(dá)式和
var
聲明在其定義的整個(gè)范圍內(nèi)都可用,但其值為undefined
?直到您的賦值語(yǔ)句執(zhí)行。 - 第二條提升規(guī)則:函數(shù)聲明的名稱及其主體在定義它們的范圍內(nèi)可用。
下一步最好是利用 JavaScript 作用域的新知識(shí)來(lái)理解閉包。為此,請(qǐng)查看 Kyle Simpson 的 Scopes & Closures。
最后,關(guān)于 this
有很多話要說(shuō),我無(wú)法在此介紹。如果該關(guān)鍵字看起來(lái)仍然像是黑魔法,請(qǐng)查看此和對(duì)象原型來(lái)了解它。
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)

The main reasons why WordPress causes the surge in server CPU usage include plug-in problems, inefficient database query, poor quality of theme code, or surge in traffic. 1. First, confirm whether it is a high load caused by WordPress through top, htop or control panel tools; 2. Enter troubleshooting mode to gradually enable plug-ins to troubleshoot performance bottlenecks, use QueryMonitor to analyze the plug-in execution and delete or replace inefficient plug-ins; 3. Install cache plug-ins, clean up redundant data, analyze slow query logs to optimize the database; 4. Check whether the topic has problems such as overloading content, complex queries, or lack of caching mechanisms. It is recommended to use standard topic tests to compare and optimize the code logic. Follow the above steps to check and solve the location and solve the problem one by one.

Miniving JavaScript files can improve WordPress website loading speed by removing blanks, comments, and useless code. 1. Use cache plug-ins that support merge compression, such as W3TotalCache, enable and select compression mode in the "Minify" option; 2. Use a dedicated compression plug-in such as FastVelocityMinify to provide more granular control; 3. Manually compress JS files and upload them through FTP, suitable for users familiar with development tools. Note that some themes or plug-in scripts may conflict with the compression function, and you need to thoroughly test the website functions after activation.

Methods to optimize WordPress sites that do not rely on plug-ins include: 1. Use lightweight themes, such as Astra or GeneratePress, to avoid pile-up themes; 2. Manually compress and merge CSS and JS files to reduce HTTP requests; 3. Optimize images before uploading, use WebP format and control file size; 4. Configure.htaccess to enable browser cache, and connect to CDN to improve static resource loading speed; 5. Limit article revisions and regularly clean database redundant data.

TransientsAPI is a built-in tool in WordPress for temporarily storing automatic expiration data. Its core functions are set_transient, get_transient and delete_transient. Compared with OptionsAPI, transients supports setting time of survival (TTL), which is suitable for scenarios such as cache API request results and complex computing data. When using it, you need to pay attention to the uniqueness of key naming and namespace, cache "lazy deletion" mechanism, and the issue that may not last in the object cache environment. Typical application scenarios include reducing external request frequency, controlling code execution rhythm, and improving page loading performance.

The most effective way to prevent comment spam is to automatically identify and intercept it through programmatic means. 1. Use verification code mechanisms (such as Googler CAPTCHA or hCaptcha) to effectively distinguish between humans and robots, especially suitable for public websites; 2. Set hidden fields (Honeypot technology), and use robots to automatically fill in features to identify spam comments without affecting user experience; 3. Check the blacklist of comment content keywords, filter spam information through sensitive word matching, and pay attention to avoid misjudgment; 4. Judge the frequency and source IP of comments, limit the number of submissions per unit time and establish a blacklist; 5. Use third-party anti-spam services (such as Akismet, Cloudflare) to improve identification accuracy. Can be based on the website

When developing Gutenberg blocks, the correct method of enqueue assets includes: 1. Use register_block_type to specify the paths of editor_script, editor_style and style; 2. Register resources through wp_register_script and wp_register_style in functions.php or plug-in, and set the correct dependencies and versions; 3. Configure the build tool to output the appropriate module format and ensure that the path is consistent; 4. Control the loading logic of the front-end style through add_theme_support or enqueue_block_assets to ensure that the loading logic of the front-end style is ensured.

To add custom user fields, you need to select the extension method according to the platform and pay attention to data verification and permission control. Common practices include: 1. Use additional tables or key-value pairs of the database to store information; 2. Add input boxes to the front end and integrate with the back end; 3. Constrain format checks and access permissions for sensitive data; 4. Update interfaces and templates to support new field display and editing, while taking into account mobile adaptation and user experience.

robots.txt is crucial to the SEO of WordPress websites, and can guide search engines to crawl behavior, avoid duplicate content and improve efficiency. 1. Block system paths such as /wp-admin/ and /wp-includes/, but avoid accidentally blocking the /uploads/ directory; 2. Add Sitemap paths such as Sitemap: https://yourdomain.com/sitemap.xml to help search engines quickly discover site maps; 3. Limit /page/ and URLs with parameters to reduce crawler waste, but be careful not to block important archive pages; 4. Avoid common mistakes such as accidentally blocking the entire site, cache plug-in affecting updates, and ignoring the matching of mobile terminals and subdomains.
