JavaScript 深度解析var、let 和 const变量声明
在 JavaScript 的学习过程中,变量声明是最基础却也是最容易踩坑的知识点。尤其是当你从传统的 Java/C++ 等语言背景过渡到 JavaScript 时,var、let、const 的行为差异可能让你一头雾水。本文将结合示例和原理,带你深入理解 JavaScript 中变量的声明与作用域规则,并总结最佳实践。
1. 早期的 JavaScript 与 var
在 ES5(ECMAScript 5,即 2011 年标准)之前,JavaScript 只有 var 一种声明变量的方式。var 看似简单,但隐藏了很多坑。有些老师叫你直接弃用 var 转而使用 let ,但是并没有告诉你为什么,现在我来解释解释。
ini
var age = 18; // js 是弱类型语言,变量的类型由值决定
age++;
var PI = 3.1415926; // 变量名大写通常表示常量
console.log(age); // 19
注意:虽然我们用大写
PI表示"常量",但是var并不能阻止你修改它。
ini
PI = 3.14;
console.log(PI); // 3.14
这意味着,使用 var 时,变量既可以被重复声明,也可以被重新赋值,这在大型项目中很容易引起混乱。
1.1 var 的变量提升(Hoisting)
var 的最大特点之一就是变量提升 。即便你在声明之前使用变量,代码也不会报 ReferenceError,而是返回 undefined:
ini
console.log(age); // undefined
var age = 18;
这里的执行顺序可以理解为:
- 编译阶段:JavaScript 引擎把所有
var声明提前(提升)到函数或全局的最顶端,但不会赋值。 - 执行阶段:按原代码顺序执行,进行赋值。
这种机制虽然允许代码运行,但会降低代码可读性,容易导致 BUG。在现代开发中,建议尽量避免使用
var。
1.2 var 的作用域问题
var 是函数作用域 ,不支持块级作用域。这意味着它在大括号 {} 内部声明时,依然属于外层函数或全局作用域:
ini
{
var age = 18;
}
console.log(age); // 18,仍然能访问
在复杂逻辑中,这种行为会让变量名冲突和状态管理变得非常麻烦。
2. ES6 引入的 let 与 const
从 ES6(2015 年)开始,JavaScript 引入了 let 和 const,提供了块级作用域和常量声明,从根本上解决了 var 带来的诸多问题。
ini
let height = 188;
height++;
console.log(height); // 189
const key = 'abc123';
key = 'abc234'; // TypeError: Assignment to constant variable.
2.1 块级作用域
let 和 const 支持块级作用域,即变量只在 {} 内有效:
ini
{
let height = 188;
const PI = 3.141592;
}
console.log(height); // ReferenceError
console.log(PI); // ReferenceError
这与
var的行为截然不同,能有效避免变量污染全局的问题。
2.2 暂时性死区(Temporal Dead Zone, TDZ)
let 和 const 有一个重要特点:暂时性死区 。在变量声明之前访问,会报错 ReferenceError: 暂时性死区(Temporal Dead Zone, TDZ) 是指:
当你用
let或const声明一个变量时,从代码块开始 到变量声明的位置之间 的这段区域,变量处于一种"已声明但不可访问"的状态。如果在这段区域内访问变量,就会抛出ReferenceError。
换句话说:
- 变量在编译阶段就已经知道存在,但在声明之前不能访问。
- 直到变量真正执行声明(并赋值)之后,才能正常使用。
javascript
// 实例1.
const foo = () => {
console.log('///')
console.log(a)
let a = 3;
}
foo() //ReferenceError
或者
javascript
// 实例2.
const foo = () => {
let a;
console.log('///')
console.log(a)
a = 3;
}
foo(); //undefined
let和const可以说会变量提升但是被 TDZ 阻挡了访问,看起来就像没有提升。
这种机制避免了 var 的变量提升带来的困扰,让代码更安全、更易读。
2.3 const 的使用注意点
const 声明的变量不能被重新赋值(简单数据类型):
简单数据类型包括:
NumberStringBooleanundefinednullSymbol(ES6 新增)
ini
const PI = 3.1415926;
PI = 3.14; // TypeError
但是,const 对对象和数组是浅冻结的:
ini
const person = { name: "gg", age: 20 };
person.age = 21; // 可以修改对象属性
console.log(person); // { name: "gg", age: 21 }
const wes = Object.freeze(person); // 深冻结对象
wes.age = 17; // 无效
console.log(wes); // { name: "gg", age: 21 }
简单类型(number、string、boolean)的
const完全不可变,而复杂类型(对象、数组)引用地址不可变,但属性可变。用Object.freeze可以实现深层不可变。
3. 函数与变量提升的差异
在 JavaScript 中,函数也是一等公民 ,可以像变量一样被提升,但提升行为与 var 不完全相同。
scss
setWidth(); // 运行正常
function setWidth() {
var width = 100;
console.log(width); // 100
}
// console.log(width); // ReferenceError
特点总结:
- 函数声明会连同函数体一起提升。
var声明的变量只会提升声明,不会提升赋值。- 函数内部使用
var、let、const,遵循各自的作用域规则。
因此,函数提升可以在调用前使用函数,而
var变量提升只能得到undefined。
4. 总结 var、let 和 const
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 是(赋值未提升) | 是,但有 TDZ | 是,但有 TDZ |
| 可重复声明 | 是 | 否 | 否 |
| 可修改值 | 是 | 是 | 否(对象属性可改,但引用不可改) |
4.1 开发实践建议
- 不再使用
var
let+const结合使用,保证代码安全和可读性。 - 默认使用
const
只有在需要重新赋值时才使用let。 - 对象或数组尽量用
Object.freeze
保证数据不可变,避免副作用。 - 理解 TDZ 与作用域
有助于减少ReferenceError,让调试更简单。
5. 常见错误集合
ReferenceError: height is not defined
访问未声明的变量。TypeError: Assignment to constant variable.
对const变量重新赋值。ReferenceError: Cannot access 'PI' before initialization
暂时性死区内访问let或const变量。
理解这些错误,能够帮助你快速定位问题,并加深对作用域和提升机制的理解。
6. 代码示例总结
6.1 块级作用域
块级作用域 只在大括号 {} 内有效 ,并且主要影响 let 和 const:
- 普通块
{} if/elseswitch casetry/catchfor/while本身不是块级作用域,它们只是语句。var只遵循函数作用域(或全局作用域),忽略块级作用域:
arduino
{
var age = 18; // 不支持块级作用域或忽略块级作用域
let height = 188; // 支持块级作用域
}
console.log(age); // 18
console.log(height); // ReferenceError
6.2 对象常量
ini
const person = { name: "gg", age: 20 };
person.age = 21; // 可修改属性
console.log(person);
const wes = Object.freeze(person);
wes.age = 17; // 无效
console.log(wes);
6.3 函数提升
scss
setWidth(); // 可以调用
function setWidth() {
var width = 100;
console.log(width); // 100
}
// console.log(width); // ReferenceError
7. 总结
var适合老旧代码维护,现代开发不推荐使用。let提供块级作用域,解决var的变量提升问题。const用于不可变值或引用,结合Object.freeze可实现深冻结。- 理解 TDZ 和提升机制,能够避免常见的
ReferenceError和TypeError。 - 在企业级开发中,合理使用
let和const能显著提升代码质量和可维护性。