varlet 的区别,实际上就是 ES3 定义变量 和 ES6 定义变量的区别

  • ES3定义变量的方式:

    • var
    • function
  • ES6定义变量的方式:

    • let
    • const
    • class
    • import

ES6 语法相对于 ES3 来说,最大的特点就是让 JS 变得更加严谨,告别 JS 松散的特点

思维导图

37.ES3 定义变量 和 ES6 定义变量的区别 - 图1

一、LETVAR 的区别

1、变量提升方面

  • let 不存在变量提升,所以变量只能在声明定义后使用
  • var 存在变量提升
  1. console.log(n); //=>undefined
  2. var n = 10;
  3. console.log(n); //=>10
  4. console.log(n); //=>Uncaught ReferenceError: Cannot access 'n' before initialization LET不存在变量提升,所以变量只能在声明定义后使用
  5. let n = 10;
  6. console.log(n); //=>10
  7. 复制代码

2、重复声明方面

  • let是不允许重复声明的

    • 在当前上下文中,不管用什么方式,只要声明了这个变量,都不能基于 let 重复声明了,会报错
    • 是否重新声明,并不是在代码执行阶段检测的,而是在词法解析的阶段检测的
  • var允许重复声明

    • 浏览器本身只识别一次,但不会报错
var n = 12;
var n = 13;
console.log(n); //=>13
let n = 12;
let n = 13;
console.log(n); //=>Uncaught SyntaxError: Identifier 'n' has already been declared
var n = 12;
let n = 13; //=>Uncaught SyntaxError: Identifier 'n' has already been declared
//=>是否重新声明,并不是在代码执行阶段检测的,而是在词法解析阶段检测的(词法解析阶段类似于变量提升,在代码还没有执行之前,就发生了,一旦发现有词法错误 SyntaxError ,当前代码都不会再执行了)
console.log('OK');
var n = 12;
let n = 13; //=>报错 但是OK都没有被执行
复制代码

词法解析、词法错误、语法错误

  • 词法解析:词法解析阶段类似于变量提升,在代码还没有执行之前就发生了
  • 词法错误SyntaxError :在词法解析阶段报错,代码还没有执行就报错,一旦发现有词法错误 SyntaxError ,当前代码不会在执行了
  • 语法错误ReferenceError:在代码执行阶段报错,报错前的代码会正常执行

3、全局对象GO方面

在全局上下文中

  • let 声明的变量仅仅是全局变量,和GO没什么关系
  • var 声明的变量即是全局变量,也相当于给GO(window)设置了一个属性,而且两者建立映射机制
var n = 10;
console.log(window.n); //=>10
let n = 10;
console.log(window.n); //=>undefined
复制代码

4、暂时性死区问题

  • let能解决基于typeof检测一个没有被声明过的变量结果是"undefined"的暂时性死区问题;
  • var 不能

浏览器有一个BUG(暂时性死区):基于typeof检测一个没有被声明过的变量,并不会报错,结果是"undefined";但是从正确角度来讲应该报错才是正常的!

console.log(typeof n); //=>"undefined"
//=> 如果这个变量在后面会用`LET`声明,则前面再基于`typeof`检测就会报错:不能在声明之前使用
console.log(typeof n); //=>Uncaught ReferenceError: Cannot access 'n' before initialization
let n = 10;
复制代码

5、块级作用域

  • let 在遇到除对象/函数等大括号以外的大括号(例如判断和循环)时,会形成新的块级作用域
  • var 没有块级作用域

二、LET 的块级作用域

在整个JS中,目前为止我们接触的上下文(作用域)只有两种

  • 全局上下文 EC(G)
  • 函数执行形成的私有上下文 EC(XX)

此时循环体或者判断体等,都不会单独形成私有的上下文,里面的变量都是所在上下文中的变量。

if (1 === 1) {
    var n = 10;
    console.log(n); //=>10
}
console.log(n); //=>10
for (var i = 0; i < 5; i++) {...}
console.log(i); //=>5
复制代码

但是在ES6中,提供了一个新的上下文(作用域)形式:块级作用域

  • 除对象/函数等大括号以外,如果在其余的大括号中(例如:判断和循环)出现 LET/CONST 等,则会把当前大括号包起来的部分形成一个独立的私有上下文,基于 LET/CONST创建的变量是当前块级作用域域中的私有变量;
if (1 === 1) {
    var n = 10; //=>n是全局变量
    let m = 20; //=>m是当前大括号包起来的 私有的 块级作用域中的 私有变量
    console.log(m); //=>20
}
console.log(n); //=>10
console.log(m); //=>Uncaught ReferenceError: m is not defined
// 类似于形成两个闭包(这里叫做私有的块级作用域)
{
    let x = 10,
        y = 20;
    console.log(x, y);
}
{
    let x = 100,
        y = 200;
    console.log(x, y);
}
for (let i = 0; i < 3; i++) {
    console.log(i); //=>0 1 2
}
console.log(i); //=>Uncaught ReferenceError: i is not defined
复制代码

循环中遇到let/const

  • 图解:37.ES3 定义变量 和 ES6 定义变量的区别 - 图2

  • 1、创建一个总的块级作用域(父作用域):用来控制循环一轮轮执行

  • 2、每一轮循环都会形成一个新的私有上下文(子作用域):

    • 用来实现循环中要处理的业务需求
    • 会把每一轮循环 i 的值,作为子作用域中的私有变量

和我们基于 VAR ,每一轮循环都创建一个闭包类似

三、LETCONST 的区别

  • CONST声明的变量,不能重新指向新的值(不能修改指针的指向);
let n = 10;
n = 20;
console.log(n); //=>20
const n = 10;
n = 20; //=>Uncaught TypeError: Assignment to constant variable.
console.log(n);
复制代码

一道经典面试题

const 定义的变量值,永远不能被修改了?

  • 答:不对
  • 1、const 不能更改指的是:这个变量不能在和其他值进行关联了,也就是不能修改 const 声明变量的指向
  • 2、但是可以在不改变指向的情况下,修改堆内存中的信息(这样也是把值更改了)
  • 3、所以:const 声明的变量,不能修改他的指针指向,但是可以改变其储存值的

例如下面这种情况:

const obj = {
    name: '金色小芝麻'
};
obj = [10, 20]; //=>Uncaught TypeError: Assignment to constant variable. 
//=> 它不能更改指的是:obj这个变量不能在和其它值进行关联了,也就是不能修改const声明变量的指向
obj.name = '哈哈';
console.log(obj);//=>{name: '哈哈'};
 //=>但是可以在不改变指向的情况下,修改堆内存中的信息(这样也是把值更改了),
 //=> 所以记住:const声明的变量,不能修改它的指针指向,但是可以改变其存储值的
 //=> 所以也就不能说const创建的是一个常量;