🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
前言
你有沒有過這種疑惑:
明明在 “出租屋”(函數)裏放的 “行李”(變量),房東都退房了,這行李咋還能拿出來用?或者循環裏的變量總 “串房間”,明明住 1 號房,結果跑到 6 號房去了?在函數裏定義的變量,出了函數居然還能用?
這背後其實藏着作用域、作用域鏈的 “小九九”,而最終的 “幕後BOSS” 就是 ——閉包。今天咱們就用 “租房” ,把作用域、作用域鏈、閉包這仨 JS 裏的 “租房規則” 講明白,讓你迅速成為閉包大師。
一、先搞懂:變量的 “租房地盤” 叫作用域
變量不是隨便 “住” 的,它得有自己的 “房子”—— 這就是作用域。簡單説:變量能待的地方,就是它的作用域。
JS 裏的 “房子” 分 3 種:
1. 全局作用域:“大街上的共享行李,誰都能碰”
你把行李放 “大街上”(函數 / 大括號外面),那整個小區(整個文件)的人都能拿。
比如:
var good = "張三的快遞"; // 放大街上(全局作用域)
function getGood() {
console.log(good); // 誰都能拿,輸出“張三的快遞”
}
getGood();
2. 函數作用域:“自家卧室的行李,外人別進”
你把行李鎖在 “卧室”(函數內部)裏,那只有卧室裏的人能拿,出了門就碰不到了。
比如:
var wallet = "全局錢包(100塊)";
function myHome() {
var wallet = "卧室錢包(2000塊)"; // 卧室裏的錢包,只在卧室有效
console.log(wallet); // 用的是卧室裏的,輸出2000
}
myHome();
console.log(wallet); // 只能拿到大街上的,輸出100
劃重點:這房子是 “單向門”—— 只能從卧室(內部)往外拿大街上的東西,大街上的人拿不到卧室裏的。
3. 塊級作用域:“用let給行李劃個小格子”
var是 “粗心租客”:哪怕把行李放 “衣櫃”(if/for的{}裏),也會不小心 “掉” 到卧室裏;但let/const是 “細心租客”,把行李鎖在衣櫃格子裏,只有格子裏能拿。
上代碼:
if (true) {
let money = 500; // 鎖在衣櫃格子(塊級作用域)裏
}
console.log(money); // 報錯!外面打不開這格子
結果也是顯示了money is not defined。所以當形成塊級作用域時,外層是訪問不了內層的。要是換成var money = 500;,這錢就會 “掉” 到外面,能被拿到 —— 這就是let的 “格子鎖” 功能。
二、變量 “找不到?喊房東!”—— 作用域鏈
你在卧室裏找行李,找不到咋辦?喊 “房東”(外層作用域)啊!房東找不到,就喊 “大房東”(外外層作用域),一層一層往上喊,這串 “喊人的鏈條” 就是作用域鏈。
看個例子:
function myHome() {
var TV = "客廳的大電視";
let sofa = "客廳沙發";
{
let sofa = "卧室小沙發";
var eat = "客廳零食"; // var沒格子鎖,掉客廳裏了
let clothes = "卧室睡衣";
console.log(TV); // 卧室裏找不到,喊房東(客廳)→ 找到電視,輸出“大電視”
console.log(sofa); // 卧室裏有小沙發,直接拿→ 輸出“小沙發”
}
console.log(sofa); // 客廳裏的大沙發→ 輸出“客廳沙發”
console.log(eat); // 零食掉客廳了→ 輸出“客廳零食”
console.log(clothes); // 睡衣鎖在卧室格子裏→ 報錯!拿不到
}
myHome();
結果跟我們想的一樣:
大致分析如下:
關鍵規律:你租房子的時候(函數定義時),就會記下來 “房東是誰”(用outer指針)—— 不管你後來搬到哪(函數在哪調用),找東西都得按 “最初的房東” 來!
三、閉包:“租客退租了,行李卻被保潔扣下了”
先記兩個租房規矩:
- 租客退租(函數執行完),卧室裏的東西(執行上下文)會被物業清走(內存銷燬);
- 保潔(內部函數)能進租客的卧室拿東西(作用域規則)。
那閉包是咋回事?
別急,先看個例子:
function leave() {
var left = "租客的筆記本電腦"; // 卧室裏的行李
function cleaner() { // 保潔是卧室裏的“內部員工”
console.log(left); // 保潔能拿這行李
}
return cleaner; // 租客把保潔“帶走了”
}
var cleaner1 = leave(); // 租客退租了,卧室該被清了
cleaner1(); // 但保潔居然拿出了“筆記本電腦”!
這就離譜了:租客都走了,行李咋還在?
答案:因為保潔(內部函數)還 “惦記着” 這行李,物業(JS 引擎)就不會把卧室全清掉 —— 而是留個 “小儲物箱”(這就是閉包),專門裝保潔需要的行李(這裏就是遺留行李)。
大白話翻譯閉包:
租客退租了,但保潔把他的行李扣在儲物箱裏,走到哪帶到哪 —— 這儲物箱就是閉包。
還沒懂的話我再舉個例子:
假設你有一個不聽話的兒子,他跟你斷絕關係出去闖蕩,幸運的是他出去之後你們的老房子就拆遷了,於是你拿着拆遷補貼買了一套新房子,但你於心不忍,你怕兒子走投無路回來找你,但是房子又拆遷了,這時候你就會在這個拆遷遺址立一個牌子或者什麼做標記,讓你兒子知道你搬到哪裏了,而這個牌子就是閉包。雖然老房子沒了,但是這個牌子有它的作用----提醒你兒子新家的地址,你兒子如果回來會用得到這個作用。
雖然已經出棧,但依舊保留函數要用到的方法,這就是閉包的核心。
再看一個例子:為啥能輸出 1-5?就是因為閉包把 “每個房間的行李” 都扣下來了:
var arr = [];
for (var i = 1; i <= 5; i++) {
function foo(j) {
arr.push(function(){
console.log(j);
})
}
foo(i);
}
for (let n = 0; n < arr.length; n++) {
arr[n]();
}
四、閉包:是 “儲物神器” 也是 “占房坑”
優點:
- 藏私房錢:想把行李鎖起來,只有自己能拿?用閉包!
- 封裝 “專屬房間” :早期 JS 沒 “獨立公寓”(模塊),全靠閉包封出專屬空間。
缺點:
- 佔着房間不撒手(內存泄露) :閉包扣着行李,物業就沒法清房間 —— 要是不用的閉包不扔,房間會越佔越多。解決辦法:不用保潔的時候,把保潔辭了(比如
離職保潔 = null),物業就會把儲物箱清掉。
總結:一句話串起所有 “租房規則”
作用域是變量的 “房間”,作用域鏈是 “找不到東西喊房東的順序”,閉包是 “租客退租後,保潔扣下行李的儲物箱”—— 這仨就是 JS 裏 “變量住哪、咋找、咋留” 的全部邏輯!
恭喜你成為閉包大師!