5分钟告诉你let和const也有“变量提升”

659 阅读3分钟

5分钟告诉你let和const也有“变量提升”

话不多说,提到var、let、const三者的区别,大家第一个想到的肯定是“只有var存在变量提升”。事实真的是这样吗? 先看两个例子

console.log(1)  // 打印undefinde
var a = 1  

var声明的变量有变量提升,没问题

console.log(b); // Cannot access 'b' before initialization 
let b = 1

let暂时性死区,也没问题

let b = 1
function fn() {
    console.log(b); 
    let b = 2
}
fn() // Cannot access 'b' before initialization at fn

js代码由上往下执行,这里为什么无法访问外部变量b=1的值,而是表现出暂时性死区的特性呢?

变量生命周期

首先我们要知道生命周期的概念。 每一个变量的创建都有一个生命周期:即声明,初始化,赋值。

声明

在作用域内声明这个变量,此时变量已被标记为存在。

初始化

在内存中为这个变量开辟一块空间,此时由于没有赋值,所以会初始化为undefined。

赋值

将值赋给这个变量。

原因

var: 在执行上下文执行之前的预解析阶段,会有声明提升,和初始化提升,这时js会把var存放在变量环境中(你可以简单理解为一个存放变量的地方,函数声明也会放在这个地方),且值为undefined,因此我们可以提前访问,结果为undefined。

let: 在执行上下文执行之前的预解析阶段,仅有声明提升,不会给它初始化,更不会给他赋值,此时存储中有这个变量,但我们无法访问。(let和const存放在词法环境中,你可以简单理解为另一个地方)这也就是所说的暂时性死区当执行到真正的赋值语句的时候,才会执行初始化操作并赋值。

const:let的创建顺序基本一致,仅有 声明提升,但非常重要的区别是const本身不存在赋值这一个概念,当执行到 const a = 2时,是直接将const初始化为2。也就是我们所说的const是常量。因为它根本无法赋值,也因此在声明时必须给它一个值。(相当于Promise内部状态只能修改一次,因为Promise源码里后续也没有为状态赋值的操作。) 而对于引用数据类型来说,const初始化的是一个栈内存里分配的指针。你虽然无法修改指针的指向,但可以修改指针指向的堆里面的内容,也就是我们所说的cosnt声明的引用数据类型可以更改

const a // 直接提示错误 不用运行
console.log(a)
const a = 1
a = 2
console.log(a) // 报错 const常量不可改
const a = { name: 'shm' }
console.log(a); // { name: 'shm' }
a.name = 'zs'
console.log(a) // { name: 'zs' }  指针不变,堆里的内容可变

function:存在声明提升初始化提升赋值提升。

总结

函数:声明提升,初始化提升,赋值提升。

var:声明提升,初始化提升。执行时赋值。

let:声明提升,且会检查重名。执行时初始化并赋值。

const:声明提升、执行时初始化,内部无赋值过程,因此创建时必须给值!

题目

var fn
function fn() { }
console.log(fn); // 打印函数
-----------------------------------------------------
答案:函数比var多一个赋值提升,上面代码可以写成
var fn 
fn = function () { } // 此时fn已赋值,且var fn没有重新赋值
var fn
console.log(fn) // 是函数

var fn = 1
function fn() { }
console.log(fn()); // 报错:fn不是一个函数
----------------------------------------------------------
答案:函数fn先提升,var后提升,之后fn重新赋值为1,不再是一个函数。上面代码可以写成
var fn = function () { }
fn = 1
console.log(fn()) // fn为数字1,不再是函数

let fn = 1
function fn() { }
console.log(fn()); // 报错
----------------------------------------------------------
答案:依然是函数先提升,但let声明会检查是否有重名。