一、先搞懂 JS 代码的 “两步走” 逻辑
JavaScript 代码运行时就像一场 “先准备后表演” 的舞台剧:
- 准备阶段(编译期) :
- 扫描所有变量(
var
)和函数声明,把它们 “提前放到舞台上”(提升到作用域顶部),但var
变量先空着(初始值是undefined
),函数则完整放上去。 - 举个栗子:
sayHi() // 能直接调用,因为函数被提前“放上台”了
var name = "小明" // 变量声明被提前,但赋值还在原地
function sayHi() { console.log("Hi, " + name) }
- 等价于:
var name; // 先声明变量
function sayHi() { ... } // 先声明函数
sayHi() // 此时name是undefined,输出“Hi, undefined”
name = "小明" // 最后赋值
- 表演阶段(执行期) :
按顺序执行赋值、运算等操作,变量才真正有值。
二、var 的 “混乱时代”:到处乱窜的变量
1. 作用域像 “大通铺”:只认函数,不认块
var
声明的变量要么在整个函数里都能访问(函数作用域),要么在整个程序里都能访问(全局作用域),但不认if
/for
等花括号{}
的块级作用域。- 例子
if (true) {
var age = 18 // 在函数里声明,块外也能访问
}
console.log(age) // 输出18,块级作用域“拦不住”var
2. 变量能 “提前见面”,但值是 “空的”
var
变量会被 “提前声明”,但赋值还在原地,所以声明前访问会得到undefined
(不会报错)。- 例子:
console.log(num) // 输出undefined(提前声明了,但没赋值)
var num = 100
3. 允许 “重名” 变量:后面的覆盖前面的
- 同一个作用域里可以用
var
多次声明同一个变量,后面的声明会覆盖前面的。
var car = "自行车"
var car = "汽车" // 允许重复声明,值变成“汽车”
三、let 的 “规矩时代”:块级作用域 + 严格限制
1. 作用域像 “单人房间”:只认块级花括号{}
-
let
声明的变量只能在声明它的花括号{}
里访问(块级作用域),出了这个块就 “消失” 了。 -
例子:
for (let i = 0; i < 3; i++) {
// i只在for循环的花括号里有效
}
console.log(i) // 报错!i已经“消失”了
2. 禁止 “提前见面”:声明前访问会 “挨打”
let
变量不会被提前声明,在声明之前的区域(暂时性死区)访问会直接报错(ReferenceError
)。
console.log(book) // 报错!不能在声明前访问
let book = "JavaScript入门"
3. 禁止 “重名”:同一个作用域里只能声明一次
-
同一个作用域里用
let
重复声明同一个变量,代码直接报错(编译阶段就失败)。 -
例子:
let cat = "小白"
let cat = "小黑" // 报错!“cat”已经声明过了
四、var vs let 对比表:一看就懂
特性 | var(混乱时代) | let(规矩时代) |
---|---|---|
作用域范围 | 函数或全局(不认{} 块级作用域) | 仅限声明的{} 块级作用域 |
提前访问 | 可以访问,值是undefined | 不能访问,会报错(暂时性死区) |
重复声明 | 允许(后面的覆盖前面的) | 禁止(编译阶段报错) |
全局变量影响 | 全局声明会变成全局对象的属性 | 不会成为全局对象属性 |
适合场景 | 老旧代码兼容,极少数特殊场景 | 现代开发,推荐优先使用 |
五、新手如何避免踩坑?记住这 3 条
-
能用
let
/const
就不用var
:let
适合值会变的变量,const
适合值不变的常量(比如配置项)。
-
变量声明尽量靠近使用的地方:
- 别在作用域顶部声明一堆暂时用不到的变量,代码会更清晰。
-
用
{}
块级作用域 “圈住” 变量:- 比如在
if
/for
里用let
声明变量,避免变量 “乱跑” 污染其他地方。
- 比如在
六、为什么 JS 要设计两种变量声明?
-
var
是历史遗留产物:早期 JS 没有块级作用域,var
的设计是为了兼容老代码。 -
let
是 ES6 的新规范:让 JS 更严谨,减少变量作用域混乱导致的 bug,更符合现代编程习惯。
总结:var
像 “放养的野马”,容易失控;let
像 “圈养的绵羊”,规矩但安全。新手入门直接用let
和const
,基本能解决 90% 的作用域问题