let、const、var有哪些区别

4 阅读5分钟

1. 核心区别概览

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升是(初始化为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. 最佳实践总结

  1. 默认使用 const:除非需要重新赋值,否则使用 const
  2. 需要重新赋值时用 let:计数器、状态变量等
  3. 避免使用 var:除非需要兼容旧代码或需要函数作用域特性
  4. 注意暂时性死区:在声明前不要访问 let/const 变量
  5. 利用块级作用域:使用 { } 创建独立作用域
  6. 全局变量使用 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 已经成为行业标准。