🚀 JavaScript执行江湖全揭秘:从调用栈到作用域链的奇幻漂流 🌊
🌍 第零章:代码执行前的神秘仪式——编译阶段
JavaScript虽被称为"解释型语言",但在执行前会经历闪电编译(通常<1ms)。这个阶段藏着变量提升的核心秘密:
// 你以为的执行顺序:
console.log(name); // 报错?
var name = "wql";
// 实际发生的编译魔法:
var name = undefined; // ← 变量提升!
console.log(name); // 输出undefined
name = "wql";
🔍 编译阶段三巨头:
- 变量登记处:扫描var声明,分配undefined
- 函数保险库:完整保存函数声明
- TDZ禁区:标记let/const声明(但不上锁)
📊 内存快照对比:
阶段 | var变量 | 函数 | let/const |
---|---|---|---|
编译阶段 | undefined | 完整函数 | 🚫不可访问 |
执行阶段 | 实际值 | 可调用 | 已初始化 |
🔍代码对比:
console.log(name); // 报错 输出ReferenceError
let name = "wql";
console.log(name); // 输出undefined
var name = "wql";
console.log(fun()) // 输出 fun 函数的返回值 :undefined 或者返回值类型的默认值
console.log(fun) // 输出 fun 函数本身 :[Function: fun],传递fu函数本身
function fu(){
return "wql"
}
🏰 第一章:时间旅行者的烦恼——变量提升的时空悖论
1.1 var的"半吊子提升"
function timeParadox() {
console.log(age); // undefined
if (false) {
var age = 18; // 这个声明依然会提升!
}
}
💡 奇特现象:即使if条件为false,var声明仍会提升!这是因为JS的函数级作用域特性。
1.2 函数的"VIP提升通道"
hero(); // "亚索!"
function hero() {
console.log("亚索!");
}
villain(); // TypeError: villain is not a function
var villain = function() {
console.log("劫!");
}
⚡ 关键差异:函数表达式不会提升!只有函数声明享受VIP待遇。
1.3 let/const的"安检流程"
{
console.log(weapon); // ReferenceError
let weapon = "无尽之刃";
}
🛑 TDZ规则:
- 进入块作用域时创建标识符
- 直到声明语句前都处于"暂时性死区"
- 任何访问尝试都会触发错误
🌐 第二章:作用域链的量子纠缠
2.1 作用域的三重宇宙
// 全局宇宙
const galaxy = "银河系";
function outer() {
// 恒星系
const star = "太阳";
function inner() {
// 行星世界
const planet = "地球";
console.log(galaxy + "-" + star + "-" + planet); // "银河系-太阳-地球"
}
return inner;
}
📡 作用域链查找流程:
inner作用域 → outer作用域 → 全局作用域 → null
2.2 闭包的"时空胶囊"
function createCounter() {
let count = 0;
return {
add: () => count++,
get: () => count
};
}
// 调用 createCounter 函数,创建一个计数器对象
const counter = createCounter();
// 调用计数器对象的 add 方法,将计数器的值加 1
counter.add();
// 调用计数器对象的 get 方法,获取当前计数器的值,并将其打印到控制台
console.log(counter.get()); // 1
🔗 闭包三要素:
- 外层函数返回内层函数
- 内层函数访问外层变量
- 外层变量被长期保持
🏗️ 第三章:执行上下文的建筑工地
🔗 执行上下文:
js 代码要执行 <-- 执行机制(调用栈)<-- 函数入栈(操作系统) <-- 执行上下文(代码和变量声明的关系)<-- 作用域 + 变量提升 + 可代码运行 <-- 运行阶段
3.1 全局执行上下文栈的叠盘子艺术
var name ='wzy'
function fu(){
}
let a = 10
📜 调用栈轨迹:
3.2 执行上下文栈的叠盘子艺术
var a = 1;
function fn(a){
var a = 2;
function a(){}
var b = a;
console.log(a);
}
fn(3); // 输出2
console.log(a); // 输出1
📜 调用栈轨迹:
🛠️ 第四章:现代JS编程的倚天剑与屠龙刀
4.1 块级作用域的最佳实践
//setTimeout的回调函数会在 for循环结束后才开始执行(需要的重点)
// 旧时代 es5的特性
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 10); // 输出3次3
}
// 新时代 es6的新功能
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 10); // 输出0,1,2
}
🔮 终章:成为JavaScript执行大师的十二道试炼
- 练习1:画出以下代码的作用域链
const globalVar = 1;
function outer() {
const outerVar = 2;
function inner() {
const innerVar = 3;
}
}
2. 练习2:预测输出顺序
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
3. 练习3:修复变量提升问题
for (var i = 0; i < 3; i++) {
button[i].onclick = function() {
console.log(i); // 总是输出3
}
}
🎉 致未来的JS宗师:
掌握调用栈就像获得时间宝石,理解作用域链如同解锁空间宝石。当你能在脑海中构建完整的执行上下文宇宙时,异步编程、性能优化、框架原理都将成为你的掌中之物。继续前行吧,JavaScript世界的无限可能正在等待你的探索!🚀不懂可以私信博主!或者留言!