
基礎(chǔ)知識(shí)復(fù)習(xí)- 關(guān)于Js中閉包的詳細(xì)學(xué)習(xí)
核心定義
閉包(Closure)是 函數(shù)與其詞法環(huán)境的組合,使內(nèi)部函數(shù)能夠訪問并保留其外部函數(shù)作用域中的變量,即使外部函數(shù)已執(zhí)行完畢。從實(shí)現(xiàn)角度看,閉包是函數(shù)內(nèi)部定義的子函數(shù)與外部變量之間的“橋梁”,允許跨作用域的數(shù)據(jù)訪問。
形成條件
存在嵌套函數(shù):外層函數(shù)內(nèi)定義內(nèi)層函數(shù)。
內(nèi)部函數(shù)引用外部變量:內(nèi)層函數(shù)需訪問外層函數(shù)的局部變量或參數(shù)。
外部函數(shù)返回內(nèi)部函數(shù):通過返回值將閉包暴露到外層作用域。
特性
封裝性:通過閉包隱藏變量,模擬私有屬性(如計(jì)數(shù)器、緩存)。
持久性:閉包中的變量生命周期與閉包本身綁定,不受外層函數(shù)執(zhí)行周期限制。
核心機(jī)制
詞法作用域:閉包的變量訪問規(guī)則由函數(shù)定義時(shí)的位置決定,而非執(zhí)行時(shí)的作用域。
持久化環(huán)境:外層函數(shù)執(zhí)行完畢后,其變量因被內(nèi)層函數(shù)引用而無法被垃圾回收,形成長(zhǎng)期駐留內(nèi)存的狀態(tài)。
示例:
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
此例中,inner
函數(shù)通過閉包持續(xù)訪問并修改外層 count
變量,形成獨(dú)立且持久的狀態(tài)。
工作原理
詞法作用域決定變量訪問范圍。
作用域鏈實(shí)現(xiàn)變量逐層查找。
執(zhí)行上下文保留使外部變量持久化。
通過這一機(jī)制,閉包能夠在函數(shù)執(zhí)行后仍維持對(duì)原作用域變量的引用,支持?jǐn)?shù)據(jù)封裝、狀態(tài)持久化等高級(jí)功能。
詞法作用域綁定
閉包的核心機(jī)制基于 JavaScript 的詞法作用域(靜態(tài)作用域),即函數(shù)的作用域在代碼編寫階段確定,而非運(yùn)行時(shí)動(dòng)態(tài)生成。函數(shù)在定義時(shí)即會(huì)記錄其所在作用域的變量環(huán)境,即使函數(shù)在其詞法作用域之外執(zhí)行,仍能訪問這些變量。
function outer() {
let x = 10;
function inner() {
console.log(x); // 訪問外部作用域的變量
}
return inner;
}
const func = outer();
func(); // 輸出 10(x 仍可訪問)
這里,inner
函數(shù)在 outer
函數(shù)執(zhí)行后仍能通過閉包訪問。
作用域鏈機(jī)制
閉包通過作用域鏈逐級(jí)向上查找變量:
每個(gè)函數(shù)在創(chuàng)建時(shí)會(huì)生成一個(gè)作用域鏈,包含自身作用域及所有外層作用域的變量環(huán)境。當(dāng)內(nèi)部函數(shù)訪問變量時(shí),會(huì)先在自身作用域查找,若未找到則沿作用域鏈向外層作用域查找。
function outer() {
let y = 20;
return function inner() {
return y; // 通過作用域鏈訪問外層變量
};
}
此時(shí),inner
函數(shù)的作用域鏈包含 outer
的作用域,因此能訪問 y
。
執(zhí)行上下文與變量持久化
閉包的持久化依賴于執(zhí)行上下文的保留:
當(dāng)外部函數(shù)執(zhí)行完畢后,其執(zhí)行上下文通常會(huì)被銷毀,但若內(nèi)部函數(shù)引用了外部函數(shù)的變量,則這些變量會(huì)被保留在內(nèi)存中(形成閉包)。
function createCounter() {
let count = 0;
return function() {
return ++count;
};
}
const counter = createCounter();
counter(); // 1(count 未被回收)
此處,count
變量因被閉包引用而長(zhǎng)期駐留內(nèi)存,直至閉包被銷毀。
實(shí)現(xiàn)過程
函數(shù)嵌套與變量引用觸發(fā)閉包捕獲。
作用域鏈保留確保外部變量持久化。
內(nèi)存管理機(jī)制控制變量的生命周期。
從理解觸發(fā)對(duì)工作原理的定義-閉包在 JavaScript 中的實(shí)現(xiàn)過程基于詞法作用域和作用域鏈機(jī)制。
函數(shù)嵌套與變量引用
函數(shù)嵌套:內(nèi)部函數(shù)定義在外部函數(shù)體內(nèi),形成嵌套結(jié)構(gòu)。
變量綁定:內(nèi)部函數(shù)需直接引用外部函數(shù)的變量或參數(shù),此時(shí)外部變量被閉包“捕獲”。
function outer() {
let count = 0;
function inner() {
count++;
return count;
}
return inner;
}
此時(shí)代碼中,inner
函數(shù)引用了外部變量 count
。
執(zhí)行上下文與作用域鏈的保留
外部函數(shù)執(zhí)行:調(diào)用 outer()
時(shí),創(chuàng)建其執(zhí)行上下文,包含變量 count
和作用域鏈。
內(nèi)部函數(shù)創(chuàng)建:inner
函數(shù)在定義時(shí)記錄其詞法環(huán)境(即 outer
的作用域鏈)。
跨作用域傳遞:當(dāng) outer
執(zhí)行完畢并返回 inner
函數(shù)時(shí),outer
的執(zhí)行上下文理論上應(yīng)銷毀,但因 inner
仍引用 count
,該變量被保留在內(nèi)存中。
閉包的實(shí)際運(yùn)行
閉包調(diào)用:將返回的 inner
函數(shù)賦值給變量(如 const counter = outer()
),后續(xù)調(diào)用 counter()
時(shí),沿作用域鏈查找 count
,確認(rèn)其存在于閉包保留的 outer
作用域中。修改 count
的值并返回,實(shí)現(xiàn)狀態(tài)持久化。
內(nèi)存駐留:只要閉包存在(如 counter
未被釋放),count
變量便不會(huì)被垃圾回收。
內(nèi)存管理機(jī)制
變量引用計(jì)數(shù):JavaScript 引擎通過檢查變量是否被閉包引用,決定是否回收內(nèi)存。
手動(dòng)釋放:將閉包變量置為 null
(如 counter = null
),可主動(dòng)觸發(fā)垃圾回收。
let closure = (function() {
let data = "敏感數(shù)據(jù)";
return {
getData: () => data
};
})();
closure = null; // 解除引用,釋放內(nèi)存
此操作可避免內(nèi)存泄漏。
代碼實(shí)現(xiàn)示例
基礎(chǔ)閉包實(shí)現(xiàn)
閉包的核心是內(nèi)部函數(shù)引用外部變量,且外部函數(shù)執(zhí)行后變量仍駐留內(nèi)存。
function createCounter() {
let count = 0; // 外部函數(shù)的變量
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 輸出 1
console.log(counter()); // 輸出 2
說明:createCounter
返回的匿名函數(shù)通過閉包保留了 count
變量,每次調(diào)用 counter()
都會(huì)更新 count
的值。
循環(huán)中的閉包
在循環(huán)中直接創(chuàng)建閉包可能導(dǎo)致變量共享問題。
const funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs(); // VM68:7 Uncaught TypeError: funcs is not a function at :7:1
funcs.forEach(func => func()); // 輸出 3、3、3
原因:
調(diào)用方式錯(cuò)誤:
funcs
是數(shù)組而非函數(shù),直接調(diào)用funcs()
會(huì)報(bào)錯(cuò)TypeError: funcs is not a function
。所有閉包共享同一個(gè)
i
(var
無塊級(jí)作用域)。所有閉包共享全局變量
i
(循環(huán)結(jié)束后i = 3
),因此輸出相同值。
修復(fù)方法(使用 IIFE 或 let
):
// 方法1:使用 IIFE
const funcs = [];
for (var i = 0; i < 3; i++) {
(function(j) {
funcs.push(function() {
console.log(j);
});
})(i);
}
funcs.forEach(func => func());
// 方法2:使用 let(塊級(jí)作用域)
const funcs = [];
for (let i = 0; i < 3; i++) {
funcs.push(function() {
console.log(i);
});
}
funcs.forEach(func => func());
說明:兩種方法均通過隔離作用域使每個(gè)閉包捕獲獨(dú)立的 i
值。
模塊模式封裝私有變量
通過閉包實(shí)現(xiàn)數(shù)據(jù)私有化:
const module = (function() {
let privateData = "私有數(shù)據(jù)";
function privateMethod() {
console.log(privateData);
}
return {
publicMethod: function() {
privateMethod();
}
};
})();
module.publicMethod(); // 輸出 “私有數(shù)據(jù)”
console.log(module.privateData); // undefined(無法訪問)
說明:立即執(zhí)行函數(shù)(IIFE)返回包含公共方法的對(duì)象,私有變量 privateData
僅通過閉包暴露接口。
閉包實(shí)現(xiàn)狀態(tài)保留
在DOM事件處理中保持狀態(tài):
function setupButtons() {
const buttons = document.querySelectorAll("button");
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", function() {
console.log(`按鈕 ${i} 被點(diǎn)擊`);
});
}
}
說明:使用 let
為每個(gè)按鈕事件回調(diào)生成獨(dú)立閉包,正確綁定對(duì)應(yīng)的索引 i
。
通過以上代碼,我們可以簡(jiǎn)單的對(duì)閉包有一個(gè)簡(jiǎn)單的理解。現(xiàn)在我們?cè)敿?xì)的分析閉包問題的拆分,以上就可以應(yīng)付八股面試,以下是對(duì)閉包的一些簡(jiǎn)陋的認(rèn)識(shí)。
詞法作用域?qū)﹂]包的影響機(jī)制
詞法作用域通過固化變量查找路徑和延長(zhǎng)變量生命周期,為閉包提供基礎(chǔ)運(yùn)行環(huán)境。閉包利用這一機(jī)制實(shí)現(xiàn)跨作用域數(shù)據(jù)持久化,但其變量共享問題需通過作用域隔離技術(shù)(如 let
、IIFE)規(guī)避。
概念
詞法作用域(靜態(tài)作用域):函數(shù)的作用域在代碼編寫時(shí)由其物理位置決定,而非運(yùn)行時(shí)動(dòng)態(tài)確定。
閉包:函數(shù)能夠持續(xù)訪問其定義時(shí)所處詞法環(huán)境中的變量,即使該函數(shù)在原始作用域外執(zhí)行。
影響閉包的核心機(jī)制
作用域鏈固化:閉包通過詞法作用域鎖定變量查找路徑,形成包含外部變量的作用域鏈。即使外部函數(shù)執(zhí)行完畢,閉包仍能通過固化鏈訪問變量,也就是我們上文中的持久化環(huán)境。
function outer() {
let x = 10;
function inner() {
console.log(x); // 通過詞法作用域訪問外層x
}
return inner;
}
const closure = outer();
closure(); // 輸出10(閉包保留x的引用)
變量生命周期延長(zhǎng):詞法作用域使閉包引用的變量脫離原始作用域銷毀規(guī)則,只要閉包存在,變量就不會(huì)被垃圾回收。
典型表現(xiàn)
變量共享問題:若閉包依賴的詞法作用域中存在循環(huán)變量(如 var
聲明的全局變量),所有閉包共享同一變量,導(dǎo)致最終值相同。在上文中循環(huán)中的閉包就出現(xiàn)了這樣的場(chǎng)景。
設(shè)計(jì)意義與限制
優(yōu)勢(shì):詞法作用域的靜態(tài)特性使閉包行為可預(yù)測(cè),便于封裝私有變量和維持狀態(tài)。
風(fēng)險(xiǎn):過度依賴閉包可能導(dǎo)致內(nèi)存泄漏(如意外保留大對(duì)象引用)或調(diào)試?yán)щy(作用域鏈復(fù)雜化)。
JavaScript 閉包、執(zhí)行上下文與作用域鏈的作用
三者的協(xié)作關(guān)系
執(zhí)行上下文生成作用域鏈:函數(shù)執(zhí)行時(shí),其執(zhí)行上下文包含作用域鏈,作為變量查找的依據(jù)。
詞法作用域決定作用域鏈結(jié)構(gòu):函數(shù)定義時(shí)的詞法環(huán)境固化作用域鏈層級(jí),與運(yùn)行時(shí)調(diào)用位置無關(guān)。
閉包依賴作用域鏈實(shí)現(xiàn)數(shù)據(jù)持久化:閉包通過固化后的作用域鏈訪問外部變量,即使外層上下文已銷毀。
執(zhí)行上下文:動(dòng)態(tài)管理代碼運(yùn)行環(huán)境,存儲(chǔ)變量、作用域鏈及 this
。
作用域鏈:靜態(tài)固化變量查找路徑,支持閉包和詞法作用域隔離。
閉包:通過作用域鏈實(shí)現(xiàn)跨作用域數(shù)據(jù)持久化和封裝私有變量。
三者共同構(gòu)成 JavaScript 變量管理和閉包功能的核心機(jī)制。
執(zhí)行上下文的作用
管理代碼執(zhí)行環(huán)境:執(zhí)行上下文是 JavaScript 代碼運(yùn)行時(shí)的核心容器,存儲(chǔ)當(dāng)前環(huán)境的變量對(duì)象(VO/AO)、作用域鏈及 this
綁定。每次函數(shù)調(diào)用或全局代碼執(zhí)行時(shí),會(huì)創(chuàng)建新的執(zhí)行上下文,并按“后進(jìn)先出”規(guī)則壓入執(zhí)行棧。
控制變量與函數(shù)生命周期:在創(chuàng)建階段,執(zhí)行上下文預(yù)解析變量和函數(shù)聲明,初始化變量為 undefined
,并綁定作用域鏈。函數(shù)執(zhí)行完畢后,其執(zhí)行上下文從棧中彈出,但閉包引用的變量可能保留在內(nèi)存中。
具體可看 JavaScript執(zhí)行上下文。
閉包的7大核心應(yīng)用場(chǎng)景
封裝私有變量與方法
通過閉包隱藏內(nèi)部變量,僅暴露特定接口,防止外部直接修改數(shù)據(jù),提升代碼安全性和可維護(hù)性。
示例:模塊化開發(fā)中創(chuàng)建獨(dú)立作用域,避免全局污染。
IIFE(立即執(zhí)行函數(shù)表達(dá)式)
通過自執(zhí)行函數(shù)創(chuàng)建獨(dú)立作用域,變量?jī)H在閉包內(nèi)有效:
const myModule = (function() {
let privateVar = '內(nèi)部數(shù)據(jù)'; // 私有變量
function privateMethod() { // 私有方法
console.log(privateVar);
}
return { // 暴露的公共接口
publicMethod: function() {
privateMethod();
}
};
})();
myModule.publicMethod(); //內(nèi)部數(shù)據(jù)
模塊模式(Module Pattern)
結(jié)合閉包與對(duì)象返回,支持多實(shí)例化:
function createModule() {
let state = 0; // 私有狀態(tài)
return {
increment: () => ++state,
getState: () => state
};
}
const mod1 = createModule();
const mod2 = createModule();
mod1.increment();
命名空間增強(qiáng)
在閉包中構(gòu)建模塊層級(jí),防止全局對(duì)象膨脹:
(function(namespace) {
let config = { env: 'prod' }; // 私有配置
namespace.getConfig = () => config;
})(window.APP = window.APP || {});
console.log(APP.getConfig().env);
高階函數(shù)與函數(shù)工廠
生成可定制化函數(shù)(如參數(shù)化函數(shù)模板)或?qū)崿F(xiàn)裝飾器模式。
示例:函數(shù)返回自增閉包,實(shí)現(xiàn)狀態(tài)持久化。
基礎(chǔ)函數(shù)工廠案例
通過閉包隔離計(jì)數(shù)器狀態(tài),生成多個(gè)獨(dú)立實(shí)例:
function createCounter() {
let count = 0; // 閉包保存私有狀態(tài)
return function() { // 高階函數(shù)返回新函數(shù)
return ++count;
};
}
const counterA = createCounter();
const counterB = createCounter();
console.log(counterA());
console.log(counterB());
帶配置參數(shù)的增強(qiáng)型工廠
利用高階函數(shù)傳遞初始化參數(shù),動(dòng)態(tài)生成不同行為的函數(shù):
function createMultiplier(factor) {
return function(num) {
return num * factor; // 閉包保留factor參數(shù)
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
模塊化開發(fā)中的應(yīng)用
結(jié)合IIFE(立即執(zhí)行函數(shù))實(shí)現(xiàn)模塊作用域隔離:
const dataService = (function() {
let cache = {}; // 私有變量
return {
fetch: (key) => cache[key] || loadData(key), // 高階方法
clear: () => cache = {} // 暴露接口
};
})();
dataService;
回調(diào)函數(shù)與異步編程
在異步操作(如 setTimeout
、AJAX)中保留上下文變量,確?;卣{(diào)函數(shù)執(zhí)行時(shí)仍能訪問所需數(shù)據(jù)。
示例:事件處理函數(shù)中通過閉包捕獲 DOM 元素或事件參數(shù)。
function handleClick(element) {
element.addEventListener('click', function() {
setTimeout(() => {
console.log(element.id); // 閉包保留觸發(fā)事件的DOM對(duì)象
}, 1000);
});
}
function createHandler(element, config) {
return function(event) {
element.style.color = config.color; // 閉包保留元素和配置參數(shù)
console.log('觸發(fā)事件:', event.target);
};
}
const btnHandler = createHandler(document.getElementById('btn'), {color: 'red'});
變量狀態(tài)持久化:閉包通過保留外部函數(shù)作用域的變量,確保回調(diào)函數(shù)執(zhí)行時(shí)能訪問到正確的變量值,避免異步操作中因變量被覆蓋導(dǎo)致的邏輯錯(cuò)誤。
封裝私有狀態(tài):閉包允許在異步操作中隱藏內(nèi)部數(shù)據(jù),僅通過回調(diào)接口暴露必要功能。例如封裝網(wǎng)絡(luò)請(qǐng)求的緩存機(jī)制。
function createFetchService() {
let cache = {}; // 私有變量
return function(url, callback) {
if (cache[url]) return callback(cache[url]);
fetch(url).then(res => {
cache[url] = res;
callback(res);
});
};
}
const fetchWithCache = createFetchService();
事件處理:通過閉包保存事件觸發(fā)時(shí)的上下文信息,避免全局變量污染:
document.querySelectorAll('.btn').forEach(btn => {
btn.addEventListener('click', function() {
const currentBtn = this; // 閉包保留當(dāng)前按鈕對(duì)象
setTimeout(() => console.log(currentBtn.id), 1000);
});
});
異步流程控制:結(jié)合閉包與回調(diào)實(shí)現(xiàn)順序執(zhí)行異步操作。
function asyncSequence(tasks, finalCallback) {
let index = 0;
function next() {
if (index < tasks.length) {
tasks[index++](next); // 閉包保存當(dāng)前執(zhí)行進(jìn)度
} else {
finalCallback();
}
}
next();
}
// 依次執(zhí)行任務(wù)隊(duì)列
模塊化開發(fā)
將功能模塊的變量和方法封裝在閉包中,通過返回接口對(duì)象提供外部訪問權(quán)限,減少全局命名沖突。
模塊化工具庫(kù)開發(fā):如日期處理、數(shù)據(jù)校驗(yàn)等工具庫(kù),通過閉包隔離工具方法,僅暴露必要接口。
延遲執(zhí)行與狀態(tài)保留
閉包可保存臨時(shí)狀態(tài)(如循環(huán)中的索引值),解決異步操作因變量提升導(dǎo)致的邏輯錯(cuò)誤。
循環(huán)內(nèi)使用閉包綁定 i
的值,避免異步回調(diào)輸出重復(fù)結(jié)果。
數(shù)據(jù)緩存與性能優(yōu)化
緩存計(jì)算結(jié)果(如斐波那契數(shù)列),避免重復(fù)計(jì)算,提升執(zhí)行效率。
function memoize(fn) {
const cache = new Map(); // 創(chuàng)建緩存容器
return function(...args) {
const key = JSON.stringify(args); // 序列化參數(shù)作為緩存鍵
if (cache.has(key)) return cache.get(key); // 緩存命中
const result = fn(...args); // 原始函數(shù)調(diào)用
cache.set(key, result); // 緩存計(jì)算結(jié)果
return result;
};
}
// 應(yīng)用示例 斐波那契示例解析
const memoizedFib = memoize(function(n) {
return n <= 1 ? n : memoizedFib(n - 1) + memoizedFib(n - 2);
});
console.log(memoizedFib(50)); // 快速計(jì)算結(jié)果
緩存機(jī)制:使用 Map
存儲(chǔ)計(jì)算結(jié)果,JSON.stringify(args)
將參數(shù)序列化為唯一緩存鍵。
閉包應(yīng)用:返回的函數(shù)保持對(duì) cache
的引用,形成閉包。
性能優(yōu)化:避免重復(fù)計(jì)算,空間換時(shí)間的典型場(chǎng)景。
遞歸優(yōu)化:普通遞歸斐波那契時(shí)間復(fù)雜度是 O(2?),記憶化后降為 O(n)。
緩存生效關(guān)鍵:必須通過 memoizedFib
進(jìn)行遞歸調(diào)用才能觸發(fā)緩存機(jī)制。
計(jì)算規(guī)模:memoizedFib(50)
在普通遞歸下不可行(需要約 1.6e11 次運(yùn)算),記憶化后只需 50 次計(jì)算。
注意事項(xiàng):
參數(shù)序列化限制:當(dāng)參數(shù)包含循環(huán)引用對(duì)象時(shí),
JSON.stringify
會(huì)失敗。引用類型參數(shù):對(duì)象參數(shù)即使內(nèi)容相同但引用不同,會(huì)被視為不同鍵值。
副作用函數(shù):不適用于有副作用的函數(shù)(如修改外部變量),因?yàn)榫彺鏁?huì)跳過實(shí)際執(zhí)行。
函數(shù)柯里化與裝飾器:拆分多參數(shù)函數(shù)為鏈?zhǔn)秸{(diào)用,或通過裝飾器擴(kuò)展函數(shù)功能(如日志記錄、權(quán)限校驗(yàn))。
函數(shù)柯里化通過閉包保存已傳遞參數(shù),逐步接收剩余參數(shù),最終完成計(jì)算。
// 通用柯里化函數(shù)實(shí)現(xiàn)
function curry(fn) {
return function curried(...args) {
// 判斷當(dāng)前參數(shù)數(shù)量是否滿足原始函數(shù)要求
if (args.length >= fn.length) {
return fn(...args); // 參數(shù)足夠時(shí)執(zhí)行原始函數(shù)
} else {
return (...newArgs) => curried(...args, ...newArgs); // 閉包保存已傳參數(shù)
}
};
}
// 示例:三參數(shù)加法函數(shù)柯里化
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6(鏈?zhǔn)秸{(diào)用)
// 參數(shù)分步傳遞
const add5 = curriedAdd(2)(3);
console.log(add5(5)); // 10(2+3+5)
// 組合函數(shù)
const doubleAdd = curriedAdd(1)(1);
[1,2,3].map(doubleAdd); // [3,4,5]
參數(shù)累積:通過閉包保存每次調(diào)用的參數(shù)。
遞歸返回:每次參數(shù)不足時(shí)返回新的函數(shù)。
長(zhǎng)度判斷:
fn.length
獲取原始函數(shù)形參個(gè)數(shù)。函數(shù)組合:通過連續(xù)返回函數(shù)實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。
示例執(zhí)行流程
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
// 執(zhí)行過程分解:
1. curriedAdd(1) → 參數(shù)長(zhǎng)度 1 < 3 → 返回新函數(shù)保存 [1]
2. (2) → 合并參數(shù) [1,2] → 長(zhǎng)度 2 < 3 → 返回新函數(shù)
3. (3) → 合并參數(shù) [1,2,3] → 觸發(fā)執(zhí)行 add(1,2,3)
閉包應(yīng)用:通過嵌套函數(shù)保留參數(shù)狀態(tài)。
函數(shù)式編程:實(shí)現(xiàn)函數(shù)的延遲執(zhí)行和參數(shù)分步傳遞。
自動(dòng)參數(shù)收集:使用剩余參數(shù)語法(…)簡(jiǎn)化參數(shù)處理。
權(quán)限校驗(yàn)裝飾器
function withAuth(fn) {
return function(...args) {
const user = getCurrentUser(); // 模擬獲取用戶信息
if (!user?.isAdmin) throw new Error("無權(quán)限操作"); // 權(quán)限校驗(yàn)邏輯
return fn(...args); // 校驗(yàn)通過后執(zhí)行原函數(shù)
};
}
// 應(yīng)用示例
const deleteUser = withAuth(function(userId) {
/* 刪除用戶邏輯 */
});
deleteUser(123); // 非管理員觸發(fā)異常
功能解耦:核心邏輯與輔助功能(日志、權(quán)限)分離,提升代碼可維護(hù)性。
復(fù)用性:同一裝飾器可應(yīng)用于多個(gè)函數(shù)(如全局日志記錄)。
閉包高效使用
泄漏核心原因
長(zhǎng)期持有外部變量引用:閉包會(huì)保留其外層函數(shù)的作用域鏈,若閉包本身長(zhǎng)期存活(如被全局變量引用),其引用的外部變量無法被垃圾回收。
循環(huán)引用:閉包與外部變量形成相互引用(如閉包引用 DOM 元素,DOM 元素又通過事件監(jiān)聽器引用閉包),導(dǎo)致雙方均無法回收。
未及時(shí)釋放資源:未主動(dòng)解除閉包對(duì)大型對(duì)象(如數(shù)組、緩存數(shù)據(jù))的引用,導(dǎo)致內(nèi)存長(zhǎng)期占用。
高效內(nèi)存使用策略
及時(shí)釋放外部變量:閉包使用完畢后,將不再需要的外部變量顯式設(shè)為 null
,解除強(qiáng)引用。
function createClosure() {
let largeData = new Array(1e6).fill('data'); // 改用 let 聲明
return {
useData: function() { // 使用數(shù)據(jù)
const result = largeData.length; // 使用后立即釋放
largeData = null; // ? 解除引用
return result;
},
cleanup: function() { // 可選清理方法
largeData = null;
}
};
}
// 在使用完數(shù)據(jù)后立即置為 null,確保:
const closure = createClosure();
closure.useData(); // 使用數(shù)據(jù)并自動(dòng)釋放
// 此時(shí) largeData 已被標(biāo)記為可回收
sequenceDiagram
participant User
participant Closure
participant GC
User->>Closure: createClosure()
Closure->>Heap: 分配 1MB 內(nèi)存
User->>Closure: useData()
Closure->>Heap: 讀取數(shù)據(jù)
Closure->>Heap: 置 null 解除引用
GC->>Heap: 檢測(cè)到無引用,回收內(nèi)存
?減少閉包嵌套層級(jí)
優(yōu)先使用局部變量緩存外層變量,減少作用域鏈遍歷次數(shù)?
function outer() {
const data = computeData();
return function () {
const cachedData = data; // 緩存到局部變量
// 使用 cachedData 操作
};
}
及時(shí)釋放無用閉包
手動(dòng)解除閉包對(duì)外部變量的引用(如置空變量、移除事件監(jiān)聽)?
生命周期的控制
閉包生命周期的觸發(fā)與終止條件?
創(chuàng)建時(shí)機(jī)?:當(dāng)外部函數(shù)被調(diào)用,且內(nèi)部函數(shù)引用外部作用域的變量時(shí),閉包立即生成?。
終止條件?:閉包會(huì)持續(xù)存在,直到所有引用內(nèi)部函數(shù)的變量被解除(如置為 null
或超出作用域),此時(shí)閉包及其關(guān)聯(lián)的變量會(huì)被垃圾回收(GC)銷毀?
主動(dòng)控制
將持有閉包的變量手動(dòng)設(shè)置為 null
,強(qiáng)制解除對(duì)外部變量的強(qiáng)引用,觸發(fā) GC 回收?
function createClosure() {
let data = "敏感數(shù)據(jù)";
return function () { console.log(data); };
}
const closure = createClosure();
closure(); // 正常使用
closure = null; // 解除引用,GC 可回收閉包和 data?:ml-citation{ref="1,3" data="citationList"}
事件監(jiān)聽器的顯式移除
若閉包用于 DOM 事件回調(diào),需通過 removeEventListener
移除監(jiān)聽,避免閉包長(zhǎng)期持有 DOM 元素導(dǎo)致內(nèi)存泄漏?
function setupListener() {
const btn = document.getElementById("btn");
const handler = () => { /* 操作閉包變量 */ };
btn.addEventListener("click", handler);
// 移除監(jiān)聽時(shí)解除閉包引用
return () => btn.removeEventListener("click", handler);
}
const remove = setupListener();
remove(); // 觸發(fā)閉包銷毀?:ml-citation{ref="2,3" data="citationList"}
模塊化設(shè)計(jì)中的單例管理
通過閉包封裝模塊私有變量,僅在必要時(shí)暴露接口,并在模塊卸載時(shí)主動(dòng)釋放資源?
const CounterModule = (function () {
let count = 0;
return {
increment: () => count++,
reset: () => { count = 0; },
destroy: () => { count = null; } // 主動(dòng)釋放閉包變量?:ml-citation{ref="3,6" data="citationList"}
};
})();