引言
你有没有过这样的经历?
- 写代码时变量找不到,不知道为什么;
- 函数调用顺序乱七八糟,自己都搞不清谁先谁后;
- 看到"闭包"两个字就头大,感觉根本不明白。
别担心,这些看似高深的概念,其实都能用我们生活中常见的场景来类比。今天我们就用"日常逻辑"来聊聊 JS 是怎么运行你的代码的。
🧑💼 调用栈:JS 的"任务清单"
想象一下你是个上班族,每天早上打开电脑的第一件事就是列个待办事项清单:
- 回复邮件 ✅
- 开会 ⏳
- 写报告 📝
这个清单就是你的 调用栈(Call Stack) ,它是 JS 用来管理函数调用顺序的一个"任务清单"。
比如:
lua
function 做早餐() {
console.log("煎蛋");
}
function 上班() {
console.log("开会");
做早餐();
}
上班();
JS 的调用栈是这样工作的:
- 先执行
上班()
→ 把它放进任务清单; - 发现里面要执行
做早餐()
→ 把它压进去; 做早餐()
完成后弹出;- 最后
上班()
完成也弹出。
就像你先开始上班流程,中途临时去煎了个蛋,吃完再回来继续上班。
🏡 作用域:你家的门禁
你可以把每个函数看作是一个房间,房间里有私人物品(变量),不是谁都能随便拿。
javascript
function 房间A() {
let 杯子 = "在床头";
}
function 房间B() {
console.log(杯子); // ❌ 找不到!
}
这就是 作用域(Scope) :决定一个变量在哪里能被访问。
作用域链:找东西从近到远
如果你在一个房间里找遥控器,你会怎么做?
- 先在自己房间找;
- 没找到,去室友房间看看;
- 还没有,就去客厅(全局空间)翻一翻。
JS 查找变量也是这样:
javascript
let 零花钱 = 50;
function 外层() {
let 存款 = 10000;
function 内层() {
let 收入 = 8000;
console.log(零花钱); // 找到了!去外层找
}
内层();
}
外层();
这个查找路径就叫做 作用域链(Scope Chain) 。
🧱 词法作用域:你出生在哪,决定了你能看到什么
这个词听起来很专业,但其实很好理解:
你在哪写的函数,它能看到哪些变量,就已经定下来了。
举个例子:
javascript
let 地点 = "兴国";
function 打印地点() {
console.log(地点);
}
function 外层() {
let 地点 = "抚州";
打印地点(); // 输出的是兴国,不是抚州!
}
虽然 打印地点()
是在"外层"里被调用的,但它是在全局作用域中被定义的,所以它看到的还是"兴国"。
就像你出生在兴国,不管以后搬到哪生活,身份证上的籍贯是不会变的。
🎁 闭包:记住你曾经拥有的一切
终于说到"闭包"了。这可是很多人觉得最难懂的部分,但我们用隔壁老张的故事来解释它。
故事:
你一直记得一个女孩,你们曾经在一起三年,很相爱,一起经历过很多事,开心,难过,距离,矛盾。可是后来你们分开了,再也没有续集了,但你一直记得那个女孩。 直到有一天你去到你们常去的酒馆,你恍惚了,想知道她还在吗,可是她已经不在了,可是只要你还记得她,她就一直在你心里,这个她,就是闭包里的变量。
看代码:
javascript
function 创建女孩() {
let 回忆 = ["散步", "喝酒"];
return function () {
console.log(回忆);
};
}
let 记忆 = 创建女孩();
记忆(); // 输出: ["散步", "喝酒"]
即使 创建女孩()
已经执行完了,里面的变量 回忆
并没有被销毁,因为还有一个函数(闭包)还记着它。
💡 闭包的本质:函数 + 它能记住的词法作用域
🔚 总结一句话:
JavaScript 的执行机制,就像你的生活:
- 调用栈 是你的待办清单;
- 作用域 是你家的门禁系统;
- 作用域链 是你找东西的路线图;
- 词法作用域 决定了你"出生地"的归属感;
- 闭包 就是隔壁老张永远记得的那个她。
理解了这些,你就不再只是写代码的人,而是真正懂得代码为什么会这样运行的开发者啦!