let和const最全解析

107 阅读5分钟

参考链接阮一峰:ES6 let和const 光看文章没用。要输出,探讨。所以为了加强对let const的理解,记录下自己的学习过程

let和var的区别

let的基本用法应该都会,那么来看看let 和 之前var声明的变量之间有什么区别呢?

let不存在变量提升

先来看一个例子

console.log(a);
var a = 3

执行这段代码,会输出undefined。但是要彻底理解这段代码,是有点东西要讲的。那么我将从 执行上下文的角度,从变量对象的创建开始讲。 首先 将 globalContext压入 Execution Context Stack,进入执行上下文,先初始化variable Object,再 函数声明,变量声明(变量提升)。其中变量声明是不会覆盖同名的函数声明。 初始化作用域链,指定this为window,最后再开始执行全局代码。 所以在进入执行上下文的时候,就已经存在变量提升了,此时 a 已经声明,但是没有进行赋值为 3。 执行代码,打印 a 的值,就输出 undefined。 ps:其实这段代码也不是很复杂,如果给这段代码套上一个 函数作用域,那么就可以彻底串联起 词法作用域、 执行上下文栈、变量对象、作用域链 和 this的指向,这些基本的js问题了。这里不再展开。

所以var是存在 变量提升的。但是如果把这段代码改为

console.log(a);
let a = 3

这样的话,运行的时候就会报错,因为let是不存在变量提升的。 又会牵扯出一个新的问题,这时候对 a 进行报错,是因为存在 暂时性死区,并且let声明的变量是不放在全局作用域中的,而是放在脚本的作用域中。可以通过window.a 进行打印,打印的时候输出undefined。

同时,写的同时又想到了另一个问题。也是因为最近在学习this的指向的关系。 我把代码贴一下

let a = 1
function ap(){
    console.log(this.a);
}
ap()

这会输出啥? 第一反应是输出 a。但是打印输出的是undefined。为什么呢?理一下逻辑。 在 进入执行上下文的时候,给 ap这个函数的scope绑定了 块级作用域 和 全局作用域,其中,a由于let定义,所以实在Script作用域中,ap是函数声明,所以是在window上。那么运行ap()函数,函数的this是window,而window上是没有a的,那么返回undefined 可以看一下chrome调试的截图

image.png

let不允许重复声明

直接上代码

// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}

ES6同时还带来了块级作用域的概念

块级作用域

var tmp = new Date();

function f() {
  console.log(tmp);
  if (false) {
    var tmp = 'hello world';
  }
}

f(); // undefined

分析这段代码,第一反应是 if(false) 这个循环进不去。但是由于 if里面的tmp是由 var定义的,不会单独分开一个作用域,所以tmp这个变量存在变量提升,在打印的时候就 输出变量提升的 undefined了。

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

改用ES6的块级作用域,

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}

函数在块级作用域中进行声明 ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于let,在块级作用域之外不可引用。

const命令

const声明一个只读的常量。一旦声明,常量的值就不能改变。

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

const foo;
// SyntaxError: Missing initializer in const declaration

上面代码表示,对于const来说,只声明不赋值,就会报错。

const的作用域与let命令相同:只在声明所在的块级作用域内有效。

if (true) {
  const MAX = 5;
}

MAX // Uncaught ReferenceError: MAX is not defined

const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

if (true) {
  console.log(MAX); // ReferenceError
  const MAX = 5;
}

上面代码在常量MAX声明之前就调用,结果报错。

const声明的常量,也与let一样不可重复声明。

var message = "Hello!";
let age = 25;

// 以下两行都会报错
const message = "Goodbye!";
const age = 30;

本质

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined