JS 里的 “变量租房记”:闭包是咋把变量 “扣” 下来的?

前言

你有没有过这种疑惑:

明明在 "出租屋"(函数)里放的 "行李"(变量),房东都退房了,这行李咋还能拿出来用?或者循环里的变量总 "串房间",明明住 1 号房,结果跑到 6 号房去了?在函数里定义的变量,出了函数居然还能用?

这背后其实藏着作用域、作用域链 的 "小九九",而最终的 "幕后BOSS" 就是 ------闭包 。今天咱们就用 "租房" ,把作用域、作用域链、闭包 这仨 JS 里的 "租房规则" 讲明白,让你迅速成为闭包大师

一、先搞懂:变量的 "租房地盘" 叫作用域

变量不是随便 "住" 的,它得有自己的 "房子"------ 这就是作用域 。简单说:变量能待的地方,就是它的作用域

JS 里的 "房子" 分 3 种:

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

你把行李放 "大街上"(函数 / 大括号外面),那整个小区(整个文件)的人都能拿。

比如:

javascript 复制代码
var good = "张三的快递"; // 放大街上(全局作用域)
function getGood() {
  console.log(good);   // 谁都能拿,输出"张三的快递"
}
getGood();

2. 函数作用域:"自家卧室的行李,外人别进"

你把行李锁在 "卧室"(函数内部)里,那只有卧室里的人能拿,出了门就碰不到了。

比如:

ini 复制代码
var wallet = "全局钱包(100块)"; 
function myHome() {
    var wallet = "卧室钱包(2000块)"; // 卧室里的钱包,只在卧室有效
    console.log(wallet); // 用的是卧室里的,输出2000
}
myHome();
console.log(wallet); // 只能拿到大街上的,输出100

划重点:这房子是 "单向门"------ 只能从卧室(内部)往外拿大街上的东西,大街上的人拿不到卧室里的。

3. 块级作用域:"用let给行李划个小格子"

var是 "粗心租客":哪怕把行李放 "衣柜"(if/for{}里),也会不小心 "掉" 到卧室里;但let/const是 "细心租客",把行李锁在衣柜格子里,只有格子里能拿。

上代码:

javascript 复制代码
if (true) {
  let money = 500; // 锁在衣柜格子(块级作用域)里
}
console.log(money); // 报错!外面打不开这格子

结果也是显示了money is not defined 。所以当形成块级作用域时,外层是访问不了内层的。要是换成var money = 500;,这钱就会 "掉" 到外面,能被拿到 ------ 这就是let的 "格子锁" 功能。

二、变量 "找不到?喊房东!"------ 作用域链

你在卧室里找行李,找不到咋办?喊 "房东"(外层作用域)啊!房东找不到,就喊 "大房东"(外外层作用域),一层一层往上喊,这串 "喊人的链条" 就是作用域链

看个例子:

ini 复制代码
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指针)------ 不管你后来搬到哪(函数在哪调用),找东西都得按 "最初的房东" 来!

三、闭包:"租客退租了,行李却被保洁扣下了"

先记两个租房规矩:

  1. 租客退租(函数执行完),卧室里的东西(执行上下文)会被物业清走(内存销毁);
  2. 保洁(内部函数)能进租客的卧室拿东西(作用域规则)。

那闭包是咋回事?

别急,先看个例子:

scss 复制代码
function leave() {
  var left = "租客的笔记本电脑"; // 卧室里的行李
  function cleaner() { // 保洁是卧室里的"内部员工"
    console.log(left); // 保洁能拿这行李
  }
  return cleaner; // 租客把保洁"带走了"
}
var cleaner1 = leave(); // 租客退租了,卧室该被清了
cleaner1(); // 但保洁居然拿出了"笔记本电脑"!

最喜欢的输出结果环节:

这就离谱了:租客都走了,行李咋还在?

答案 :因为保洁(内部函数)还 "惦记着" 这行李,物业(JS 引擎)就不会把卧室全清掉 ------ 而是留个 "小储物箱"(这就是闭包),专门装保洁需要的行李(这里就是遗留行李)。

大白话翻译闭包:

租客退租了,但保洁把他的行李扣在储物箱里,走到哪带到哪 ------ 这储物箱就是闭包。

还没懂的话我再举个例子:

假设你有一个不听话的儿子,他跟你断绝关系出去闯荡,幸运的是他出去之后你们的老房子就拆迁了,于是你拿着拆迁补贴买了一套新房子,但你于心不忍,你怕儿子走投无路回来找你,但是房子又拆迁了,这时候你就会在这个拆迁遗址立一个牌子 或者什么做标记 ,让你儿子知道你搬到哪里了,而这个牌子 就是闭包 。虽然老房子没了,但是这个牌子有它的作用----提醒你儿子新家的地址,你儿子如果回来会用得到这个作用。

虽然已经出栈,但依旧保留函数要用到的方法,这就是闭包的核心。

再看一个例子:为啥能输出 1-5?就是因为闭包把 "每个房间的行李" 都扣下来了:

ini 复制代码
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 里 "变量住哪、咋找、咋留" 的全部逻辑!

恭喜你成为闭包大师

相关推荐
Danny_FD34 分钟前
用 ECharts markLine 标注节假日
前端·echarts
程序员西西35 分钟前
SpringBoot无感刷新Token实战指南
java·开发语言·前端·后端·计算机·程序员
烛阴35 分钟前
Luban集成CocosCreator完整教程
前端·typescript·cocos creator
有点笨的蛋36 分钟前
深入理解 JavaScript 原型机制:构造函数、原型对象与原型链
前端·javascript
o***741737 分钟前
spring-boot-starter和spring-boot-starter-web的关联
前端
晴栀ay39 分钟前
JS中原型式面向对象的精髓
前端·javascript
3秒一个大39 分钟前
JavaScript 原型详解:从概念到实践
javascript
美幻40 分钟前
前端复制功能在移动端失效?一文彻底搞懂 Clipboard API 的兼容性陷阱
前端
llxxyy卢42 分钟前
XSS跨站之订单及shell箱子反杀记
前端·xss