本文將深入探討現代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()
函數。
如果我們像這樣提醒 currentLocation
– alert(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()
——它們都引用外部變量 city
。 get()
獲取 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,您需要執行兩個步驟:
- 您需要將整個函數括在括號中。顧名思義,IIFE 必須是函數表達式,而不是函數定義。因此,封閉括號的目的是將函數定義轉換為表達式。這是因為在JavaScript中,括號中的所有內容都被視為表達式。
- 您需要在最後添加一對括號(變體 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,變量 today
和 currentTime
變成了局部變量。
我們已經知道閉包會保留對外部變量的引用,因此,它們會返回最新/更新的值。那麼,您認為以下示例的輸出是什麼?
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中文網其他相關文章!

引言我知道你可能會覺得奇怪,JavaScript、C 和瀏覽器之間到底有什麼關係?它們之間看似毫無關聯,但實際上,它們在現代網絡開發中扮演著非常重要的角色。今天我們就來深入探討一下這三者之間的緊密聯繫。通過這篇文章,你將了解到JavaScript如何在瀏覽器中運行,C 在瀏覽器引擎中的作用,以及它們如何共同推動網頁的渲染和交互。 JavaScript與瀏覽器的關係我們都知道,JavaScript是前端開發的核心語言,它直接在瀏覽器中運行,讓網頁變得生動有趣。你是否曾經想過,為什麼JavaScr

Node.js擅長於高效I/O,這在很大程度上要歸功於流。 流媒體匯總處理數據,避免內存過載 - 大型文件,網絡任務和實時應用程序的理想。將流與打字稿的類型安全結合起來創建POWE

Python和JavaScript在性能和效率方面的差異主要體現在:1)Python作為解釋型語言,運行速度較慢,但開發效率高,適合快速原型開發;2)JavaScript在瀏覽器中受限於單線程,但在Node.js中可利用多線程和異步I/O提升性能,兩者在實際項目中各有優勢。

JavaScript起源於1995年,由布蘭登·艾克創造,實現語言為C語言。 1.C語言為JavaScript提供了高性能和系統級編程能力。 2.JavaScript的內存管理和性能優化依賴於C語言。 3.C語言的跨平台特性幫助JavaScript在不同操作系統上高效運行。

JavaScript在瀏覽器和Node.js環境中運行,依賴JavaScript引擎解析和執行代碼。 1)解析階段生成抽象語法樹(AST);2)編譯階段將AST轉換為字節碼或機器碼;3)執行階段執行編譯後的代碼。

Python和JavaScript的未來趨勢包括:1.Python將鞏固在科學計算和AI領域的地位,2.JavaScript將推動Web技術發展,3.跨平台開發將成為熱門,4.性能優化將是重點。兩者都將繼續在各自領域擴展應用場景,並在性能上有更多突破。

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。

是的,JavaScript的引擎核心是用C語言編寫的。 1)C語言提供了高效性能和底層控制,適合JavaScript引擎的開發。 2)以V8引擎為例,其核心用C 編寫,結合了C的效率和麵向對象特性。 3)JavaScript引擎的工作原理包括解析、編譯和執行,C語言在這些過程中發揮關鍵作用。


熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

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

熱門文章

熱工具

Atom編輯器mac版下載
最受歡迎的的開源編輯器

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

WebStorm Mac版
好用的JavaScript開發工具

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

禪工作室 13.0.1
強大的PHP整合開發環境