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声明会检查是否有重名。