JavaScript 中 var、let、const 的区别与最佳实践

作者:青柠

时间:2026-10-31

浏览:597

本文深入解析 JavaScript 中 var、let、const 三种变量声明方式的核心区别。详细探讨变量提升(Hoisting)机制、暂时性死区(TDZ)原理及块级作用域特性,结合经典面试题剖析常见陷阱。最后给出 ES6 环境下的变量声明最佳实践建议,帮助开发者编写更安全、规范的代码。

文章目录

简介

var, let and const是JavaScript中三种定义变量的方式,它们之间有什么区别呢?这是前端面试中常见的一道题,今天我们来一文说透它。 let和const区别不大,主要是const声明的是常量,不可修改,而let声明的变量是可修改的。所以我们重点放在var和let上。

变量初始化

在声明变量的同时为其赋予初始值,这一过程被称为初始化。

  • 使用 var 和 let 声明变量时,可以不立即赋值,此时变量的默认值为 undefined。
  • 相比之下,使用 const 声明变量时必须同时提供初始值,否则程序会抛出语法错误。
// `var`和`let`声明的变量可以不赋值,此时变量的值为`undefined`。
var num; // num的值是undefined
num = 1; // num的值是1
let str; // str的值是undefined
str = 'hello'; // str的值是'hello' 
// `const`声明的变量必须赋值,否则会报错。
const a; // SyntaxError: Missing initializer in const declaration

变量提升 - Hoisting

“Hoisting”一词中文通常译为“提升”,指的是变量的声明会被移动到其所在作用域的顶部,但需要注意的是,只有声明会被提升,赋值操作不会。

  • 使用 var 声明的变量,其声明部分会被提升至当前作用域的顶部。
  • 而使用 let 和 const 声明的变量则不会发生提升现象(注:关于此点的具体行为存在不同解读,建议参考 MDN 官方文档)。
  • 提升机制仅针对变量声明,不涉及赋值操作。

当 在全局作用域中声明时,其声明会被提升至全局作用域的顶部。

console.log(name); // undefined
var name = 'Philip';

上述代码的执行逻辑等价于以下形式:

var name; // `var`声明的变量会被提升到其作用域顶部。
console.log(name); // undefined
name = 'Philip';

若 在函数作用域内声明,其声明同样会被提升至该函数作用域的顶部。

function printName() {
  console.log(name); // undefined
  var name = 'Philip';
}
printName();

上述代码的执行逻辑等价于以下形式:

function printName() {
  var name; // `var`声明的变量会被提升到其作用域顶部。
  console.log(name); // undefined
  name = 'Philip';
}
printName();

使用 和 声明的变量不会发生提升。

由于 let 和 const 不会提升,因此尝试在声明前访问它们会导致错误。

console.log(num); // ReferenceError: Cannot access 'num' before initialization
const num = 1;

此前曾提及,关于 let 和 const 是否被提升存在争议。

  • 一种观点认为,let 和 const 不会被提升,因此在声明前访问会直接报错。
  • 另一种观点则认为,let 和 const 会被提升,但在声明前的访问会抛出 Temporal Dead Zone 错误。

参考以下代码示例:

const x = 1;
{
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
  const x = 2;
}

该代码会抛出错误。然而,如果将 {} 块内的 const x = 2; 注释掉,代码即可正常运行。如果 const x = 2 真的没有被提升,那么 console.log(x) 应该能访问到全局的 const x = 1 而不会报错。反之,正是因为 const x = 2 发生了提升,导致 console.log(x) 访问的是提升后的 x,而此时 x 尚未初始化,从而引发错误。

提升机制仅针对变量声明,不包括赋值操作。

以下代码会报错,因为 `x = 1` 属于赋值操作而非声明,因此不会触发提升。(注意:如果变量声明前未添加 var, let 或 const,则会意外创建一个全局变量。)

console.log(x); // ReferenceError: x is not defined
x = 1;

当存在同名函数和变量时,提升后变量声明会位于函数声明之前(即函数声明会覆盖变量声明)。

在以下代码中,存在一个同名函数和变量。

console.log(foo); // [Function: foo], not undefined.
function foo() {
  console.log('function foo');
}
var foo = 1;

提升后的代码逻辑如下:

var foo;
function foo() {
  console.log('function foo');
}
console.log(foo);
foo = 1;

面试题

通过几道经典面试题来检验对提升机制的理解,请判断以下代码的输出结果:

  • 第一题
a = 2;
var a;
console.log(a); // 2

解决 提升问题的方法很简单,只需按照提升规则重写代码即可,上述代码等价于如下形式,结果一目了然。

var a;
a = 2;
console.log(a); // 2
  • 第二题
var a = true;
foo();
function foo() {
  if (a) {
    var a = 10;
  }
  console.log(a);
}

只要函数内部存在 var 声明的变量,所有全局声明的 var 变量都会被忽略。上述代码提升后等价于如下形式(注意 function 也会发生提升),函数内部的 声明永远会覆盖全局的 var。

var a = true;
function foo() {
  var a; // value of a is `undefined`
  if (a) {
    a = 10; // never executed.
  }
  console.log(a);
}
foo();
  • 第三题
function fn() {
  console.log(typeof foo);
  var foo = 'variable';
  function foo() {
    return 'function';
  }
  console.log(typeof foo);
}
fn();

同样地,此类题目的解法是依据提升规则重写代码,上述代码提升后等价于如下形式:

function fn() {
  var foo;
  function foo() {
    return 'function';
  }
  console.log(typeof foo);
  foo = 'variable';
  console.log(typeof foo);
}
fn();

因此,输出结果分别为 function 和 string。

变量的作用域

  • 使用 var 声明的变量仅具备两种作用域:全局作用域和函数作用域(不具备块级作用域)。
  • 使用 let 和 const 声明的变量具备三种作用域:全局作用域、函数作用域以及块级作用域。
  • 使用 var 声明的全局变量会挂载到 window 对象上,而 let 和 const 声明的全局变量则不会。
  • let 和 const 存在“暂时性死区”,而 var 没有此概念。

面试题

第一题

以下代码的输出结果是什么?

let x = 1;
{
  let x = 2;
}
console.log(x);

答案:1。因为 let 具备块级作用域,所以 let x = 2 仅在 {} 代码块内有效。

第二题

以下代码的输出结果是什么?

var x = 1;
{
  var x = 2;
}
console.log(x);

答案:2。因为 var 不具备块级作用域,所以 var x = 2 会覆盖外部的 var x = 1。

第三题

以下代码的输出结果是什么?

let name = 'zdd';
{
  console.log(name); 
  let name = 'Philip';
}

答案:ReferenceError: Cannot access 'name' before initialization。因为 let 具备块级作用域,所以 console.log(name); 访问的是 let name = 'Philip'; 之前的 name,而此时 name 尚未初始化,处于暂时性死区中,因此报错。

第四题

以下代码的输出结果是什么?

'use strict';
{
  function foo() {
    console.log('foo');
  }
}
foo();

答案:ReferenceError: foo is not defined。因为 foo 是在块级作用域内声明的,所以在外部无法访问。但如果移除 'use strict'; 严格模式限制,代码即可正常运行,因为在非严格模式下,函数声明会被提升至全局作用域。

第五题

以下代码的输出结果是什么?

(() => {
  let x;
  let y;
  try {
    throw new Error();
  } catch (x) {
    x = 1;
    y = 2;
    console.log(x);
  }
  console.log(x);
  console.log(y);
})();

答案:1 undefined 2。因为 catch 块中的 x 是一个新变量,并非外部的 x,所以 x = 1 仅会修改 catch 块内的 x,而不会影响外部的 x。而 y = 2 并非 catch 的参数,仅在 catch 块内赋值,因此会改变外部的 y。

暂时性死区 - Temporal Dead Zone

TDZ 即 Temporal Dead Zone(中文名为暂时性死区),是指 let 和 const 声明的变量在其作用域开始到变量实际声明之间的这段区域。在暂时性死区内无法访问变量,否则会导致错误。

function foo() {
  console.log(b); // ReferenceError: Cannot access 'b' before initialization
  let a = 1;
  const b = 2;
}
foo();

对于上述代码,常量 b 的暂时性死区从函数第一行开始,直到 b 声明结束。而 console.log(b); 这句代码恰恰在暂时性死区内访问了 b,因此会报错。

面试题

以下代码的输出结果是什么?

function foo() {
  console.log(typeof bar);
  const bar = 1;
}
foo();

答案: ReferenceError: Cannot access 'bar' before initialization。因为 console.log(typeof bar); 这句代码在 bar 的暂时性死区内访问了 bar,所以会报错。由此可见,即使是 typeof 这种通常不会报错的操作符,也无法规避暂时性死区。

如果移除 const bar = 1;,代码将不再报错。typeof 操作符对于未声明的变量不会报错,而是返回 undefined。

function foo() {
  console.log(typeof bar); // 输出undefined
}

重新声明- Redeclaration

  • 使用 var 声明的变量允许被重复声明,后声明的会覆盖先声明的。
  • 使用 let 和 const 声明的变量不允许被重复声明。

面试题

查看几道面试题,判断以下代码的输出结果:

  • 第一题
var a = 1;
function foo() {
  var a = 2;
  {
    var a = 3;
    console.log(a);
  }
  console.log(a);
}
foo();
console.log(a);

答案:3 3 1。此题主要考察两个知识点:

  • 使用 var 声明的变量没有块级作用域。
  • 使用 var 声明的变量可以被重复声明,后声明的会覆盖先声明的。 因此 var a = 3 会覆盖外部的 var a = 2,但 var a = 2 不会覆盖最外层的 var a = 1。这是因为 var 仅具备函数作用域。

上述代码提升后等价于如下形式:

var a;
a = 1;
function foo() {
  var a;
  var a; // redeclaration
  a = 2;
  {
    a = 3;
    console.log(a);
  }
  console.log(a);
}
foo();
console.log(a);

注意:面试题中凡是用 {} 包裹 var 的通常都是障眼法,var 并不具备块级作用域。

第二题

这道题较为简单,考察的是 let 的块级作用域,代码输出结果为 2, 1。因为 let 具备块级作用域,let a = 2 仅在 {} 代码块内有效。

function foo() {
  let a = 1;
  {
    let a = 2;
    console.log(a);
  }
  console.log(a);
}
foo();

意外的全局变量

如果在声明变量时忘记了书写 var, let 或者 const,那么该变量将成为所谓的“Accidental Global Variables”,意为“意外的全局变量”。

function f1() {
  b = 2; // accident global variable
}
f1();
console.log(b); // 2

面试题

以下代码的输出结果是什么?

for (var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  })
}

答案:3 3 3。 因为 var 不具备块级作用域,所以 setTimeout 内的 i 均指向同一个 i。而 setTimeout 是异步操作,其回调函数代码需先进入宏任务队列,待 for 循环结束后才能执行,此时 i 的值已是 3。关于此题的详细解释,请参阅相关文章。

最佳实践

  • 如今 ES6 已广泛普及,在业务代码中基本不再需要使用 var,var 目前仅见于 JS 框架或底层工具库中。

  • 对于 let 和 const,建议优先使用 const,仅在确实需要修改变量值的情况下才使用 let。

  • 经典的 `for` 循环应使用 let,因为循环变量在迭代过程中会被修改。

    for (let i = 0; i < 5; i++) {
      console.log(i);
    }
  • 对于 for...in 和 for...of 循环,应使用 const,因为循环变量在迭代过程中不会被修改。

    const arr = [1, 2, 3];
    for (const item of arr) {
      console.log(item);
    }
    const obj = {a: 1, b: 2};
    for (const key in obj) {
      console.log(key);
    }

至此,关于 JavaScript 中 var、let、const 的文章介绍完毕,更多相关 js var let const 内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章