JS 变量那些坑:从 var 到 let/const 的终极解密

JavaScript 深度解析var、let 和 const变量声明

在 JavaScript 的学习过程中,变量声明是最基础却也是最容易踩坑的知识点。尤其是当你从传统的 Java/C++ 等语言背景过渡到 JavaScript 时,var、let、const 的行为差异可能让你一头雾水。本文将结合示例和原理,带你深入理解 JavaScript 中变量的声明与作用域规则,并总结最佳实践。


1. 早期的 JavaScript 与 var

在 ES5(ECMAScript 5,即 2011 年标准)之前,JavaScript 只有 var 一种声明变量的方式。var 看似简单,但隐藏了很多坑。有些老师叫你直接弃用 var 转而使用 let ,但是并没有告诉你为什么,现在我来解释解释。

ini 复制代码
var age = 18; // js 是弱类型语言,变量的类型由值决定
age++;
var PI = 3.1415926; // 变量名大写通常表示常量
console.log(age); // 19

注意:虽然我们用大写 PI 表示"常量",但是 var 并不能阻止你修改它。

ini 复制代码
PI = 3.14;
console.log(PI); // 3.14

这意味着,使用 var 时,变量既可以被重复声明,也可以被重新赋值,这在大型项目中很容易引起混乱。


1.1 var 的变量提升(Hoisting)

var 的最大特点之一就是变量提升 。即便你在声明之前使用变量,代码也不会报 ReferenceError,而是返回 undefined:

ini 复制代码
console.log(age); // undefined
var age = 18;

这里的执行顺序可以理解为:

  1. 编译阶段:JavaScript 引擎把所有 var 声明提前(提升)到函数或全局的最顶端,但不会赋值。
  2. 执行阶段:按原代码顺序执行,进行赋值。

这种机制虽然允许代码运行,但会降低代码可读性,容易导致 BUG。在现代开发中,建议尽量避免使用 var。


1.2 var 的作用域问题

var 是函数作用域 ,不支持块级作用域。这意味着它在大括号 {} 内部声明时,依然属于外层函数或全局作用域:

ini 复制代码
{
    var age = 18;
}
console.log(age); // 18,仍然能访问

在复杂逻辑中,这种行为会让变量名冲突和状态管理变得非常麻烦。


2. ES6 引入的 let 与 const

从 ES6(2015 年)开始,JavaScript 引入了 let 和 const,提供了块级作用域和常量声明,从根本上解决了 var 带来的诸多问题。

ini 复制代码
let height = 188;
height++;
console.log(height); // 189

const key = 'abc123';
key = 'abc234'; // TypeError: Assignment to constant variable.

2.1 块级作用域

let 和 const 支持块级作用域,即变量只在 {} 内有效:

ini 复制代码
{
    let height = 188;
    const PI = 3.141592;
}

console.log(height); // ReferenceError
console.log(PI);     // ReferenceError

这与 var 的行为截然不同,能有效避免变量污染全局的问题。


2.2 暂时性死区(Temporal Dead Zone, TDZ)

let 和 const 有一个重要特点:暂时性死区 。在变量声明之前访问,会报错 ReferenceError: 暂时性死区(Temporal Dead Zone, TDZ) 是指:

当你用 let 或 const 声明一个变量时,从代码块开始 到变量声明的位置之间 的这段区域,变量处于一种"已声明但不可访问"的状态。如果在这段区域内访问变量,就会抛出 ReferenceError。

换句话说:

  • 变量在编译阶段就已经知道存在,但在声明之前不能访问。
  • 直到变量真正执行声明(并赋值)之后,才能正常使用。
javascript 复制代码
// 实例1.
const foo = () => {
    console.log('///')
    console.log(a)
    let a = 3;
}

foo()  //ReferenceError

或者

javascript 复制代码
// 实例2.
const foo = () => {
    let a;
    console.log('///')
    console.log(a)
    a = 3;
}
foo();  //undefined

let和const可以说会变量提升但是被 TDZ 阻挡了访问,看起来就像没有提升。

这种机制避免了 var 的变量提升带来的困扰,让代码更安全、更易读。


2.3 const 的使用注意点

const 声明的变量不能被重新赋值(简单数据类型):

简单数据类型包括:

  • Number
  • String
  • Boolean
  • undefined
  • null
  • Symbol(ES6 新增)
ini 复制代码
const PI = 3.1415926;
PI = 3.14; // TypeError

但是,const 对对象和数组是浅冻结的:

ini 复制代码
const person = { name: "gg", age: 20 };
person.age = 21; // 可以修改对象属性
console.log(person); // { name: "gg", age: 21 }

const wes = Object.freeze(person); // 深冻结对象
wes.age = 17; // 无效
console.log(wes); // { name: "gg", age: 21 }

简单类型(number、string、boolean)的 const 完全不可变,而复杂类型(对象、数组)引用地址不可变,但属性可变。用 Object.freeze 可以实现深层不可变。


3. 函数与变量提升的差异

在 JavaScript 中,函数也是一等公民 ,可以像变量一样被提升,但提升行为与 var 不完全相同。

scss 复制代码
setWidth(); // 运行正常

function setWidth() {
    var width = 100;
    console.log(width); // 100
}

// console.log(width); // ReferenceError

特点总结:

  • 函数声明会连同函数体一起提升。
  • var 声明的变量只会提升声明,不会提升赋值。
  • 函数内部使用 var、let、const,遵循各自的作用域规则。

因此,函数提升可以在调用前使用函数,而 var 变量提升只能得到 undefined。


4. 总结 var、let 和 const

特性 var let const
作用域 函数作用域 块级作用域 块级作用域
变量提升 是(赋值未提升) 是,但有 TDZ 是,但有 TDZ
可重复声明 是 否 否
可修改值 是 是 否(对象属性可改,但引用不可改)

4.1 开发实践建议

  1. 不再使用 var
    let + const 结合使用,保证代码安全和可读性。
  2. 默认使用 const
    只有在需要重新赋值时才使用 let。
  3. 对象或数组尽量用 Object.freeze
    保证数据不可变,避免副作用。
  4. 理解 TDZ 与作用域
    有助于减少 ReferenceError,让调试更简单。

5. 常见错误集合

  • ReferenceError: height is not defined
    访问未声明的变量。
  • TypeError: Assignment to constant variable.
    对 const 变量重新赋值。
  • ReferenceError: Cannot access 'PI' before initialization
    暂时性死区内访问 let 或 const 变量。

理解这些错误,能够帮助你快速定位问题,并加深对作用域和提升机制的理解。


6. 代码示例总结

6.1 块级作用域

块级作用域 只在大括号 {} 内有效 ,并且主要影响 let 和 const:

  • 普通块 {}
  • if / else
  • switch case
  • try / catch
  • for / while 本身不是块级作用域,它们只是语句。
  • var 只遵循函数作用域(或全局作用域),忽略块级作用域:
arduino 复制代码
{
    var age = 18;    // 不支持块级作用域或忽略块级作用域
    let height = 188;  // 支持块级作用域
}
console.log(age);    // 18
console.log(height); // ReferenceError

6.2 对象常量

ini 复制代码
const person = { name: "gg", age: 20 };
person.age = 21; // 可修改属性
console.log(person);

const wes = Object.freeze(person);
wes.age = 17; // 无效
console.log(wes);

6.3 函数提升

scss 复制代码
setWidth(); // 可以调用

function setWidth() {
    var width = 100;
    console.log(width); // 100
}
// console.log(width); // ReferenceError

7. 总结

  1. var 适合老旧代码维护,现代开发不推荐使用。
  2. let 提供块级作用域,解决 var 的变量提升问题。
  3. const 用于不可变值或引用,结合 Object.freeze 可实现深冻结。
  4. 理解 TDZ 和提升机制,能够避免常见的 ReferenceError 和 TypeError。
  5. 在企业级开发中,合理使用 let 和 const 能显著提升代码质量和可维护性。
相关推荐
福兮说10 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理10 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆66611 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
凤城老人11 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)11 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan12 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_13 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒13 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技13 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
yivifu14 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub