閉包講解
ES6回顧:閉包->(優點:實現工廠函數、記憶化和異步實現)、(應用場景:Promise的then與catch的回調、async/await、柯里化函數)
以下是與 JavaScript 閉包相關的常見考點整理,結合 Promise、async/await、緩存結果、工廠函數、柯里化等內容,并依據我搜索到的資料進行詳細說明:
1. 閉包(Closure)
- 定義:由函數及其引用的外部詞法環境變量組成,即使外部函數執行完畢,內部函數仍能訪問這些變量 。
- 作用:
- 延長外部函數變量的生命周期,使外部可操作內部數據(如模塊化封裝)。
- 避免全局變量污染,實現私有變量 。
- 缺點:不當使用會導致內存泄漏(變量無法被回收)。
- 應用場景:
- 緩存結果:通過閉包保存計算結果,避免重復計算(記憶化函數)。
- 工廠函數:生成帶有特定配置的函數實例(如計數器生成器)。
- 柯里化:拆分多參數函數為鏈式單參數調用,依賴閉包保存中間參數 。
- 異步回調:在事件處理或定時器中保留上下文 。
2. Promise
- 核心概念:
- 三種狀態:
pending
、fulfilled
、rejected
,狀態一旦改變不可逆 。 - 解決回調地獄(Callback Hell),支持鏈式調用
.then()
和.catch()
。
- 三種狀態:
- 常用方法:
Promise.all()
:所有 Promise 成功時返回結果數組,任一失敗立即拒絕 。Promise.race()
:首個完成的 Promise 決定最終狀態 。Promise.resolve()
/Promise.reject()
:快速創建成功/失敗的 Promise 。
- 手寫實現:常考手寫
Promise.all
和Promise.race
的實現 。
3. async/await
- 本質:基于 Generator 和 Promise 的語法糖,使異步代碼更接近同步寫法 。
- 規則:
async
函數返回 Promise 對象,return
值會被Promise.resolve()
包裝 。await
后接 Promise,暫停當前函數執行直到 Promise 完成(屬于微任務)。await
只能在async
函數內使用,否則需通過立即調用異步函數(如(async () => { ... })()
)。
- 錯誤處理:用
try...catch
捕獲await
后的 Promise 拒絕 。 - 執行順序:
await
后的代碼相當于放入.then()
中,屬于微任務隊列 。
function asyncToGenerator(fn) {// 返回函數return function() {const gen = fn.apply(this, arguments);//生成器// 方法返回一個Promise對象return new Promise((resolve, reject) => {function step(key, arg) {// console.log(arg)let result;try {result = gen[key](arg);//生成器返回數據{value,done:bool}// 移除調試日志以保持輸出整潔} catch (error) {return reject(error);}//如果執行器執行完成if (result.done) {return resolve(result.value);}//如果執行器未完成,將當前結果傳入Promise的resolve方法中,并遞歸調用step方法Promise.resolve(result.value).then(v => step("next", v),e => step("throw", e));}step("next");});};
}// 使用示例(已修復)
const asyncFunction = asyncToGenerator(function* () {// 修正 Promise 的創建方式,使用箭頭函數包裹 resolveconst result = yield new Promise(resolve => setTimeout(() => resolve("result"), 1000));const result1 = yield new Promise(resolve => setTimeout(() => resolve("result2"), 2000));console.log(result, result1); // 輸出: "result" "result2"(3秒后)
});asyncFunction(); // 啟動執行
4. 柯里化(Currying)
- 定義:將多參數函數轉換為一系列單參數函數鏈式調用的技術,依賴閉包保存中間參數 。
- 示例:
function add(x) {return function(y) {return x + y;};}add(2)(3); // 5
- 應用:參數復用、延遲執行、函數組合 。
5. 工廠函數
- 作用:通過閉包生成具有獨立狀態的函數實例。例如生成獨立的計數器:
function createCounter() {let count = 0;return function() {return ++count;};}const counter1 = createCounter(); // 獨立作用域
- 場景:封裝私有變量、實現模塊化 。
6. 閉包與異步編程的結合
- 事件回調:在閉包中使用
async/await
處理異步邏輯,避免全局變量污染 。
button.addEventListener('click', () => {(async () => {const data = await fetchData();console.log(data);})();});
- 定時器:閉包保存定時器狀態,結合
async/await
控制執行流程 。
7. 內存管理
- 內存泄漏:閉包長期引用外部變量會導致內存無法釋放,需及時解除引用(如手動置
null
)。 - 優化:避免不必要的閉包,或在不需要時清除事件監聽器、定時器等 。
總結
閉包是 JavaScript 的核心概念,與異步編程(Promise/async/await)、函數式編程(柯里化、工廠函數)緊密相關。理解閉包的作用域機制、內存管理,以及與其他特性的結合方式,是應對面試和實際開發的關鍵。
async/await 底層實現解析
async/await 是 JavaScript 中處理異步操作的語法糖,其底層實現基于 Promise 和 Generator(生成器) 的協同機制。通過分析資料中的代碼轉換、設計原理和規范定義,其核心實現邏輯可拆解如下:
一、核心依賴:Promise 與 Generator 的協作
-
Promise 的基礎作用
async/await 的異步控制完全依賴于 Promise 的鏈式調用。每個await
表達式本質上會創建一個 Promise,并將后續代碼包裝到.then()
中等待執行。例如:async function foo() {const result = await somePromise; // 等價于 somePromise.then(result => {...}) }
這種設計使得異步操作的 狀態管理 和 錯誤傳播 能夠通過 Promise 鏈式結構實現。
-
Generator 的流程控制
Generator 函數通過yield
關鍵字暫停執行,并通過迭代器(Iterator)手動恢復。async/await 利用這一特性,將異步代碼的 暫停-恢復機制 轉化為生成器函數的yield
操作。例如,以下代碼:async function a() {const res = await asyncTask(); }
會被 Babel 轉換為使用 Generator 的代碼:
function a() {return __awaiter(this, void 0, void 0, function* () {const res = yield asyncTask();}); }
這里的
yield
替代了await
,而__awaiter
函數負責管理生成器的迭代。
二、轉換邏輯:代碼降級與執行器封裝
通過 Babel/TypeScript 等工具的代碼轉換,async/await 的實現可拆解為以下步驟:
-
生成器函數包裝
async 函數被轉換為生成器函數,await
被替換為yield
。例如:// 原始代碼 async function fetchData() {const data = await fetch(url);return data; }// 轉換后代碼(簡化) function fetchData() {return __awaiter(this, function* () {const data = yield fetch(url);return data;}); }
-
執行器函數(如
__awaiter
)的作用
執行器負責驅動生成器的迭代,并處理 Promise 的鏈式調用。其核心邏輯如下:- 將生成器的每個
yield
值包裝為 Promise。 - 通過
generator.next(value)
將 Promise 的結果傳遞回生成器。 - 捕獲錯誤并通過
generator.throw(error)
拋出異常。
function __awaiter(generator) {return new Promise((resolve, reject) => {function step(result) {if (result.done) {resolve(result.value);} else {Promise.resolve(result.value).then(value => step(generator.next(value)), // 傳遞結果并繼續迭代error => step(generator.throw(error)) // 拋出錯誤);}}step(generator.next());}); }
此過程實現了 自動迭代 和 錯誤冒泡,使代碼看似同步執行。
- 將生成器的每個
三、執行順序與事件循環的關聯
-
微任務隊列的調度
await
后的代碼會被包裝為微任務(Microtask),在 Promise 解決后加入微任務隊列。例如:async function demo() {console.log(1);await Promise.resolve();console.log(2); // 相當于 Promise.resolve().then(() => console.log(2)) } demo(); console.log(3); // 輸出順序:1 → 3 → 2
這種機制確保了異步代碼的執行不會阻塞主線程。
-
協程(Coroutine)模型的實現
async/await 通過生成器和 Promise 模擬了協程的 掛起-恢復 行為:- 掛起:在
await
處暫停生成器,釋放主線程。 - 恢復:當 Promise 解決后,通過執行器繼續生成器的迭代。
- 掛起:在
四、錯誤處理機制的實現
-
try/catch 的轉換
async 函數中的try/catch
會被轉換為 Promise 的.catch()
鏈。例如:async function foo() {try {await somePromise();} catch (err) {handleError(err);} }
轉換后邏輯:
function* foo() {try {const result = yield somePromise();} catch (err) {handleError(err);} }
執行器在生成器拋出錯誤時觸發
reject
。 -
未捕獲異常的傳播
若未使用try/catch
,錯誤會通過 Promise 鏈冒泡到頂層,觸發unhandledrejection
事件。
五、性能與優化考量
-
生成器與 Promise 的開銷
async/await 相比原生 Promise 鏈會引入額外開銷(如生成器對象的創建),但在現代引擎中差異可忽略。 -
并發的實現方式
需顯式使用Promise.all()
實現并行,避免順序等待:async function parallel() {const [a, b] = await Promise.all([task1(), task2()]); // 并行執行 }
若直接順序
await
,會導致任務串行執行。
總結:async/await 的架構設計
層級 | 實現機制 | 作用 |
---|---|---|
語法層 | async /await 關鍵字 | 提供同步代碼風格的異步寫法 |
轉換層 | Babel/TypeScript 代碼降級 | 將 async/await 轉為 Generator + Promise |
運行時層 | 生成器迭代器 + Promise 鏈 | 管理暫停/恢復、錯誤傳播 |
事件循環層 | 微任務隊列調度 | 確保異步代碼非阻塞執行 |
通過多層抽象,async/await 將復雜的異步流程控制簡化為直觀的同步式代碼,同時保持與 Promise 的完全兼容性。