搜尋
首頁web前端js教程揭開JavaScript關閉,回調和IIFES的神秘面紗

深入浅出JavaScript闭包、回调函数和立即执行函数表达式 (IIFE)

本文將深入探討現代JavaScript開發中三個至關重要的概念:閉包、回調函數和立即執行函數表達式 (IIFE)。我們已詳細了解變量作用域和提升,現在讓我們完成探索之旅。

核心要點

  • JavaScript閉包是能夠訪問其父作用域變量的函數,即使父函數已執行完畢,閉包仍然可以記住並操作這些變量。
  • 回調函數是作為參數傳遞給其他函數的函數,這些函數隨後在外部函數內執行,從而提供了一種延遲執行或維護異步操作順序的方法。
  • 立即執行函數表達式 (IIFE) 是在定義後立即執行的函數,用於保護變量的作用域並防止全局作用域污染。
  • 閉包既可以讀取也可以更新存儲在其作用域中的變量,並且這些更新對任何訪問這些變量的閉包都是可見的,這證明了閉包存儲的是變量的引用,而不是值。
  • 使用IIFE有助於在函數內創建私有作用域,從而更好地管理變量並防止外部訪問這些變量。
  • 這些概念(閉包、回調函數和IIFE)的組合為編寫簡潔、高效和安全的JavaScript代碼提供了強大的工具,可以封裝功能並避免全局作用域污染。

閉包

在JavaScript中,閉包是任何保留對其父作用域變量引用的函數,即使父函數已返回

實際上,任何函數都可以被認為是閉包,因為正如我們在本教程第一部分的變量作用域部分中學到的那樣,函數可以引用或訪問:

  • 其自身函數作用域中的任何變量和參數
  • 外部(父)函數的任何變量和參數
  • 全局作用域中的任何變量

因此,您可能已經在不知不覺中使用了閉包。但我們的目標不僅僅是使用它們——而是理解它們。如果我們不了解它們的工作原理,我們就無法正確地使用它們。為此,我們將上述閉包定義分解為三個易於理解的要點。

要點1:您可以引用在當前函數外部定義的變量。

function setLocation(city) {
  var country = "France";

  function printLocation() {
    console.log("You are in " + city + ", " + country);
  }

  printLocation();
}

setLocation("Paris"); // 输出:You are in Paris, France

在這個代碼示例中,printLocation() 函數引用了封閉(父)setLocation() 函數的 country 變量和 city 參數。結果是,當調用 setLocation() 時,printLocation() 成功地使用前者的變量和參數輸出“You are in Paris, France”。

要點2:內部函數即使在外部函數返回後,也可以引用外部函數中定義的變量。

function setLocation(city) {
  var country = "France";

  function printLocation() {
    console.log("You are in " + city + ", " + country);
  }

  printLocation();
}

setLocation("Paris"); // 输出:You are in Paris, France

這與第一個示例幾乎相同,只是這次 printLocation() 外部 setLocation() 函數中返回,而不是立即調用。因此,currentLocation 的值是內部 printLocation() 函數。

如果我們像這樣提醒 currentLocationalert(currentLocation); – 我們將得到以下輸出:

function setLocation(city) {
  var country = "France";

  function printLocation() {
    console.log("You are in " + city + ", " + country);
  }

  return printLocation;
}

var currentLocation = setLocation("Paris");

currentLocation(); // 输出:You are in Paris, France

正如我們所看到的,printLocation() 在其詞法作用域之外執行。 setLocation() 似乎消失了,但 printLocation() 仍然可以訪問並“記住”其變量(country)和參數(city)。

閉包(內部函數)能夠記住其周圍的作用域(外部函數),即使它在其詞法作用域之外執行。因此,您可以稍後在程序中的任何時間調用它。

要點3:內部函數通過引用存儲其外部函數的變量,而不是通過值。

function printLocation () {
  console.log("You are in " + city + ", " + country);
}

這裡 cityLocation() 返回一個包含兩個閉包的對象——get()set()——它們都引用外部變量 cityget() 獲取 city 的當前值,而 set() 更新它。當第二次調用 myLocation.get() 時,它輸出 city 的更新(當前)值——“Sydney”——而不是默認的“Paris”。

因此,閉包既可以讀取也可以更新其存儲的變量,並且這些更新對任何訪問它們的閉包都是可見的。這意味著閉包存儲的是對其外部變量的引用,而不是複制其值。這是一個非常重要的點,因為不知道這一點可能會導致一些難以發現的邏輯錯誤——正如我們在“立即執行函數表達式 (IIFE)”部分將看到的。

閉包的一個有趣特性是,閉包中的變量會自動隱藏。閉包在其封閉變量中存儲數據,而不提供直接訪問它們的方法。改變這些變量的唯一方法是間接地訪問它們。例如,在最後一個代碼片段中,我們看到我們只能通過使用 get()set() 閉包來間接修改變量 city

我們可以利用這種行為在對像中存儲私有數據。與其將數據存儲為對象的屬性,不如將其存儲為構造函數中的變量,然後使用閉包作為引用這些變量的方法。

如您所見,閉包周圍沒有什麼神秘或深奧的東西——只需要記住三個簡單的要點。

回調函數

在JavaScript中,函數是一等公民。這一事實的結果之一是,函數可以作為參數傳遞給其他函數,也可以由其他函數返回。

將其他函數作為參數或返回函數作為其結果的函數稱為高階函數,作為參數傳遞的函數稱為回調函數。它被稱為“回調”,因為在某個時間點,它會被高階函數“回調”。

回調函數有很多日常用途。其中之一是當我們使用瀏覽器窗口對象的 setTimeout()setInterval() 方法時——這些方法接受並執行回調函數:

function setLocation(city) {
  var country = "France";

  function printLocation() {
    console.log("You are in " + city + ", " + country);
  }

  printLocation();
}

setLocation("Paris"); // 输出:You are in Paris, France

另一個例子是當我們將事件監聽器附加到頁面上的元素時。通過這樣做,我們實際上提供了一個指向回調函數的指針,當事件發生時將調用該函數。

function setLocation(city) {
  var country = "France";

  function printLocation() {
    console.log("You are in " + city + ", " + country);
  }

  return printLocation;
}

var currentLocation = setLocation("Paris");

currentLocation(); // 输出:You are in Paris, France

理解高階函數和回調函數工作原理的最簡單方法是創建您自己的高階函數和回調函數。所以,讓我們現在創建一個:

function printLocation () {
  console.log("You are in " + city + ", " + country);
}

這裡我們創建了一個函數 fullName(),它接受三個參數——兩個用於名字和姓氏,一個用於回調函數。然後,在 console.log() 語句之後,我們放置一個函數調用,該調用將觸發實際的回調函數——在 fullName() 下面定義的 greeting() 函數。最後,我們調用 fullName(),其中 greeting() 作為變量傳遞——沒有括號——因為我們不希望它立即執行,而只是希望指向它以便稍後由 fullName() 使用。

我們正在傳遞函數定義,而不是函數調用。這可以防止回調函數立即執行,這與回調函數背後的理念不符。作為函數定義傳遞,它們可以在任何時間和包含函數中的任何點執行。此外,因為回調函數的行為就像它們實際上放置在該函數內部一樣,所以它們實際上是閉包:它們可以訪問包含函數的變量和參數,甚至可以訪問全局作用域中的變量。

回調函數可以是現有函數(如前面的示例所示),也可以是匿名函數,我們在調用高階函數時創建匿名函數,如以下示例所示:

function cityLocation() {
  var city = "Paris";

  return {
    get: function() { console.log(city); },
    set: function(newCity) { city = newCity; }
  };
}

var myLocation = cityLocation();

myLocation.get(); // 输出:Paris
myLocation.set('Sydney');
myLocation.get(); // 输出:Sydney

回調函數在JavaScript庫中大量使用,以提供通用性和可重用性。它們允許輕鬆自定義和/或擴展庫方法。此外,代碼更易於維護,更簡潔易讀。每當您需要將不必要的重複代碼模式轉換為更抽象/通用的函數時,回調函數都會派上用場。

假設我們需要兩個函數——一個打印已發布文章信息的函數,另一個打印已發送消息信息的函數。我們創建了它們,但我們注意到我們的邏輯的一部分在這兩個函數中都重複了。我們知道,在一個地方擁有相同的一段代碼是不必要的,而且難以維護。那麼,解決方案是什麼呢?讓我們在下一個示例中說明它:

function setLocation(city) {
  var country = "France";

  function printLocation() {
    console.log("You are in " + city + ", " + country);
  }

  printLocation();
}

setLocation("Paris"); // 输出:You are in Paris, France

我們在這裡所做的是將重複的代碼模式(console.log(item)var date = new Date())放入一個單獨的通用函數(publish())中,只將特定數據保留在其他函數中——這些函數現在是回調函數。這樣,使用同一個函數,我們可以打印各種相關事物的相關信息——消息、文章、書籍、雜誌等等。您唯一需要做的就是為每種類型創建一個專門的回調函數,並將其作為參數傳遞給 publish() 函數。

立即執行函數表達式 (IIFE)

立即執行函數表達式,或 IIFE(發音為“iffy”),是一個立即在其創建後執行的函數表達式(命名或匿名)。

此模式有兩種略微不同的語法變體:

function setLocation(city) {
  var country = "France";

  function printLocation() {
    console.log("You are in " + city + ", " + country);
  }

  return printLocation;
}

var currentLocation = setLocation("Paris");

currentLocation(); // 输出:You are in Paris, France

要將常規函數轉換為 IIFE,您需要執行兩個步驟:

  1. 您需要將整個函數括在括號中。顧名思義,IIFE 必須是函數表達式,而不是函數定義。因此,封閉括號的目的是將函數定義轉換為表達式。這是因為在JavaScript中,括號中的所有內容都被視為表達式。
  2. 您需要在最後添加一對括號(變體 1),或者在閉合大括號之後添加一對括號(變體 2),這會導致函數立即執行。

還需要記住三件事:

首先,如果您將函數分配給變量,則不需要將整個函數括在括號中,因為它已經是表達式了:

function printLocation () {
  console.log("You are in " + city + ", " + country);
}

其次,IIFE 結尾需要分號,否則您的代碼可能無法正常工作。

第三,您可以向 IIFE 傳遞參數(它畢竟是一個函數),如下面的示例所示:

function cityLocation() {
  var city = "Paris";

  return {
    get: function() { console.log(city); },
    set: function(newCity) { city = newCity; }
  };
}

var myLocation = cityLocation();

myLocation.get(); // 输出:Paris
myLocation.set('Sydney');
myLocation.get(); // 输出:Sydney

將全局對像作為參數傳遞給 IIFE 是一種常見模式,以便在函數內部訪問它而無需使用 window 對象,這使得代碼獨立於瀏覽器環境。以下代碼創建了一個變量 global,無論您使用什麼平台,它都將引用全局對象:

function showMessage(message) {
  setTimeout(function() {
    alert(message);
  }, 3000);
}

showMessage('Function called 3 seconds ago');

這段代碼在瀏覽器中(全局對像是 window)或 Node.js 環境中(我們使用特殊變量 global 引用全局對象)都能工作。

IIFE 的一大好處是,使用它時,您不必擔心用臨時變量污染全局空間。您在 IIFE 內部定義的所有變量都將是局部的。讓我們檢查一下:

<!-- HTML -->
<button id="btn">Click me</button>

<!-- JavaScript -->
function showMessage() {
  alert('Woohoo!');
}

var el = document.getElementById("btn");
el.addEventListener("click", showMessage);

在這個示例中,第一個 console.log() 語句工作正常,但第二個語句失敗了,因為由於 IIFE,變量 todaycurrentTime 變成了局部變量。

我們已經知道閉包會保留對外部變量的引用,因此,它們會返回最新/更新的值。那麼,您認為以下示例的輸出是什麼?

function setLocation(city) {
  var country = "France";

  function printLocation() {
    console.log("You are in " + city + ", " + country);
  }

  printLocation();
}

setLocation("Paris"); // 输出:You are in Paris, France

您可能期望水果的名稱會以一秒鐘的間隔一個接一個地打印出來。但是,實際上,輸出是四次“undefined”。那麼,問題出在哪裡呢?

問題在於,在 console.log() 語句中,i 的值對於循環的每次迭代都等於 4。並且,由於我們在 fruits 數組中索引 4 處沒有任何內容,因此輸出為“undefined”。 (記住,在JavaScript中,數組的索引從 0 開始。)當 i 等於 4 時,循環終止。

為了解決這個問題,我們需要為循環創建的每個函數提供一個新的作用域——這將捕獲 i 變量的當前狀態。我們通過在 IIFE 中關閉 setTimeout() 方法,並定義一個私有變量來保存 i 的當前副本,來做到這一點。

function setLocation(city) {
  var country = "France";

  function printLocation() {
    console.log("You are in " + city + ", " + country);
  }

  return printLocation;
}

var currentLocation = setLocation("Paris");

currentLocation(); // 输出:You are in Paris, France

我們還可以使用以下變體,它執行相同的任務:

function printLocation () {
  console.log("You are in " + city + ", " + country);
}

IIFE 通常用於創建作用域以封裝模塊。在模塊內,存在一個自包含的私有作用域,可以防止意外修改。這種技術稱為模塊模式,是使用閉包管理作用域的強大示例,它在許多現代JavaScript庫(例如jQuery和Underscore)中大量使用。

結論

本教程的目的是盡可能清晰簡潔地介紹這些基本概念——作為一組簡單的原則或規則。很好地理解它們是成為一名成功且高效的JavaScript開發人員的關鍵。

為了更詳細和深入地解釋此處介紹的主題,我建議您閱讀 Kyle Simpson 的《你不知道JS:作用域與閉包》。

(後續內容,即FAQ部分,由於篇幅過長,已省略。如有需要,請提出具體問題。)

以上是揭開JavaScript關閉,回調和IIFES的神秘面紗的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
在JavaScript中替換字符串字符在JavaScript中替換字符串字符Mar 11, 2025 am 12:07 AM

JavaScript字符串替換方法詳解及常見問題解答 本文將探討兩種在JavaScript中替換字符串字符的方法:在JavaScript代碼內部替換和在網頁HTML內部替換。 在JavaScript代碼內部替換字符串 最直接的方法是使用replace()方法: str = str.replace("find","replace"); 該方法僅替換第一個匹配項。要替換所有匹配項,需使用正則表達式並添加全局標誌g: str = str.replace(/fi

構建您自己的Ajax Web應用程序構建您自己的Ajax Web應用程序Mar 09, 2025 am 12:11 AM

因此,在這裡,您準備好了解所有稱為Ajax的東西。但是,到底是什麼? AJAX一詞是指用於創建動態,交互式Web內容的一系列寬鬆的技術。 Ajax一詞,最初由Jesse J創造

10個JQuery Fun and Games插件10個JQuery Fun and Games插件Mar 08, 2025 am 12:42 AM

10款趣味橫生的jQuery遊戲插件,讓您的網站更具吸引力,提升用戶粘性!雖然Flash仍然是開發休閒網頁遊戲的最佳軟件,但jQuery也能創造出令人驚喜的效果,雖然無法與純動作Flash遊戲媲美,但在某些情況下,您也能在瀏覽器中獲得意想不到的樂趣。 jQuery井字棋遊戲 遊戲編程的“Hello world”,現在有了jQuery版本。 源碼 jQuery瘋狂填詞遊戲 這是一個填空遊戲,由於不知道單詞的上下文,可能會產生一些古怪的結果。 源碼 jQuery掃雷遊戲

jQuery視差教程 - 動畫標題背景jQuery視差教程 - 動畫標題背景Mar 08, 2025 am 12:39 AM

本教程演示瞭如何使用jQuery創建迷人的視差背景效果。 我們將構建一個帶有分層圖像的標題橫幅,從而創造出令人驚嘆的視覺深度。 更新的插件可與JQuery 1.6.4及更高版本一起使用。 下載

如何創建和發布自己的JavaScript庫?如何創建和發布自己的JavaScript庫?Mar 18, 2025 pm 03:12 PM

文章討論了創建,發布和維護JavaScript庫,專注於計劃,開發,測試,文檔和促銷策略。

如何在瀏覽器中優化JavaScript代碼以進行性能?如何在瀏覽器中優化JavaScript代碼以進行性能?Mar 18, 2025 pm 03:14 PM

本文討論了在瀏覽器中優化JavaScript性能的策略,重點是減少執行時間並最大程度地減少對頁面負載速度的影響。

Matter.js入門:簡介Matter.js入門:簡介Mar 08, 2025 am 12:53 AM

Matter.js是一個用JavaScript編寫的2D剛體物理引擎。此庫可以幫助您輕鬆地在瀏覽器中模擬2D物理。它提供了許多功能,例如創建剛體並為其分配質量、面積或密度等物理屬性的能力。您還可以模擬不同類型的碰撞和力,例如重力摩擦力。 Matter.js支持所有主流瀏覽器。此外,它也適用於移動設備,因為它可以檢測觸摸並具有響應能力。所有這些功能都使其值得您投入時間學習如何使用該引擎,因為這樣您就可以輕鬆創建基於物理的2D遊戲或模擬。在本教程中,我將介紹此庫的基礎知識,包括其安裝和用法,並提供一

使用jQuery和Ajax自動刷新DIV內容使用jQuery和Ajax自動刷新DIV內容Mar 08, 2025 am 12:58 AM

本文演示瞭如何使用jQuery和ajax自動每5秒自動刷新DIV的內容。 該示例從RSS提要中獲取並顯示了最新的博客文章以及最後的刷新時間戳。 加載圖像是選擇

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具