前言
在 JavaScript 的开发中,变量声明是最基础也是最重要的概念之一。随着 ES6(ECMAScript 2015)的发布,我们有了三种声明变量的方式:var、let 和 const。这篇文章将深入探讨这三者的区别、特性以及最佳实践。
1. 从 var 开始:JavaScript 的原始变量声明
1.1 var 的基本特性
// js 是弱类型语言,变量类型由值决定
var age = 18;
age++;
console.log(age); // 19
// 约定俗成:变量名大写为常量,但不真正保证不可变
var PI = 3.1415926;
PI = 3.14; // 这行代码不会报错
console.log(PI); // 3.14
1.2 变量提升(Hoisting)
console.log(age); // undefined,而不是报错
var age; // undefined
age = 18; // 赋值
// 相当于下面的代码:
var age; // 变量提升到顶部
console.log(age); // undefined
age = 18;
变量提升机制:
- 在代码执行前的编译阶段,
var声明的变量会被提升到作用域顶部 - 只提升声明,不提升赋值
- 这是 JavaScript 早期设计中的一个"糟粕"
2. ES6 新特性:let 和 const
2.1 let 声明变量
let height = 188;
height++;
console.log(height); // 189
// 解决了 var 的一些问题
console.log(PI); // 报错:ReferenceError: Cannot access 'PI' before initialization
const PI = 3.1415926;
2.2 const 声明常量
const key = "abc123";
// key = "abc234"; // 报错:TypeError: Assignment to constant variable
const person = {
name: "张三",
age: 28
}
// 可以修改对象的属性,但不能重新赋值整个对象
person.name = "李四";
console.log(person); // {name: "李四", age: 28}
// person = {}; // 报错:TypeError: Assignment to constant variable
2.3 完全冻结对象
const person = {
name: "张三",
age: 28
}
Object.freeze(person);
person.age = 30; // 静默失败,严格模式下会报错
console.log(person); // {name: "张三", age: 28}
3. 块级作用域
3.1 作用域对比
{
var age = 18;
let height = 188;
const sex = "男";
}
console.log(age); // 18,var 没有块级作用域
console.log(height); // 报错:ReferenceError: height is not defined
console.log(sex); // 报错:ReferenceError: sex is not defined
作用域类型:
- 函数作用域:
var声明的变量具有函数作用域 - 块级作用域:
let和const声明的变量具有块级作用域
4. 函数提升与变量提升
4.1 函数提升
setWidth(); // 正常执行,函数整体被提升
function setWidth(){
var width = 100;
console.log(width);
}
// 函数表达式不会提升
// setHeight(); // 报错
var setHeight = function() {
console.log("设置高度");
}
4.2 提升的区别
| 声明类型 | 提升内容 | 初始化值 |
|---|---|---|
var | 仅声明 | undefined |
function | 声明+赋值 | 函数体 |
let/const | 暂时性死区 | 未初始化 |
5. 错误类型总结
5.1 ReferenceError
// 作用域外访问
{
let height = 188;
}
console.log(height); // ReferenceError: height is not defined
// 暂时性死区
console.log(PI); // ReferenceError: Cannot access 'PI' before initialization
const PI = 3.1415926;
5.2 TypeError
const key = "abc123";
key = "abc234"; // TypeError: Assignment to constant variable
6. 实际应用场景
6.1 循环中的变量声明
// var 在循环中的问题
// var是函数作用域,循环中的所有迭代共享同一个变量
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 3, 3, 3
}, 100);
}
// setTimeout回调函数形成了闭包,捕获的是对 i的引用,而不是 i的值
// let 解决循环问题
// let是块级作用域,每次循环都会创建新的变量绑定
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 0, 1, 2
}, 100);
}
- 闭包捕获的是变量的引用,而不是值
6.2 模块模式中的使用
// 模块配置 - 使用 const
const CONFIG = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retryCount: 3
};
// 模块状态 - 使用 let
let moduleState = {
isLoading: false,
data: null,
error: null
};
// 修改状态
function setLoading(state) {
moduleState.isLoading = state;
}
7. 性能考量
7.1 编译优化
使用 const 和 let 可以帮助 JavaScript 引擎进行更好的优化:
const:编译器知道值不会改变,可以进行常量折叠等优化let:明确的块级作用域有助于作用域链的优化var:函数作用域和变量提升增加了优化的复杂性
7.2 内存管理
// 良好的作用域管理有助于内存回收
function processData(data) {
// 使用块级作用域及时释放临时变量
{
let tempData = JSON.parse(data);
// 处理数据...
}
// tempData 在这里已经被垃圾回收
const result = { status: 'success' };
return result;
}
8. 迁移策略
8.1 从 var 迁移到 let/const
// 旧代码
var count = 0;
var name = "张三";
var isActive = true;
// 新代码
let count = 0;
const name = "张三";
const isActive = true;
// 逐步迁移策略
// 1. 先将不会改变的 var 改为 const
// 2. 将需要改变的 var 改为 let
// 3. 删除所有 var 的使用
8.2 代码检查工具配置
在 ESLint 中配置规则,强制使用现代变量声明:
{
"rules": {
"no-var": "error",
"prefer-const": "error",
"no-const-assign": "error"
}
}
9. 最佳实践总结
-
默认使用
const- 对于不会重新赋值的变量,优先使用
const - 提高代码可读性和可维护性
- 对于不会重新赋值的变量,优先使用
-
需要重新赋值时使用
let- 当变量需要被重新赋值时使用
let - 明确标识出可变的变量
- 当变量需要被重新赋值时使用
-
避免使用
var- 在现代 JavaScript 开发中,尽量避免使用
var - 只在维护旧代码时使用
- 在现代 JavaScript 开发中,尽量避免使用
-
命名规范
- 常量使用全大写和下划线:
const API_KEY = "abc123" - 变量使用驼峰命名:
let userName = "张三"
- 常量使用全大写和下划线:
-
作用域最小化
- 在最小的作用域内声明变量
- 使用块级作用域限制变量生命周期
结语
理解 var、let 和 const 的区别是掌握现代 JavaScript 的基础。通过合理使用这些声明方式,可以编写出更安全、更易维护的代码。记住:默认使用 const,需要重新赋值时用 let,避免使用 var,这是现代 JavaScript 开发的最佳实践。