深入理解 JavaScript 变量声明:var、let 与 const 的全面解析

75 阅读5分钟

前言

在 JavaScript 的开发中,变量声明是最基础也是最重要的概念之一。随着 ES6(ECMAScript 2015)的发布,我们有了三种声明变量的方式:varletconst。这篇文章将深入探讨这三者的区别、特性以及最佳实践。

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 声明的变量具有函数作用域
  • 块级作用域letconst 声明的变量具有块级作用域

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 编译优化

使用 constlet 可以帮助 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. 最佳实践总结

  1. 默认使用 const

    • 对于不会重新赋值的变量,优先使用 const
    • 提高代码可读性和可维护性
  2. 需要重新赋值时使用 let

    • 当变量需要被重新赋值时使用 let
    • 明确标识出可变的变量
  3. 避免使用 var

    • 在现代 JavaScript 开发中,尽量避免使用 var
    • 只在维护旧代码时使用
  4. 命名规范

    • 常量使用全大写和下划线:const API_KEY = "abc123"
    • 变量使用驼峰命名:let userName = "张三"
  5. 作用域最小化

    • 在最小的作用域内声明变量
    • 使用块级作用域限制变量生命周期

结语

理解 varletconst 的区别是掌握现代 JavaScript 的基础。通过合理使用这些声明方式,可以编写出更安全、更易维护的代码。记住:默认使用 const,需要重新赋值时用 let,避免使用 var,这是现代 JavaScript 开发的最佳实践。