JavaScript中閉包(Closure)舉例深度講解
1.什么是閉包:
閉包是JavaScript中最強大且獨特的特性之一,它是函數(shù)與其詞法環(huán)境的組合。閉包使得函數(shù)能夠訪問其外部作用域的變量,即使外部函數(shù)已經(jīng)執(zhí)行完畢。這種機制讓JavaScript具備了許多其他語言需要復雜語法才能實現(xiàn)的功能。
// 最簡單的閉包示例
function outer() {
const message = "Hello from outer!"; // 外部變量
function inner() {
console.log(message); // inner函數(shù)"記住"了message
}
return inner; // 返回inner函數(shù)
}
const myClosure = outer();
myClosure(); // "Hello from outer!"
2. 核心概念與前置知識:
2.1 執(zhí)行上下文 (Execution Context):
執(zhí)行上下文是 JavaScript 代碼執(zhí)行時的環(huán)境,每當代碼執(zhí)行時都會創(chuàng)建對應的執(zhí)行上下文。

執(zhí)行上下文的組成部分:
// 偽代碼:執(zhí)行上下文的內(nèi)部結構
ExecutionContext = {
// 1. 詞法環(huán)境 (用于let/const和函數(shù)聲明)
LexicalEnvironment: {
EnvironmentRecord: {}, // 環(huán)境記錄
outer: null // 外部環(huán)境引用
},
// 2. 變量環(huán)境 (用于var聲明)
VariableEnvironment: {
EnvironmentRecord: {},
outer: null
},
// 3. this綁定
ThisBinding: undefined
}
2.2 詞法環(huán)境 (Lexical Environment):
詞法環(huán)境是存儲標識符-變量映射的結構,由環(huán)境記錄和外部環(huán)境引用組成。

詞法環(huán)境的創(chuàng)建過程:
function createLexicalEnvironment() {
// 示例:詞法環(huán)境的創(chuàng)建
function outer(x) {
let a = 10;
const b = 20;
function inner(y) {
let c = 30;
console.log(a + b + c + x + y); // 訪問多個作用域的變量
}
return inner;
}
return outer;
}
// 執(zhí)行過程中的詞法環(huán)境變化
/*
1. 全局詞法環(huán)境:
{
EnvironmentRecord: {
createLexicalEnvironment: <function>,
outer: <function>
},
outer: null
}
2. outer函數(shù)的詞法環(huán)境:
{
EnvironmentRecord: {
x: 參數(shù)值,
a: 10,
b: 20,
inner: <function>
},
outer: <全局詞法環(huán)境的引用>
}
3. inner函數(shù)的詞法環(huán)境:
{
EnvironmentRecord: {
y: 參數(shù)值,
c: 30
},
outer: <outer函數(shù)詞法環(huán)境的引用>
}
*/
2.3 作用域鏈 (Scope Chain)
作用域鏈是通過詞法環(huán)境的外部引用形成的鏈條,用于標識符解析。

作用域鏈查找算法
// 作用域鏈查找示例
function demonstrateScopeChain() {
const globalVar = "全局變量";
function level1() {
const level1Var = "第一層變量";
function level2() {
const level2Var = "第二層變量";
function level3() {
const level3Var = "第三層變量";
// 變量查找順序演示
console.log(level3Var); // 1. 在當前環(huán)境找到
console.log(level2Var); // 2. 向上一層查找
console.log(level1Var); // 3. 繼續(xù)向上查找
console.log(globalVar); // 4. 查找到全局環(huán)境
// console.log(nonExistent); // 5. 找不到則報錯
}
return level3;
}
return level2;
}
return level1;
}
// 調(diào)用過程中的作用域鏈
const fn = demonstrateScopeChain()()();
fn(); // 執(zhí)行時會沿著作用域鏈查找變量
3. 代碼示例與分析:
3.1 經(jīng)典的閉包示例
function makeCounter() {
let count = 0; // 外部函數(shù)的局部變量
return function() { // 返回的內(nèi)部函數(shù)形成閉包
count++; // 訪問外部函數(shù)的變量
return count;
};
}
const counter = makeCounter(); // 調(diào)用外部函數(shù)
console.log(counter()); // 1 - 調(diào)用閉包函數(shù)
console.log(counter()); // 2 - count 變量被保持
console.log(counter()); // 3
3.2 逐行執(zhí)行過程分析

詳細步驟解析:
// 步驟 1-2: 全局執(zhí)行上下文創(chuàng)建,調(diào)用makeCounter
function makeCounter() {
// 步驟 3: 在makeCounter的詞法環(huán)境中創(chuàng)建count變量
let count = 0;
// 步驟 4: 創(chuàng)建匿名函數(shù),該函數(shù)的[[Environment]]屬性
// 指向makeCounter的詞法環(huán)境,形成閉包
return function() {
// 步驟 9: 通過作用域鏈找到外部的count變量
count++;
return count;
};
// 步驟 5-6: makeCounter執(zhí)行完畢,執(zhí)行上下文出棧
// 但count變量因為被閉包引用而不會被垃圾回收
}
// 步驟 7: counter變量保存了閉包函數(shù)的引用
const counter = makeCounter();
// 步驟 8-10: 每次調(diào)用counter()都會訪問保存的count變量
console.log(counter()); // 1
3.3 V8 引擎的優(yōu)化
V8 引擎對閉包進行了以下優(yōu)化:
- 變量提升優(yōu)化:只保留被閉包實際使用的外部變量
- 內(nèi)存管理:未被引用的外部變量會被垃圾回收
- 作用域分析:在編譯時分析變量使用情況
function optimizationExample() {
let used = "被閉包使用的變量";
let unused = "未被使用的變量"; // V8會優(yōu)化掉這個變量
let alsoUnused = "同樣未被使用";
return function() {
console.log(used); // 只有這個變量會被保留在閉包中
};
}
注意:在調(diào)試時,由于V8的優(yōu)化,某些未使用的變量可能在調(diào)試器中顯示為 “undefined”。
4. 經(jīng)典應用場景與最佳實踐:
4.1 模塊化封裝
const Calculator = (function() {
let result = 0; // 私有變量
return {
add: function(x) {
result += x;
return this;
},
multiply: function(x) {
result *= x;
return this;
},
getResult: function() {
return result;
},
reset: function() {
result = 0;
return this;
}
};
})();
// 使用
Calculator.add(5).multiply(2).getResult(); // 10
4.2 事件處理與回調(diào)
function createButtonHandler(name) {
return function(event) {
console.log(`按鈕 ${name} 被點擊了`);
// name變量被閉包保存
};
}
document.getElementById('btn1').onclick = createButtonHandler('按鈕1');
document.getElementById('btn2').onclick = createButtonHandler('按鈕2');
4.3 防抖和節(jié)流
// 防抖函數(shù)
function debounce(func, delay) {
let timeoutId; // 被閉包保存的變量
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// 節(jié)流函數(shù)
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func.apply(this, args);
}
};
}
5. 常見陷阱與解決方案:
5.1 循環(huán)中的閉包陷阱
問題代碼:
// 經(jīng)典錯誤示例
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 輸出三次 3
}, 100);
}
解決方案:
// 方案1:使用IIFE創(chuàng)建新的作用域
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 輸出 0, 1, 2
}, 100);
})(i);
}
// 方案2:使用let塊級作用域
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 輸出 0, 1, 2
}, 100);
}
// 方案3:使用bind
for (var i = 0; i < 3; i++) {
setTimeout(function(j) {
console.log(j); // 輸出 0, 1, 2
}.bind(null, i), 100);
}
5.2 內(nèi)存泄漏風險
// 可能導致內(nèi)存泄漏的代碼
function createHandler() {
const largeData = new Array(1000000).fill('data'); // 大量數(shù)據(jù)
return function() {
// 即使不使用largeData,它也會被閉包保留
console.log('handler called');
};
}
// 解決方案:顯式釋放不需要的引用
function createHandler() {
const largeData = new Array(1000000).fill('data');
const needed = largeData.slice(0, 10); // 只保留需要的部分
return function() {
console.log(needed.length);
// largeData會被垃圾回收
};
}
總結
到此這篇關于JavaScript中閉包(Closure)的文章就介紹到這了,更多相關JS閉包Closure內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JavaScript中Promise的執(zhí)行順序詳解
Promise 是 JS 中進行異步編程的新的解決方案(舊的是純回調(diào)形式) ,下面這篇文章主要給大家介紹了關于JavaScript中Promise執(zhí)行順序的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-01-01
JS實現(xiàn)模擬百度搜索“2012世界末日”網(wǎng)頁地震撕裂效果代碼
這篇文章主要介紹了JS實現(xiàn)模擬百度搜索“2012世界末日”網(wǎng)頁地震撕裂效果代碼,引入第三方插件實現(xiàn)頁面的抖動、撕裂及圖片等效果,需要的朋友可以參考下2015-10-10

