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

JavaScript 深度解析varletconst变量声明

在 JavaScript 的学习过程中,变量声明是最基础却也是最容易踩坑的知识点。尤其是当你从传统的 Java/C++ 等语言背景过渡到 JavaScript 时,varletconst 的行为差异可能让你一头雾水。本文将结合示例和原理,带你深入理解 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 引入的 letconst

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

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

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

2.1 块级作用域

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

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

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

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


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

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

当你用 letconst 声明一个变量时,从代码块开始变量声明的位置之间 的这段区域,变量处于一种"已声明但不可访问"的状态。如果在这段区域内访问变量,就会抛出 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

letconst可以说会变量提升但是被 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 声明的变量只会提升声明,不会提升赋值。
  • 函数内部使用 varletconst,遵循各自的作用域规则。

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


4. 总结 varletconst

特性 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
    暂时性死区内访问 letconst 变量。

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


6. 代码示例总结

6.1 块级作用域

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

  • 普通块 {}
  • 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 和提升机制,能够避免常见的 ReferenceErrorTypeError
  5. 在企业级开发中,合理使用 letconst 能显著提升代码质量和可维护性。
相关推荐
俩毛豆3 小时前
【图片】【编缉】图片增加水印(通过组件的Overlay方法增加水印)
前端·harmonyos
出师未捷的小白3 小时前
[NestJS] 手摸手~工作队列模式的邮件模块解析以及grpc调用
前端·后端
十年_H3 小时前
Cesium自定义着色器-模式
javascript·cesium
shuaijie05183 小时前
表格单元格输入框转换-其一
javascript·elementui
Z_B_L4 小时前
问题记录--elementui中el-form初始化表单resetFields()方法使用时出现的问题
前端·javascript·vue.js·elementui·1024程序员节
袁煦丞4 小时前
PandaWiki开源知识库系统破解内网限制:cpolar内网穿透实验室第616个成功挑战
前端·程序员·远程工作
golang学习记4 小时前
Next.js MCP Server 实战指南:让 AI 编程助手真正“懂”你的应用
前端
柳鲲鹏4 小时前
多种方法:OpenCV中修改像素RGB值
前端·javascript·opencv·1024程序员节
susu10830189114 小时前
chrome浏览器设置为手机模式
前端·chrome