揭開JavaScript關閉,回調和IIFES的神秘面紗
本文將深入探討現代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中文網其他相關文章!

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

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

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

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

Dreamweaver CS6
視覺化網頁開發工具

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

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務
