"如果连变量都不懂,那你永远也不知道 JavaScript 在发什么疯。"
------ 来自一位在 TDZ 中走丢的程序员
今天是小Dora正式踏入 JavaScript 的第一天,目标明确:未来成为一名高级前端工程师。第一关,就是打通【变量声明】这座看似简单、实则杀机重重的迷宫。
🧭 一、变量三兄弟登场:var
、let
、const
🥷 var
------ 江湖老油条,喜欢走捷径
ini
console.log(name); // undefined
var name = "小Dora";
这不会报错,只会输出 undefined
,因为 var
有个江湖绝技叫:
变量提升(Hoisting) :变量声明会被"提前"到函数顶部,但赋值不会提升。
就像你抢了考场前排的位置,但忘了带笔,系统只看到你在,但你写不出字。
ini
var name;
console.log(name); // undefined
name = "小Dora";
特点总结:
特性 | 说明 |
---|---|
✅ 提升 | 声明被提升,初始化不被提升 |
❌ 无块级作用域 | {} 对它没用,只认函数作用域 |
✅ 可重复声明 | 多次声明互相覆盖也不吭声 |
🧑💼 let
------ 年轻新贵,条条框框一堆
ini
console.log(age); // ❌ ReferenceError
let age = 18;
为啥不是 undefined
?------因为你误闯了:
⚠️ 暂时性死区 TDZ(Temporary Dead Zone)
所谓 TDZ,就是变量虽然在词法上已经声明了,但在赋值之前你敢碰它,JS 就会直接朝你扔一个 ReferenceError。
javascript
{
// 这里进入 TDZ
console.log(tool); // ❌ ReferenceError
let tool = "Vue.js";
// TDZ 结束
}
let
特性总结:
特性 | 说明 |
---|---|
❌ 无提升 | 存在 TDZ,不允许提前访问 |
✅ 块级作用域 | 出了 {} 就再见了 |
❌ 不可重复声明 | 同一作用域声明两次就爆炸 |
🛡️ const
------ 永不叛变的"铁憨憨"
ini
const mission = "成为高级前端";
mission = "成为产品经理"; // ❌ TypeError
你不能改变它指向的值,但是:
ini
const config = { debug: true };
config.debug = false; // ✅ 可以改属性
也就是说:你不能换人,但可以给人换衣服。
🧠 二、作用域与生命周期:变量的隐形牢笼
什么是作用域?
作用域 = 变量的"活动范围"
JS 中有三种作用域:
类型 | 示例 | 说明 |
---|---|---|
全局作用域 | 文件最外层定义 | 到处都能访问 |
函数作用域 | var 定义位置 |
只在函数内可访问 |
块级作用域 | let/const 定义 |
花括号 {} 控制访问边界 |
🧬 生命周期三阶段
每个变量的"人生"都有三个阶段:
阶段 | 内容说明 |
---|---|
创建阶段 | 引擎发现变量,分配内存 |
初始化阶段 | var 设为 undefined ,let/const 进入 TDZ |
执行阶段 | 正式赋值、参与代码运行 |
例子:
ini
function demo() {
console.log(a); // undefined
console.log(b); // ❌ ReferenceError
var a = 1;
let b = 2;
}
💻 三、实战演练:作用域对比与 TDZ 演示
1. var
穿墙术
ini
{
var foo = "JS Ninja";
}
console.log(foo); // ✅ JS Ninja
因为 var
无视 {}
,只有函数能挡住它。
2. let/const
的结界
ini
{
let secret = "封印之术";
}
console.log(secret); // ❌ ReferenceError
let
/ const
在 {}
外全都"隐身"。
🧪 四、自测问答 · 你能拿满分吗?
✅ 快问快答题(选择题)
1. 以下哪个变量声明不会触发 ReferenceError?
A. let
声明前访问
B. var
声明前访问
C. const
声明前访问
D. 访问未定义变量 x
✅ 答案:B
2. 以下哪种行为不会引发报错?
A. let a = 1; let a = 2;
B. const x = 1; x = 2;
C. const obj = {}; obj.name = 'Dora';
D. let b; console.log(b);
✅ 答案:C
🧠 思维题(简答)
Q1:TDZ 的设计目的是什么?为什么不直接返回 undefined
?
🎯 答案参考:TDZ 是为了防止访问未初始化的变量。若访问未初始化变量返回 undefined
,容易掩盖错误逻辑;TDZ 让错误提早暴露,增强代码健壮性。
Q2:为什么 let
在异步循环中能正确输出索引?
javascript
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// 输出:0 1 2
🎯 答案参考:let
在每次循环中创建了独立作用域的 i ,每个 i
都是新的绑定,闭包捕获的是正确的值。
👨💻 五、大厂爱问面试题(挑战加深)
💣 面试题 1:以下代码输出什么?
ini
function foo() {
console.log(a); // ?
console.log(b); // ?
var a = 10;
let b = 20;
}
foo();
✅ 输出:
javascript
undefined
ReferenceError
💣 面试题 2:
ini
{
var a = 1;
let b = 2;
}
console.log(a); // ?
console.log(b); // ?
✅ 输出:
javascript
1
ReferenceError
💣 面试题 3:
ini
const person;
person = { name: "小Dora" };
✅ 输出:SyntaxError: Missing initializer in const declaration
✅ 六、小Dora Day 1 修炼总结
特性对比 | var |
let |
const |
---|---|---|---|
是否提升 | ✅ 只提升声明 | ❌(存在 TDZ) | ❌(存在 TDZ) |
作用域类型 | 函数作用域 | 块级作用域 | 块级作用域 |
可否重复声明 | ✅ 可重复 | ❌ 报错 | ❌ 报错 |
可否重新赋值 | ✅ 可以 | ✅ 可以 | ❌ 不可重新赋值 |
🔚 尾声:走出变量迷宫,踏入执行上下文
第一天结束了,小Dora已经理解:
- 变量三兄弟不仅语法不同,运行机制也不同
- TDZ、作用域和变量提升,是理解 JS 的起点
- 每一个变量都有生命周期,控制它就是掌控 JS
📌 下一站:词法作用域、执行上下文与闭包三连击 ------ 这是所有中高级面试绕不过去的必杀技。
如果你也在学习 JavaScript,欢迎继续追更 《小Dora 的 JS 修炼日记》 。
我们不只写代码,我们要理解代码"为什么这么写"。