核心定義

閉包(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)過程

  1. 函數(shù)嵌套與變量引用觸發(fā)閉包捕獲。

  2. 作用域鏈保留確保外部變量持久化。

  3. 內(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è) ivar 無塊級(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]
  1. 參數(shù)累積:通過閉包保存每次調(diào)用的參數(shù)。

  2. 遞歸返回:每次參數(shù)不足時(shí)返回新的函數(shù)。

  3. 長(zhǎng)度判斷fn.length 獲取原始函數(shù)形參個(gè)數(shù)。

  4. 函數(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"}  
  };  
})();