前端面试题...var let const 这三者的区别

2,327 阅读4分钟

一、区别

简述:

  1. var声明变量存在变量提升,let和const不存在变量提升
  2. let、const都是块级局部变量
  3. 同一作用域下let和const不能声明同名变量,而var可以

详述:

  1. var 定义变量,没有块的概念,可以跨块访问,但不能跨函数访问,不初始化会出现undefined,不会报错;有变量提升。
  2. let定义变量,只能在当前块级作用域里访问,也不能跨函数访问,对函数外部无影响;无变量提升。
  3. const定义常量,只能在当前块级作用域里访问,也不能跨函数访问,使用时必须初始化(即必须赋值),而且不能修改(引用数据类型,如指向的对象,即内存地址不能修改,但是能修改里面的属性);无变量提升。

二、const let var具体区别

  • const 在声明变量时必须初始化(声明变量的同时并赋值即初始化),var 和 let 则不用
// const 在声明变量时必须初始化(声明变量的同时并赋值即初始化)
    // 1、var
    var a;
    a = 1;
    console.log(a) // 可以打印出 1

    // 2、const
    const b;
    b = 2;
    console.log(b) // html:43 报错 Uncaught SyntaxError: Missing initializer in const declaration

    // 3、let
    let c;
    c = 3;
    console.log(c) // 可以打印出 3
  • 修改值(基本数据类型和引用数据类型)
修改基本数据类型值
    // 1、var
    var aa = 1;
    aa = 2;
    console.log(aa) // 打印出 2, 证明了可以修改

    // 2、let
    let bb = 1;
    bb = 2;
    console.log(bb) // 打印出 2, 证明了可以修改

    // 3、const
    const cc = 1;
    cc = 2;
    console.log(c) // 报错 Uncaught TypeError: Assignment to constant variable

-------------------------------------分割线--------------------------------------------------
12123123123131
修改引用(复杂)数据类型
    // # 修改引用数据类型
    const obj = {
      name: '张三',
      age: 18
    }
    obj.sex = '男'
    obj.name = '李四'
    console.log(obj) // 引用数据类型可以修改 打印出 {name: "李四", age: 18, sex: "男"}

    // 修改数组的元素
    const TEAM = ['1', '2', '3' , '4']
    TEAM.push('5')
    console.log(TEAM) // ["1", "2", "3", "4", "5"]
  • 是否存在变量提升
// 变量提升
    // 1、var
    aaa = 1;
    var aaa;
    console.log(aaa) // 可以打印出 1,证明有变量提升

    // 2、let
    bbb = 1;
    let bbb;
    console.log(bbb) // 报错 Uncaught ReferenceError: Cannot access 'bbb' before initialization

    // 3、const
    ccc = 1;
    const ccc; // 这一行就已经报错 Uncaught SyntaxError: Missing initializer in const declaration
    console.log(ccc)
  • 块级作用域

三、块级作用域

在ES6 之前, JavaScript中有三种作用域:

1. 全局作用域

2. 函数作用域

3. eval作用域

以上作用域内声明的变量或方法只在当前作用域内有效, 在其他作用域内引用则会返回 undefined;

而ES6则新增了一个作用域: 块级作用域

块级作用域可以简单理解为是: 包在大括号{}里面的内容, 它可以自成一个作用域, 但ES5中也有大括号, 可ES5中并没有块级作用域, 这时该怎样判断 {} 是否具有块级作用域的特点?

这时就得用到 letconst

因此, 可以将块级作用域理解为: 使用let和const声明的变量, 只在当前大阔号内生效, 由此构建出了 块级作用域 这么个东西.

这里的 "大括号内" 主要指的下面几种情况: 

// 条件语句
if () {}

// switch语句
switch () {}

// for / while循环语句
for () {}
while () {}

// try...catch语句
try () catch (err) {}

// 单大括号
{}

注意: 对象的大括号内不是一个块级作用域, 因为它里面不能直接声明变量; 

四、案例、应用

let非常适合用于 for循环内部的块级作用域。JS中的for循环体比较特殊,每次执行都是一个全新的独立的块作用域,用let声明的变量传入到 for循环体的作用域后,不会发生改变,不受外界的影响。看一个常见的面试题目:

for (var i = 0; i <10; i++) {  
  setTimeout(function() {  // 同步注册回调函数到 异步的 宏任务队列。
    console.log(i);        // 执行此代码时,同步代码for循环已经执行完成
  }, 0);
}
// 输出结果
1010// 这里面的知识点: JS的事件循环机制,setTimeout的机制等

如果把 var改成 let声明:

// i虽然在全局作用域声明,但是在for循环体局部作用域中使用的时候,变量会被固定,不受外界干扰。
for (let i = 0; i < 10; i++) { 
  setTimeout(function() {
    console.log(i);    //  i 是循环体内局部作用域,不受外界影响。
  }, 0);
}
// 输出结果:
0  1  2  3  4  5  6  7  8 9

案例

var btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
  btns[i].onclick = function () {
    console.log('第' + (i + 1) + '个被点击');
  };
}
输出结果:
点击哪一个button则会对应打印出console日志,如:第1个被点击了

ps:这个案例相当于在for循环中形成了闭包

对应的把 let 改成 var:

var btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
  (function a(i) {
    btns[i].onclick = function () {
      console.log('第' + (i + 1) + '个被点击');
    };
  })(i)
}