1. 核心区别概览
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块级作用域 | 块级作用域 |
| 变量提升 | 是(初始化为undefined) | 是(但不初始化,有TDZ) | 是(但不初始化,有TDZ) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 全局属性 | 会成为window的属性 | 不会成为window的属性 | 不会成为window的属性 |
| 暂时性死区 | 无 | 有 | 有 |
| 必须初始化 | 否 | 否 | 是 |
| 可重新赋值 | 是 | 是 | 否(基本类型)/可修改属性(引用类型) |
2. 详细对比分析
2.1 作用域 (Scope)
// var - 函数作用域
function varExample() {
if (true) {
var varVariable = "I'm var";
}
console.log(varVariable); // "I'm var" - 可以访问
}
varExample();
console.log(varVariable); // ReferenceError - 外部不能访问
// let/const - 块级作用域
function letConstExample() {
if (true) {
let letVariable = "I'm let";
const constVariable = "I'm const";
console.log(letVariable); // "I'm let" - 可以访问
console.log(constVariable); // "I'm const" - 可以访问
}
console.log(letVariable); // ReferenceError - 不能访问
console.log(constVariable); // ReferenceError - 不能访问
}
// 循环中的区别
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log('var:', i), 100); // 输出 3, 3, 3
}
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log('let:', j), 100); // 输出 0, 1, 2
}
2.2 变量提升 (Hoisting)
// var - 变量提升且初始化为undefined
console.log(varHoisted); // undefined (不会报错)
var varHoisted = "I'm hoisted";
// let/const - 变量提升但存在暂时性死区
console.log(letHoisted); // ReferenceError: Cannot access before initialization
let letHoisted = "I'm hoisted too";
console.log(constHoisted); // ReferenceError: Cannot access before initialization
const constHoisted = "Me too";
// 实际提升过程
// var 的情况(相当于):
var varHoisted; // 声明提升,初始化为undefined
console.log(varHoisted); // undefined
varHoisted = "I'm hoisted";
// let/const 的情况:
// 声明被提升,但变量处于"暂时性死区",无法访问
console.log(letHoisted); // 报错
let letHoisted = "I'm hoisted too"; // 在此处初始化
2.3 重复声明 (Redeclaration)
// var - 允许重复声明
var x = 1;
var x = 2; // 允许,覆盖之前的声明
console.log(x); // 2
// let - 不允许重复声明
let y = 1;
let y = 2; // SyntaxError: Identifier 'y' has already been declared
// const - 不允许重复声明
const z = 1;
const z = 2; // SyntaxError: Identifier 'z' has already been declared
// 跨作用域可以重复声明
let a = 1;
if (true) {
let a = 2; // 允许,不同的作用域
console.log(a); // 2
}
console.log(a); // 1
2.4 全局作用域行为
// 在全局作用域中
var globalVar = "I'm global var";
let globalLet = "I'm global let";
const globalConst = "I'm global const";
// var 会成为 window 的属性(浏览器环境)
console.log(window.globalVar); // "I'm global var"
console.log(window.globalLet); // undefined
console.log(window.globalConst); // undefined
// 注意:在Node.js中,全局对象是global,不是window
// 在浏览器控制台执行上述代码
2.5 const 的特殊性
// const 必须初始化
const a; // SyntaxError: Missing initializer in const declaration
// const 声明的变量不能重新赋值
const PI = 3.14159;
PI = 3.14; // TypeError: Assignment to constant variable
// 但对于引用类型,可以修改其属性
const person = {
name: "Alice",
age: 25
};
person.age = 26; // 允许,修改属性
person.city = "Beijing"; // 允许,添加属性
console.log(person); // {name: "Alice", age: 26, city: "Beijing"}
// 但不能重新赋值
person = {}; // TypeError: Assignment to constant variable
// 数组也是引用类型
const numbers = [1, 2, 3];
numbers.push(4); // 允许
numbers[0] = 100; // 允许
console.log(numbers); // [100, 2, 3, 4]
// 如果要完全冻结对象,使用Object.freeze()
const frozenObject = Object.freeze({
name: "Alice",
age: 25
});
frozenObject.age = 26; // 静默失败(严格模式下会报错)
console.log(frozenObject.age); // 25
2.6 暂时性死区 (Temporal Dead Zone)
// TDZ 开始
// console.log(temp); // 如果这里访问会报错
let temp = "I'm initialized"; // TDZ 结束
console.log(temp); // "I'm initialized"
// 更复杂的TDZ例子
function tdzExample() {
console.log(myVar); // undefined (变量提升)
console.log(myLet); // ReferenceError: Cannot access before initialization
var myVar = "var value";
let myLet = "let value";
// TDZ也会影响typeof
console.log(typeof undeclaredVar); // "undefined" (未声明的变量)
console.log(typeof tdzVar); // ReferenceError (在TDZ中)
let tdzVar = "in TDZ";
}
3. 实际应用场景
3.1 推荐使用方式
// 1. 优先使用 const
// 除非需要重新赋值,否则都使用const
const API_URL = "https://api.example.com";
const DEFAULT_CONFIG = { timeout: 5000 };
const COLORS = ["red", "green", "blue"];
// 2. 需要重新赋值时使用 let
let counter = 0;
let currentUser = null;
let isLoading = false;
// 3. 避免使用 var(除非有特殊需求)
// var 的特殊用例:需要函数作用域的行为
function oldStyle() {
for (var i = 0; i < 5; i++) {
// 使用 var 让 i 在函数内都可用
}
console.log(i); // 5 - 在函数内可用
}
3.2 常见陷阱与解决方案
// 陷阱1:循环中的闭包问题(已解决)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
// 如果用var,需要闭包解决
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100); // 0, 1, 2
})(i);
}
// 陷阱2:条件声明
if (true) {
let conditionMet = true;
}
// console.log(conditionMet); // ReferenceError
// 陷阱3:switch语句中的块级作用域
let action = "create";
switch (action) {
case "create":
let result = "creating"; // 需要单独的块
console.log(result);
break;
case "update":
// let result = "updating"; // 错误,重复声明
// 解决方法:使用块
{
let result = "updating";
console.log(result);
}
break;
}
4. ES6+ 中的新特性
4.1 解构赋值
// let 允许重新赋值,适合解构
let [x, y] = [1, 2];
[x, y] = [y, x]; // 交换变量
// const 也可以解构,但变量不能重新赋值
const [a, b] = [1, 2];
// [a, b] = [2, 1]; // 错误
// 对象解构
const { name, age } = { name: "Alice", age: 25 };
// name = "Bob"; // 错误,不能重新赋值
4.2 模块作用域
// 在模块中,let/const/var 的作用域行为相同
// 都不会成为全局变量
// module.js
export const MODULE_CONST = "module constant";
export let moduleVar = "module variable";
export var oldModuleVar = "old module variable";
// 在另一个模块中导入
import { MODULE_CONST, moduleVar, oldModuleVar } from './module.js';
5. 性能考虑
// 现代JavaScript引擎中,let/const 通常比 var 有更好的性能
// 原因:块级作用域使得变量生命周期更明确,便于优化
// 1. 内存管理更好
function memoryExample() {
for (let i = 0; i < 1000; i++) {
// i 只在循环内存在
}
// i 在这里已经被回收
for (var j = 0; j < 1000; j++) {
// j 在整个函数内存在
}
// j 在这里仍然存在,直到函数结束
}
// 2. 利于静态分析
// let/const 的块级作用域让代码分析工具更容易理解变量生命周期
6. 最佳实践总结
- 默认使用
const:除非需要重新赋值,否则使用 const - 需要重新赋值时用
let:计数器、状态变量等 - 避免使用
var:除非需要兼容旧代码或需要函数作用域特性 - 注意暂时性死区:在声明前不要访问 let/const 变量
- 利用块级作用域:使用 { } 创建独立作用域
- 全局变量使用
const:避免意外修改全局状态
// 好的实践
const CONFIG = {
apiUrl: "https://api.example.com",
timeout: 5000
};
function processItems(items) {
const results = [];
for (let i = 0; i < items.length; i++) {
const processed = processItem(items[i]);
results.push(processed);
}
return results;
}
// 避免的实践
var globalVar = "bad practice"; // 污染全局作用域
function badExample() {
for (var i = 0; i < 10; i++) {
// 循环逻辑
}
console.log(i); // 意外的访问
}
通过理解这些区别,可以写出更安全、更易维护的 JavaScript 代码。在现代 JavaScript 开发中,优先使用 const 和 let 已经成为行业标准。