博客 / 詳情

返回

JS 裏的 “變量租房記”:閉包是咋把變量 “扣” 下來的?

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

前言

你有沒有過這種疑惑:

明明在 “出租屋”(函數)裏放的 “行李”(變量),房東都退房了,這行李咋還能拿出來用?或者循環裏的變量總 “串房間”,明明住 1 號房,結果跑到 6 號房去了?在函數裏定義的變量,出了函數居然還能用?

這背後其實藏着作用域、作用域鏈的 “小九九”,而最終的 “幕後BOSS” 就是 ——閉包。今天咱們就用 “租房” ,把作用域、作用域鏈、閉包這仨 JS 裏的 “租房規則” 講明白,讓你迅速成為閉包大師

一、先搞懂:變量的 “租房地盤” 叫作用域

變量不是隨便 “住” 的,它得有自己的 “房子”—— 這就是作用域。簡單説:變量能待的地方,就是它的作用域

JS 裏的 “房子” 分 3 種:

1. 全局作用域:“大街上的共享行李,誰都能碰”

你把行李放 “大街上”(函數 / 大括號外面),那整個小區(整個文件)的人都能拿。

比如:

var good = "張三的快遞"; // 放大街上(全局作用域)
function getGood() {
    console.log(good);   // 誰都能拿,輸出“張三的快遞”
}
getGood();

ScreenShot_2026-01-27_103211_061

2. 函數作用域:“自家卧室的行李,外人別進”

你把行李鎖在 “卧室”(函數內部)裏,那只有卧室裏的人能拿,出了門就碰不到了。

比如:

var wallet = "全局錢包(100塊)"; 
function myHome() {
    var wallet = "卧室錢包(2000塊)"; // 卧室裏的錢包,只在卧室有效
    console.log(wallet); // 用的是卧室裏的,輸出2000
}
myHome();
console.log(wallet); // 只能拿到大街上的,輸出100

ScreenShot_2026-01-27_103224_386

劃重點:這房子是 “單向門”—— 只能從卧室(內部)往外拿大街上的東西,大街上的人拿不到卧室裏的。

3. 塊級作用域:“用let給行李劃個小格子”

var是 “粗心租客”:哪怕把行李放 “衣櫃”(if/for{}裏),也會不小心 “掉” 到卧室裏;但let/const是 “細心租客”,把行李鎖在衣櫃格子裏,只有格子裏能拿。

上代碼:

if (true) {
    let money = 500; // 鎖在衣櫃格子(塊級作用域)裏
}
console.log(money); // 報錯!外面打不開這格子

ScreenShot_2026-01-27_103243_401

結果也是顯示了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();

結果跟我們想的一樣:

ScreenShot_2026-01-27_103256_908

 大致分析如下:

ScreenShot_2026-01-27_103307_181

關鍵規律:你租房子的時候(函數定義時),就會記下來 “房東是誰”(用outer指針)—— 不管你後來搬到哪(函數在哪調用),找東西都得按 “最初的房東” 來!

三、閉包:“租客退租了,行李卻被保潔扣下了”

先記兩個租房規矩:

  1. 租客退租(函數執行完),卧室裏的東西(執行上下文)會被物業清走(內存銷燬);
  2. 保潔(內部函數)能進租客的卧室拿東西(作用域規則)。

那閉包是咋回事?

別急,先看個例子:

function leave() {
  var left = "租客的筆記本電腦"; // 卧室裏的行李
  function cleaner() { // 保潔是卧室裏的“內部員工”
    console.log(left); // 保潔能拿這行李
  }
  return cleaner; // 租客把保潔“帶走了”
}
var cleaner1 = leave(); // 租客退租了,卧室該被清了
cleaner1(); // 但保潔居然拿出了“筆記本電腦”!
最喜歡的輸出結果環節:

ScreenShot_2026-01-27_103318_696

這就離譜了:租客都走了,行李咋還在?

答案:因為保潔(內部函數)還 “惦記着” 這行李,物業(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]();
}

ScreenShot_2026-01-27_103328_397

四、閉包:是 “儲物神器” 也是 “占房坑”

優點:

  • 藏私房錢:想把行李鎖起來,只有自己能拿?用閉包!
  • 封裝 “專屬房間” :早期 JS 沒 “獨立公寓”(模塊),全靠閉包封出專屬空間。

缺點:

  • 佔着房間不撒手(內存泄露) :閉包扣着行李,物業就沒法清房間 —— 要是不用的閉包不扔,房間會越佔越多。解決辦法:不用保潔的時候,把保潔辭了(比如離職保潔 = null),物業就會把儲物箱清掉。

總結:一句話串起所有 “租房規則”

作用域是變量的 “房間”,作用域鏈是 “找不到東西喊房東的順序”,閉包是 “租客退租後,保潔扣下行李的儲物箱”—— 這仨就是 JS 裏 “變量住哪、咋找、咋留” 的全部邏輯!

恭喜你成為閉包大師

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文檔,大家一起討論學習,一起進步。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.