找回密码
 立即注册
首页 业界区 业界 一文搞懂javascript中的var、let、const

一文搞懂javascript中的var、let、const

姚梨素 2025-6-29 20:42:08
简介

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

声明变量的同时为其赋值叫做初始化。

  • var和let声明的变量都可以不赋值,此时变量的值为undefined。
  • const声明的变量必须赋值,否则会报错。
  1. // `var`和`let`声明的变量可以不赋值,此时变量的值为`undefined`。
  2. var num; // num的值是undefined
  3. num = 1; // num的值是1
  4. let str; // str的值是undefined
  5. str = 'hello'; // str的值是'hello'
复制代码
  1. // `const`声明的变量必须赋值,否则会报错。
  2. const a; // SyntaxError: Missing initializer in const declaration
复制代码
变量提升 - Hoisting

Hoisting这个词中文译为提升,就是将变量的声明提升到其作用域的顶部,注意提升的是声明,而不是赋值。

  • var声明的变量会被提升至其作用域顶部。
  • let和const声明的变量不会被提升。(注意这个说法有争议,详见MDN)
  • 提升只针对变量声明,不包括赋值。
如果var是在全局作用域声明的,那么它会被提升到全局作用域的顶部。
  1. console.log(name); // undefined
  2. var name = 'Philip';
复制代码
以上代码等价于:
  1. var name; // `var`声明的变量会被提升到其作用域顶部。
  2. console.log(name); // undefined
  3. name = 'Philip';
复制代码
如果var是在函数作用域声明的,那么它会被提升到函数作用域的顶部。
  1. function printName() {
  2.   console.log(name); // undefined
  3.   var name = 'Philip';
  4. }
  5. printName();
复制代码
以上代码等价于:
  1. function printName() {
  2.   var name; // `var`声明的变量会被提升到其作用域顶部。
  3.   console.log(name); // undefined
  4.   name = 'Philip';
  5. }
  6. printName();
复制代码
let和const声明的变量不会被提升。

对于let和const,它们不会被提升,所以下面代码会报错。
  1. console.log(num); // ReferenceError: Cannot access 'num' before initialization
  2. const num = 1;
复制代码
前面说过,关于let和const是否被提升有争议。

  • 一种说法是let和const不会被提升,所以在声明之前访问会报错。
  • 另一种说法是let和const会被提升,但是在声明之前访问会抛出Temporal Dead Zone错误。
比如下面的代码:
  1. const x = 1;
  2. {
  3.   console.log(x); // ReferenceError: Cannot access 'x' before initialization
  4.   const x = 2;
  5. }
复制代码
这段代码会报错,但是如果我们把{}内的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,那么其实产生的是一个意外的全局变量。)
  1. console.log(x); // ReferenceError: x is not defined
  2. x = 1;
复制代码
如果有同名函数和变量,那么提升后,变量位于函数之前(或者说函数会覆盖变量)。

以下代码中有一个同名的函数和变量。
  1. console.log(foo); // [Function: foo], not undefined.
  2. function foo() {
  3.   console.log('function foo');
  4. }
  5. var foo = 1;
复制代码
提升后代码如下:
  1. var foo;
  2. function foo() {
  3.   console.log('function foo');
  4. }
  5. console.log(foo);
  6. foo = 1;
复制代码
面试题

看几道面试题,以下几段代码输出什么?

  • 第一题
  1. a = 2;
  2. var a;
  3. console.log(a); // 2
复制代码
解决var提升的问题很简单,就是按照提升规则将代码重写一下,上面的代码等价于如下代码,结果一目了然。
  1. var a;
  2. a = 2;
  3. console.log(a); // 2
复制代码

  • 第二题
  1. var a = true;
  2. foo();
  3. function foo() {
  4.   if (a) {
  5.     var a = 10;
  6.   }
  7.   console.log(a);
  8. }
复制代码
只要函数内部有var声明的变量,那么所有全局声明的var变量都会被忽略,以上代码提升后等价于如下代码(注意function也有提升),函数内部的var永远会覆盖全局的var。
  1. var a = true;
  2. function foo() {
  3.   var a; // value of a is `undefined`
  4.   if (a) {
  5.     a = 10; // never executed.
  6.   }
  7.   console.log(a);
  8. }
  9. foo();
复制代码

  • 第三题
  1. function fn() {
  2.   console.log(typeof foo);
  3.   var foo = 'variable';
  4.   function foo() {
  5.     return 'function';
  6.   }
  7.   console.log(typeof foo);
  8. }
  9. fn();
复制代码
还是那句话,此类题目的解法就是按照提升规则把代码重新写一遍,以上代码提升后等价于如下代码:
  1. function fn() {
  2.   var foo;
  3.   function foo() {
  4.     return 'function';
  5.   }
  6.   console.log(typeof foo);
  7.   
  8.   foo = 'variable';
  9.   console.log(typeof foo);
  10. }
  11. fn();
复制代码
所以输出结果是function和string。
变量的作用域


  • var声明的变量有只两种作用域:全局作用域和函数作用域。(没有块级作用域)
  • let和const声明的变量有三种作用域:全局作用域,函数作用域和块级作用域。
  • var声明的全局变量会挂载到window对象上,而let和const不会。
  • let和const有临时性死区,而var没有。
面试题

第一题

以下代码输出什么?
  1. let x = 1;
  2. {
  3.   let x = 2;
  4. }
  5. console.log(x);
复制代码
答案:1,因为let有块级作用域,所以let x = 2只在{}内有效。
第二题

以下代码输出什么?
  1. var x = 1;
  2. {
  3.   var x = 2;
  4. }
  5. console.log(x);
复制代码
答案:2,因为var没有块级作用域,所以var x = 2会覆盖外部的var x = 1。
第三题

以下代码输出什么?
  1. let name = 'zdd';
  2. {
  3.   console.log(name);
  4.   let name = 'Philip';
  5. }
复制代码
答案:ReferenceError: Cannot access 'name' before initialization。因为let有块级作用域,所以console.log(name);访问的是let name = 'Philip';之前的name,而此时name还没有被初始化,处于暂时性死区中,所以报错。
第四题

以下代码输出什么?
  1. 'use strict';
  2. {
  3.   function foo() {
  4.     console.log('foo');
  5.   }
  6. }
  7. foo();
复制代码
答案:ReferenceError: foo is not defined。因为foo是在块级作用域内声明的,所以在外部无法访问。但是如果我们把'use strict';去掉,那么代码就可以正常运行。因为在非严格模式下,函数声明会被提升到全局作用域。
第五题

以下代码输出什么?
  1. (() => {
  2.   let x;
  3.   let y;
  4.   try {
  5.     throw new Error();
  6.   } catch (x) {
  7.     x = 1;
  8.     y = 2;
  9.     console.log(x);
  10.   }
  11.   console.log(x);
  12.   console.log(y);
  13. })();
复制代码
答案: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声明的变量在其作用域开始到变量声明之间的这段区域。在暂时性死区内无法访问变量,访问会报错。
  1. function foo() {
  2.   console.log(b); // ReferenceError: Cannot access 'b' before initialization
  3.   let a = 1;
  4.   const b = 2;
  5. }
  6. foo();
复制代码
对于以上代码,常量b的暂时性死区开始于函数的第一行,终止于b的声明,而console.log(b);这句恰恰在暂时性死区内访问了b,所以会报错。
面试题

以下代码输出什么?
  1. function foo() {
  2.   console.log(typeof bar);
  3.   const bar = 1;
  4. }
  5. foo();
复制代码
答案:
ReferenceError: Cannot access 'bar' before initialization
因为console.log(typeof bar);这句在bar的暂时性死区内访问了bar,所以会报错。可以看到,即使强如typeof这种几乎不会报错的操作符也无法规避暂时性死区。
如果我们把const bar = 1;去掉,那么代码就不会报错。typeof操作符对于没有声明的变量不会报错,而是返回undefined。
  1. function foo() {
  2.   console.log(typeof bar); // 输出undefined
  3. }
复制代码
重新声明- Redeclaration


  • var声明的变量可以被重复声明,后声明的覆盖先声明的。
  • let和const声明的变量不可以被重复声明。
面试题

看几道面试题,以下几段代码输出什么?

  • 第一题
  1. var a = 1;
  2. function foo() {
  3.   var a = 2;
  4.   {
  5.     var a = 3;
  6.     console.log(a);
  7.   }
  8.   console.log(a);
  9. }
  10. foo();
  11. console.log(a);
复制代码
答案:3 3 1, 这个题主要考察两个知识点:

  • var声明的变量没有块级作用域。
  • var声明的变量可以被重复声明,后声明的会覆盖先声明的。
    所以var a = 3会覆盖外部的var a = 2,但是var a = 2不会覆盖最外面的var a = 1。因为var有函数作用域。
以上代码提升后等价于如下代码:
  1. var a;
  2. a = 1;
  3. function foo() {
  4.   var a;
  5.   var a; // redeclaration
  6.   a = 2;
  7.   {
  8.     a = 3;
  9.     console.log(a);
  10.   }
  11.   console.log(a);
  12. }
  13. foo();
  14. console.log(a);
复制代码
注意:面试题中凡事用{}包裹var的都是障眼法,var没有块级作用域。
第二题

这道题比较简单,考察的是let的块级作用域,代码输出2, 1。因为let有块级作用域。let a = 2只在{}内有效。
  1. function foo() {
  2.   let a = 1;
  3.   {
  4.     let a = 2;
  5.     console.log(a);
  6.   }
  7.   console.log(a);
  8. }
  9. foo();
复制代码
意外的全局变量

如果我们声明变量的时候忘记了写var, let或者const,那么这个变量就是所谓的Accidental Global Variables,意思是意外的全局变量。
  1. function f1() {
  2.   b = 2; // accident global variable
  3. }
  4. f1();
  5. console.log(b); // 2
复制代码
面试题

以下代码输出什么?
  1. for (var i = 0; i < 10; i++) {
  2.   setTimeout(() => {
  3.     console.log(i);
  4.   })
  5. }
复制代码
答案:3 3 3
因为var没有块级作用域,所以setTimeout内的i都是指向同一个i,而setTimeout是异步的,其回调函数代码需要先进入宏任务队列,待for循环结束后才能执行,此时i已经是3了。关于这道题的详细解释,请看这篇。
最佳实践


  • 如今ES6已经普及,对于业务代码来说,基本不需要使用var了,var目前只有JS框架或者底层工具库才会使用。
  • 对于let和const,优先使用const,只有在需要修改变量的情况下才使用let。
  • 经典for循环使用let,因为循环变量会被修改。
    1. for (let i = 0; i < 5; i++) {
    2.   console.log(i);
    3. }
    复制代码
  • for...in和for...of使用const,因为循环变量不会被修改。
    1. const arr = [1, 2, 3];
    2. for (const item of arr) {
    3.   console.log(item);
    4. }
    复制代码
    1. const obj = {a: 1, b: 2};
    2. for (const key in obj) {
    3.   console.log(key);
    4. }
    复制代码
祝大家编程愉快,如果觉得有用就点个关注,每篇文章都是纯古法手打,在AI大行其道的当下,认认真真写文章的人不多了,您的点赞转发评论就是对我最大的支持!
出处:http://www.cnblogs.com/graphics/本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册