JavaScript 中的变量声明:var、let 与 const 详解
在 JavaScript 中,变量的声明方式直接影响其作用域、提升行为以及可变性。随着 ES6(ECMAScript 2015)的引入,JavaScript 引入了 let 和 const 关键字,使得变量声明更加安全和符合直觉。本文将深入探讨 var、let 和 const 的区别,并解释常见的错误类型。
1. 使用 var 声明变量
早期的JS 就用来页面交互,有一些缺失甚至设计不合理的地方
语言精粹,The Good Parts,The Bad Partsa
es5 只有var 申明变量,没有常量
基本语法
var age = 18; // js弱类型,由值决定
特性与问题
变量提升(Hoisting)
var 声明的变量具有“变量提升”特性。这意味着在编译阶段,变量的声明会被提升到其作用域的顶部,但赋值不会。
console.log(a); // 输出: undefined
var a = 1;
虽然代码看起来是先访问 a 再赋值,但由于变量提升,实际执行顺序如下:
var a; // 声明被提升
console.log(a); // 此时 a 是 undefined
a = 1; // 赋值在执行阶段进行
作用域问题
var 声明的变量是函数作用域(function-scoped),而不是块级作用域。这意味着在 if、for 等块中声明的 var 变量,在块外仍然可以访问。
if (true) {
var x = 10;
}
console.log(x); // 输出: 10
这种行为常常与开发者的直觉不符,容易导致意外的变量污染。
2. 使用 let 声明变量
let 是 ES6 引入的新关键字,用于声明块级作用域的变量。
基本语法
let b = 2;
特性
块级作用域
let 声明的变量只在当前块 {} 内有效。而var的变量全局引用并不严谨
{ // 块级作用域
var age = 18;//不支持块级作用域
let height = 188;//支持块级作用域 是大型项目或高级语言的核心
}
console.log(age);
console.log(height);
无变量提升,但有暂时性死区(Temporal Dead Zone, TDZ)
let 变量不会被提升到作用域顶部,如果在声明之前访问,会抛出错误。
console.log(z); // ReferenceError: Cannot access 'z' before initialization
let z = 3;
这个错误提示说明变量 z 处于“暂时性死区”——从块的开始到变量被正式声明之间的区域,访问该变量会报错。
3. 使用 const 声明常量
const 用于声明一个常量,其值在声明后不能被重新赋值。
es5早期常量
var PI = 3.1415926;//变量大写 约定就不应该改变 编程习惯
es6基本语法
const PI = 3.14159;
特性
const也具有块级作用域和暂时性死区。- 声明时必须初始化,且不能重新赋值。
const PI = 3.14;
PI = 3.15; // TypeError: Assignment to constant variable.
注意: 简单数据类型的常量不能改变的; 复杂数据类型的常量,不能改变引用的地址,但是可以改变引用地址中的属性值
const obj = { name: "Alice" };
obj.name = "Bob"; // 合法
obj = {}; // TypeError: Assignment to constant variable.
如果对象一定不能变呢?
const wes = Object.freeze(person);// 冻结对象
使用冻结对象那么对象属性值也无法改变
JavaScript 中的函数提升与作用域详解
在 JavaScript 中,函数被视为“一等公民”(first-class citizens),这意味着函数不仅可以被调用,还可以作为参数传递、赋值给变量,甚至作为返回值。与此同时,JavaScript 的函数提升(Function Hoisting)机制也使其在执行前就具备了可访问性。
函数提升 vs 变量提升
在编译阶段,JavaScript 会进行“提升”(Hoisting)操作,将 var 声明的变量和函数声明提升到其作用域顶部。但两者的行为有重要区别:
var提升:仅提升变量声明,不提升赋值。因此在声明前访问会得到undefined。- 函数提升:不仅提升函数声明,还会将整个函数定义一起提升,因此可以在声明之前安全调用。
示例对比
// 函数声明:可以提前调用(完全提升)
setWidth(); // 输出: 100
function setWidth() {
var width = 100;
console.log(width);
}
上述代码可以正常运行,因为函数声明 setWidth 在编译阶段就被完整提升。
然而,如果使用函数表达式并赋值给 let 或 const 变量,则行为完全不同:
setWidth(); // ❌ 报错:Cannot access 'setWidth' before initialization
let setWidth = function() {
var width = 100;
console.log(width);
};
错误分析
上面代码会抛出:
ReferenceError: Cannot access 'setWidth' before initialization
这是因为:
let声明不会像var那样简单提升为undefined。- 它进入了“暂时性死区”(Temporal Dead Zone, TDZ),在正式声明前无法访问。
- 虽然变量名
setWidth已被绑定到当前作用域,但在赋值完成前不可使用。
作用域解析
我们来详细分析上述代码中的作用域层级:
let setWidth = function() {
// 函数作用域(局部作用域)
var width = 100;
// 块级作用域示例
// {
// let a = 1;
// }
// console.log(a); // ReferenceError: a is not defined
console.log(width); // ✅ 输出: 100
};
// console.log(width); // ❌ ReferenceError: width is not defined
作用域说明:
width是使用var在函数内部声明的,因此它属于函数作用域,只能在setWidth函数内访问。- 外部全局作用域中无法访问
width,尝试访问会抛出ReferenceError: width is not defined。 - 如果启用块级作用域(如
let a = 1;),其变量a仅在{}内有效,外部也无法访问。
常见错误类型解析
1. ReferenceError: a is not defined
当你试图访问一个未声明的变量时,JavaScript 会抛出此错误。
console.log(nonExistentVar); // ReferenceError: nonExistentVar is not defined
原因:变量未在任何作用域中声明。
2. TypeError: Assignment to constant variable.
当你尝试给 const 声明的变量重新赋值时,会触发此错误。
const age = 25;
age = 30; // TypeError: Assignment to constant variable.
解决方法:使用 let 声明可变变量,或避免重新赋值。
3. ReferenceError: Cannot access 'PI' before initialization
这是 let 和 const 特有的错误,发生在你试图在声明之前访问变量。
console.log(PI); // ReferenceError: Cannot access 'PI' before initialization
const PI = 3.14;
原因:变量处于“暂时性死区”。虽然 JavaScript 知道这个变量存在,但在它被初始化之前不能访问。
总结与最佳实践
| 关键字 | 作用域 | 提升 | 可重新赋值 | 初始化要求 |
|---|---|---|---|---|
var | 函数作用域 | 是 | 是 | 否 |
let | 块级作用域 | 否(有 TDZ) | 是 | 否 |
const | 块级作用域 | 否(有 TDZ) | 否 | 是 |
推荐做法:
- 优先使用
const:如果你不打算改变变量的值,使用const。 - 其次使用
let:当变量需要重新赋值时使用。 - 避免使用
var:由于其变量提升和函数作用域带来的不可预测性,现代 JavaScript 开发中应尽量避免。
通过合理使用 let 和 const,可以写出更清晰、更安全的代码,避免常见的作用域和提升陷阱。