let vs var:JavaScript 变量声明避坑指南

在 JavaScript 中,var 和 let 都是用于声明变量的关键字,但它们在作用域、提升行为、重复声明、全局对象属性等方面存在核心差异,这些差异直接影响代码的安全性和可维护性。以下是详细对比:

一、核心区别总览表

特性varlet
作用域函数作用域(function-scoped)或全局作用域块级作用域(block-scoped)
提升行为变量提升(hoisting),初始值为 undefined暂时性死区(TDZ),提升但不可访问
重复声明允许(覆盖原有变量)不允许(同一作用域内报错)
全局对象属性声明的全局变量会成为 window(浏览器)/global(Node)的属性声明的全局变量不会成为全局对象的属性
循环中的表现循环变量泄露为全局 / 函数变量,闭包易出错循环变量为块级作用域,闭包表现符合预期

二、逐点详细解析

1. 作用域差异(最核心区别)
  • var:函数作用域 / 全局作用域var 声明的变量仅在当前函数内有效,若在函数外声明则为全局作用域(无论是否在代码块内,如 iffor 块)。

    示例:

    if (true) {
      var a = 10; // 函数/全局作用域,而非 if 块级作用域
    }
    console.log(a); // 10(可访问,因为 a 不是块级变量)
    
    function fn() {
      var b = 20;
    }
    fn();
    console.log(b); // ReferenceError: b is not defined(函数作用域,外部不可访问)
    
  • let:块级作用域let 声明的变量仅在当前代码块{} 包裹的区域,如 ifforwhile、函数体)内有效,块外无法访问。

    示例:

    if (true) {
      let a = 10; // 块级作用域(if 块内)
    }
    console.log(a); // ReferenceError: a is not defined(块外不可访问)
    
    for (let i = 0; i < 3; i++) {
      // i 是循环块级作用域,每次循环创建新的 i
    }
    console.log(i); // ReferenceError: i is not defined(循环块外不可访问)
    
2. 提升行为与暂时性死区(TDZ)
  • var:变量提升 + 初始化为 undefinedvar 声明的变量会被 “提升” 到当前作用域的顶部,且默认初始值为 undefined,因此在声明前访问变量不会报错,只会得到 undefined

    示例:

    console.log(x); // undefined(变量提升,已初始化)
    var x = 5;
    console.log(x); // 5
    

    等价于:

    var x; // 提升到作用域顶部,初始化为 undefined
    console.log(x); // undefined
    x = 5;
    console.log(x); // 5
    
  • let:提升但进入暂时性死区(TDZ)let 声明的变量也会被提升,但不会默认初始化,而是进入 “暂时性死区”(Temporal Dead Zone,TDZ)—— 从作用域开始到变量声明语句之间的区域,访问变量会直接报错。

    示例:

    console.log(y); // ReferenceError: Cannot access 'y' before initialization(TDZ 报错)
    let y = 5;
    console.log(y); // 5
    

    ✅ 好处:避免 “变量声明前使用” 的逻辑错误,强制开发者按顺序编写代码。

3. 重复声明
  • var:允许重复声明(覆盖原有变量)同一作用域内多次用 var 声明同一变量,不会报错,后续声明会覆盖前面的赋值。

    示例:

    var a = 10;
    var a = 20; // 允许重复声明,覆盖原有值
    console.log(a); // 20
    
  • let:不允许重复声明(同一作用域内报错)同一作用域内用 let 重复声明同一变量,会直接抛出语法错误(SyntaxError),无论之前是用 var 还是 let 声明的。

    示例:

    let b = 10;
    let b = 20; // SyntaxError: Identifier 'b' has already been declared
    
    var c = 30;
    let c = 40; // 同样报错(同一作用域内重复声明)
    

    ✅ 好处:避免意外覆盖变量,减少代码逻辑错误。

4. 全局对象属性
  • var:全局声明会成为全局对象的属性在全局作用域(函数外)用 var 声明的变量,会成为全局对象(浏览器中是 window,Node.js 中是 global)的属性,可通过全局对象访问。

    示例(浏览器环境):

    var globalVar = "hello";
    console.log(window.globalVar); // "hello"(可通过 window 访问)
    
  • let:全局声明不会成为全局对象的属性在全局作用域用 let 声明的变量,不会挂载到全局对象上,仅在全局作用域内有效,无法通过全局对象访问。

    示例(浏览器环境):

    let globalLet = "world";
    console.log(window.globalLet); // undefined(不会成为 window 属性)
    

    ✅ 好处:避免污染全局对象,减少全局变量冲突。

5. 循环中的表现(闭包场景)

这是 var 和 let 最常用的差异场景,尤其在循环中创建闭包时(如 setTimeout 回调)。

  • var:循环变量泄露 + 闭包共享同一变量var 声明的循环变量是函数 / 全局作用域,所有循环内的闭包都会共享同一个变量(循环结束后变量的值是最终值),导致回调执行时拿到的都是最后一个值。

    示例(经典坑):

    for (var i = 0; i < 3; i++) {
      setTimeout(() => {
        console.log(i); // 输出 3、3、3(所有回调共享同一个 i,循环结束后 i=3)
      }, 100);
    }
    console.log(i); // 3(循环变量泄露为全局变量)
    
  • let:循环变量为块级作用域 + 每次循环创建新变量let 声明的循环变量是块级作用域,且 JavaScript 引擎会为每次循环创建一个新的变量副本,闭包捕获的是每次循环的独立变量,因此回调执行时能拿到对应循环的正确值。

    示例(符合预期):

    for (let i = 0; i < 3; i++) {
      setTimeout(() => {
        console.log(i); // 输出 0、1、2(每次循环的 i 是独立的)
      }, 100);
    }
    console.log(i); // ReferenceError: i is not defined(无变量泄露)
    

    ✅ 好处:无需手动创建闭包(如 IIFE)来保存循环变量,代码更简洁、符合直觉。

三、使用建议

  1. 优先使用 letlet 解决了 var 的三大问题(作用域混乱、重复声明、变量提升导致的逻辑错误),代码更安全、可维护。
  2. 避免使用 var:除非需要兼容非常老旧的浏览器(如 IE11 以下),否则不建议用 var
  3. 补充:constconst 与 let 特性一致(块级作用域、无重复声明、TDZ),仅多了 “声明时必须赋值且不可重新赋值” 的限制,适合声明常量(如配置、固定值)。

总结

var 和 let 的核心差异源于作用域模型的升级:let 引入块级作用域,修复了 var 导致的变量泄露、重复声明、闭包异常等问题,是 ES6 后推荐的变量声明方式。记住一句话:ES6 后,用 let 声明变量,用 const 声明常量,忘掉 var

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

canjun_wen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值