目錄
詞法範圍
編譯過程:鳥瞰
第 1 步:編譯
第 2 步:执行
显微镜下的吊装
提升变量声明
提升函数表达式
提升函数声明
this 和箭头函数" >词法 this 和箭头函数
结论
首頁 CMS教程 &#&按 理解JavaScript中的作用域

理解JavaScript中的作用域

Aug 31, 2023 pm 08:17 PM
javascript作用域 作用域鏈 作用域規則

理解JavaScript中的作用域

範圍,或確定變數所在位置的一組規則,是任何程式語言的最基本概念之一。事實上,它是如此基本,以至於我們很容易忘記這些規則是多麼微妙!

確切理解JavaScript 引擎如何「思考」作用域將使您避免編寫提升可能導致的常見錯誤,讓您做好準備專注於閉包,並讓您離永遠不再編寫錯誤更近一步再次。

...無論如何,它會幫助您理解提升和關閉。

在本文中,我們將了解:

  • JavaScript 中作用域的基礎知識
  • 解釋器如何決定哪些變數屬於哪個作用域
  • 吊掛的實際運作原理
  • ES6 關鍵字 letconst 如何改變遊戲規則

讓我們深入探討。

如果您有興趣了解有關 ES6 的更多資訊以及如何利用語法和功能來改進和簡化 JavaScript 程式碼,為什麼不看看這兩門課程:

詞法範圍

如果您之前寫過一行 JavaScript,您就會知道定義變數的位置決定了您可以使用的位置> 他們。變數的可見性取決於原始碼的結構,這一事實稱為詞法​​ 範圍。

在 JavaScript 中建立作用域有三種方法:

  1. 建立函數。在函數內部宣告的變數僅在該函數內部可見,包括在巢狀函數中。
  2. 使用 letconst 在程式碼區塊內宣告變數。此類聲明僅在區塊內可見。
  3. 建立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'!") 
登入後複製

上面的程式碼片段示範了所有三種作用域機制。您可以在 Node 或 Firefox 中運行它,但 Chrome 還無法與 let 很好地配合。

我們將詳細討論其中的每一個。讓我們先詳細了解 JavaScript 如何確定哪些變數屬於哪個作用域。

編譯過程:鳥瞰

當您執行一段 JavaScript 時,會發生兩件事使其正常運作。

  1. 首先,編譯您的原始碼。
  2. 然後,編譯後的程式碼將會執行。

編譯步驟期間,JavaScript 引擎:

  1. 記下所有變數名稱
  2. 將它們註冊到適當的範圍
  3. 為自己的價值觀保留空間

只有在執行期間,JavaScript 引擎才真正將變數引用的值設為等於其賦值。在那之前,它們是 undefined

第 1 步:編譯

// 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);
登入後複製

讓我們逐步了解編譯器的作用。

首先,它會讀取行 var first_name = "Peleke"。接下來,它確定將變數保存到的範圍。因為我們位於腳本的頂層,所以它意識到我們處於全域範圍。然後,它將變數 first_name 儲存到全域範圍,並將其值初始化為 undefined

其次,編譯器會讀取帶有 function popup (first_name) 的行。因為 function 關鍵字是該行的第一個內容,因此它為函數建立一個新作用域,將函數的定義註冊到全域作用域,並查看內部以尋找變數宣告。

果然,編譯器找到了一個。由於我們函數的第一行有var last_name = "Sengstacke",因此編譯器會將變數last_name 儲存到</em> class=" inline">popup到全域範圍— 並將其值設為undefined

由於函數內不再有變數聲明,編譯器將退回到全域作用域。由於不再有變數聲明,因此此階段已完成。

請注意,我們實際上還沒有運行任何東西。 此時編譯器的工作只是確保它知道每個人的名字;它不關心他們做什麼。

此時,我們的程式知道:

  1. 全局范围内有一个名为 first_name 的变量。
  2. 全局范围内有一个名为 popup 的函数。
  3. popup 范围内有一个名为 last_name 的变量。
  4. first_namelast_name 的值均为 undefined

它并不关心我们是否在代码中的其他地方分配了这些变量值。引擎在执行时会处理这个问题。

第 2 步:执行

在下一步中,引擎再次读取我们的代码,但这一次,执行它。

首先,它读取行 var first_name = "Peleke"。为此,引擎会查找名为 first_name 的变量。由于编译器已经用该名称注册了一个变量,引擎会找到它,并将其值设置为 "Peleke"

接下来,它读取行 function popup (first_name)。由于我们没有在这里执行该函数,因此引擎不感兴趣并跳过它。

最后,它读取行 popup(first_name)。由于我们在这里执行一个函数,因此引擎:

  1. 查找 popup 的值
  2. 查找 first_name 的值
  3. popup 作为函数执行,并传递 first_name 的值作为参数

当执行 popup 时,它会经历相同的过程,但这次是在函数 popup 内。它:

  1. 查找名为 last_name 的变量
  2. last_name 的值设置为等于 "Sengstacke"
  3. 查找 alert,将其作为函数执行,并以 "Peleke Sengstacke" 作为参数

事实证明,幕后发生的事情比我们想象的要多得多!

既然您已经了解了 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!");
}
登入後複製

如果运行此代码,您会注意到三件事:

  1. 在分配之前,您可以引用 foo,但其值为 undefined
  2. 您可以在定义之前调用 已损坏的,但您会收到 TypeError
  3. 可以在定义之前调用 bar,它会按需要工作。

提升是指 JavaScript 使我们声明的所有变量名称在其作用域内的任何地方都可用,包括在我们分配给它们之前 .

代码段中的三种情况是您在自己的代码中需要注意的三种情况,因此我们将一一逐步介绍它们。

提升变量声明

请记住,当 JavaScript 编译器读取像 var foo = "bar" 这样的行时,它会:

  1. 将名称 foo 注册到最近的范围
  2. foo 的值设置为未定义

我们可以在赋值之前使用 foo 的原因是,当引擎查找具有该名称的变量时,它确实存在。这就是为什么它不会抛出 ReferenceError

相反,它获取值 undefined,并尝试使用该值执行您要求的任何操作。通常,这是一个错误。

记住这一点,我们可能会想象 JavaScript 在我们的函数 bar 中看到的更像是这样:

function bar () {
    var foo; // undefined
    if (!foo) {
        // !undefined is true, so alert
        alert(foo + "? This is strange...");
    }
    foo = "bar";
}
登入後複製

如果您愿意的话,这是提升的第一条规则:变量在其整个范围内都可用,但其值为 undefined ,直到您代码分配给他们。

常见的 JavaScript 习惯用法是将所有 var 声明写入其作用域的顶部,而不是首次使用它们的位置。用 Doug Crockford 的话来说,这可以帮助您的代码阅读更像它运行

仔细想想,这是有道理的。当我们以 JavaScript 读取代码的方式编写代码时,为什么 bar 的行为方式非常清楚,不是吗?那么为什么不一直这样写呢?

提升函数表达式

事实上,当我们在定义之前尝试执行 broken 时,我们得到了 TypeError,这只是第一条提升规则的一个特例。

我们定义了一个名为 broken 的变量,编译器会在全局范围内注册该变量,并将其设置为等于 undefined。当我们尝试运行它时,引擎会查找 broken 的值,发现它是 undefined,并尝试将 undefined 作为函数执行.

显然,undefined 不是一个函数,这就是为什么我们得到 TypeError

提升函数声明

最后,回想一下,在定义 bar 之前,我们可以调用它。这是由于第二条提升规则:当 JavaScript 编译器找到函数声明时,它会使其名称和定义在其作用域的顶部可用。再次重写我们的代码:

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读取时,它更有意义,您不觉得吗?

查看:

  1. 变量声明和函数表达式的名称在其整个范围内都可用,但它们的值在赋值之前为 undefined
  2. 函数声明的名称​​和定义在其整个范围内都可用,甚至在其定义之前。

现在让我们来看看两个工作方式稍有不同的新工具:letconst

<strong>let</strong><strong></strong>const 和临时死区<强>

var 声明不同,使用 letconst 声明的变量 被编译器提升。

至少,不完全是。

还记得我们如何调用 已损坏的,但却因为尝试执行 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 编译器在第一遍中将变量注册到其作用域时,它对待 letconst 的方式与处理 var 的方式不同。

当它找到 var 声明时,我们将该变量的名称注册到其范围,并立即将其值初始化为 undefined

但是,使用 let,编译器将变量注册到其作用域,但不会初始化其值为 undefined。相反,它会使变量保持未初始化状态,直到引擎执行您的赋值语句。访问未初始化变量的值会抛出 ReferenceError,这解释了为什么上面的代码片段在运行时会抛出异常。

let 声明和赋值语句的顶部开头之间的空间称为临时死区。该名称源自以下事实:即使引擎知道名为 foo 的变量(位于 bar 范围的顶部),该变量是“死的”,因为它没有值。

...还因为如果您尝试尽早使用它,它会杀死您的程序。

const 关键字的工作方式与 let 相同,但有两个主要区别:

  1. 使用 const 声明时,必须分配一个值。
  2. 不能为使用 const 声明的变量重新赋值。

这可以保证 const始终拥有您最初分配给它的值。

// This is legal
const React = require('react');

// This is totally not legal
const crypto;
crypto = require('crypto');
登入後複製

块范围

letconstvar 在另一方面有所不同:它们的范围大小。

当您使用 var 声明变量时,它在作用域链的尽可能高的位置可见 - 通常是在最近的函数声明的顶部,或者在全局范围,如果您在顶层声明它。

但是,当您使用 letconst 声明变量时,它会尽可能本地可见 - > 在最近的街区内。

块是由大括号分隔的一段代码,如 if/else 块、for 循环,以及显式“阻止”的代码块,如本段代码所示。

"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
登入後複製

如果您在块内使用 constlet 声明变量,则该变量在块内可见,且仅< /em> 分配后。

但是,使用 var 声明的变量在尽可能远的地方可见 - 在本例中是在全局范围内。

如果您对 letconst 的具体细节感兴趣,请查看 Rauschmayer 博士在《探索 ES6:变量和范围》中对它们的介绍,并查看有关它们的 MDN 文档。

词法 <strong>this</strong> 和箭头函数

从表面上看,this 似乎与范围没有太大关系。事实上,JavaScript 并没有根据我们在这里讨论的范围规则来解析 this 的含义。

至少,通常不会。众所周知,JavaScript 不会根据您使用该关键字的位置来解析 this 关键字的含义:

var foo = {
    name: 'Foo',
    languages: ['Spanish', 'French', 'Italian'],
    speak : function speak () {
        this.languages.forEach(function(language) {
            console.log(this.name + " speaks " + language + ".");
        })
    }
};

foo.speak();
登入後複製

我们大多数人都认为 this 表示 fooforEach 循环内,因为这就是它在循环之外的含义。换句话说,我们期望 JavaScript 能够解析 this 词法的含义。

但事实并非如此。

相反,它会在您定义的每个函数中创建一个 this,并根据您如何调用该函数来决定其含义 -不是您定义它的位置

第一点类似于在子作用域中重新定义任何变量的情况:

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,整个事情应该立即清楚!

传统上,要让 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 为我们带来了一种新的选择:箭头函数。

与“普通”函数不同,箭头函数不会通过设置自己的值来隐藏其父作用域的 this 值。相反,他们从词汇上解析其含义。

换句话说,如果您在箭头函数中使用 this,JavaScript 会像查找任何其他变量一样查找其值。

首先,它检查本地范围内的 this 值。由于箭头函数没有设置一个,因此它不会找到一个。接下来,它检查 this 值的范围。如果找到,它将使用它。

这让我们可以像这样重写上面的代码:

var foo = {
    name: 'Foo',
    languages: ['Spanish', 'French', 'Italian'],
    speak : function speak () {
        this.languages.forEach((language) => {
            console.log(this.name + " speaks " + language + ".");
        })
    }
};   
登入後複製

如果您想了解有关箭头函数的更多详细信息,请查看 Envato Tuts+ 讲师 Dan Wellman 的有关 JavaScript ES6 基础知识的精彩课程,以及有关箭头函数的 MDN 文档。

结论

到目前为止,我们已经了解了很多内容!在本文中,您了解到:

  • 变量在编译期间注册到其作用域,并在执行期间与其赋值相关联。
  • 在赋值之前引用使用 let 或 class="inline">const 声明的变量会引发 ReferenceError,并且此类变量是范围到最近的块。
  • 箭头函数 允许我们实现 this 的词法绑定,并绕过传统的动态绑定。

您还了解了提升的两条规则:

  • 第一条提升规则:函数表达式和 var 声明在其定义的整个范围内都可用,但其值为 undefined 直到您的赋值语句执行。
  • 第二条提升规则:函数声明的名称及其主体在定义它们的范围内可用。

下一步最好是利用 JavaScript 作用域的新知识来理解闭包。为此,请查看 Kyle Simpson 的 Scopes & Closures。

最后,关于 this 有很多话要说,我无法在此介绍。如果该关键字看起来仍然像是黑魔法,请查看此和对象原型来了解它。

同時,利用您所學到的知識並減少編寫錯誤!

學習 JavaScript:完整指南

#

我們建立了一個完整的指南來幫助您學習 JavaScript,無論您是剛開始擔任 Web 開發人員還是想探索更高級的主題。

以上是理解JavaScript中的作用域的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何開始WordPress博客:初學者的分步指南 如何開始WordPress博客:初學者的分步指南 Apr 17, 2025 am 08:25 AM

博客是人們在網上表達觀點、意見和見解的理想平台。許多新手渴望建立自己的網站,卻因擔心技術障礙或成本問題而猶豫不決。然而,隨著平台不斷發展以滿足初學者的能力和需求,現在開始變得比以往任何時候都更容易。 本文將逐步指導您如何建立一個WordPress博客,從主題選擇到使用插件提升安全性和性能,助您輕鬆創建自己的網站。 選擇博客主題和方向 在購買域名或註冊主機之前,最好先確定您計劃涵蓋的主題。個人網站可以圍繞旅行、烹飪、產品評論、音樂或任何激發您興趣的愛好展開。專注於您真正感興趣的領域可以鼓勵持續寫作

對於初學者來說,WordPress容易嗎? 對於初學者來說,WordPress容易嗎? Apr 03, 2025 am 12:02 AM

WordPress對初學者來說容易上手。 1.登錄後台後,用戶界面直觀,簡潔的儀表板提供所有必要功能鏈接。 2.基本操作包括創建和編輯內容,所見即所得的編輯器簡化了內容創建。 3.初學者可以通過插件和主題擴展網站功能,學習曲線存在但可以通過實踐掌握。

如何在父分類的存檔頁面上顯示子分類 如何在父分類的存檔頁面上顯示子分類 Apr 19, 2025 pm 11:54 PM

您想了解如何在父分類存檔頁面上顯示子分類嗎?在自定義分類存檔頁面時,您可能需要執行此操作,以使其對訪問者更有用。在本文中,我們將向您展示如何在父分類存檔頁面上輕鬆顯示子分類。為什麼在父分類存檔頁面上顯示子分類?通過在父分類存檔頁面上顯示所有子分類,您可以使其不那麼通用,對訪問者更有用。例如,如果您運行一個關於書籍的WordPress博客,並且有一個名為“主題”的分類法,那麼您可以添加“小說”、“非小說”等子分類法,以便您的讀者可以

如何在 WordPress 中獲取登錄用戶信息以獲得個性化結果 如何在 WordPress 中獲取登錄用戶信息以獲得個性化結果 Apr 19, 2025 pm 11:57 PM

最近,我們向您展示瞭如何通過允許用戶將自己喜歡的帖子保存在個性化庫中來為用戶創建個性化體驗。您可以通過在某些地方(即歡迎屏幕)使用他們的名字,將個性化結果提升到另一個水平。幸運的是,WordPress使獲取登錄用戶的信息變得非常容易。在本文中,我們將向您展示如何檢索與當前登錄用戶相關的信息。我們將利用get_currentuserinfo(); 功能。這可以在主題中的任何地方使用(頁眉、頁腳、側邊欄、頁面模板等)。為了使其工作,用戶必須登錄。因此我們需要使用

wordpress文章列表怎麼調 wordpress文章列表怎麼調 Apr 20, 2025 am 10:48 AM

有四種方法可以調整 WordPress 文章列表:使用主題選項、使用插件(如 Post Types Order、WP Post List、Boxy Stuff)、使用代碼(在 functions.php 文件中添加設置)或直接修改 WordPress 數據庫。

如何在 WordPress 中按帖子過期日期對帖子進行排序 如何在 WordPress 中按帖子過期日期對帖子進行排序 Apr 19, 2025 pm 11:48 PM

過去,我們分享過如何使用PostExpirator插件使WordPress中的帖子過期。好吧,在創建活動列表網站時,我們發現這個插件非常有用。我們可以輕鬆刪除過期的活動列表。其次,多虧了這個插件,按帖子過期日期對帖子進行排序也非常容易。在本文中,我們將向您展示如何在WordPress中按帖子過期日期對帖子進行排序。更新了代碼以反映插件中更改自定義字段名稱的更改。感謝Tajim在評論中讓我們知道。在我們的特定項目中,我們將事件作為自定義帖子類型。現在

如何在 WordPress 中顯示查詢數量和頁面加載時間 如何在 WordPress 中顯示查詢數量和頁面加載時間 Apr 19, 2025 pm 11:51 PM

我們的一位用戶詢問其他網站如何在頁腳中顯示查詢數量和頁面加載時間。您經常會在網站的頁腳中看到這一點,它可能會顯示類似以下內容:“1.248秒內64個查詢”。在本文中,我們將向您展示如何在WordPress中顯示查詢數量和頁面加載時間。只需將以下代碼粘貼到主題文件中您喜歡的任何位置(例如footer.php)。 queriesin

我可以在3天內學習WordPress嗎? 我可以在3天內學習WordPress嗎? Apr 09, 2025 am 12:16 AM

能在三天內學會WordPress。 1.掌握基礎知識,如主題、插件等。 2.理解核心功能,包括安裝和工作原理。 3.通過示例學習基本和高級用法。 4.了解調試技巧和性能優化建議。

See all articles