let 和 const 声明的变量会提升吗?一文详解 JavaScript 中的变量提升和函数提升

通常在直觉上我们会认为代码执行时是从上到下按顺序执行的,但在 JavaScript 中(就同步代码而言),这并不完全正确。

小二,上栗子!

实例 1:

a = 2;
var a;
console.log(a); // ?

如果实例 1 的代码是自上而下执行的话,那么 var aa = 2之后,应该是变量被重新赋值为undefined了。但输出的结果其实是 2。

实例 2:

console.log(a); // ?
var a = 2;

根据上一个例子的代码的表现,如果代码并不是完全自上而下执行的,那实例 2 代码里会输出 2,还是会报错呢。实际上输出的是 undefined

哦豁,到底发生了什么?

一、预解析

JavaScript 引擎在执行任何代码片段(例如函数调用)之前,会先对其中所有变量(包括函数)声明进行处理,这是一个预解析的过程。

1. 变量提升

JavaScript 会将var a = 2;看成是两个操作:var a;a = 2;。第一个声明是预解析阶段进行的,第二个赋值操作留在原地等待执行阶段。

上面实例 1 的代码可以认为被处理成如下形式:

// 预解析阶段
var a; 
//----------------
// 执行阶段
a = 2; 
console.log(a); // 2

实例 2 的代码可以认为被处理成如下形式:

// 预解析阶段
var a;
//----------------
// 执行阶段
console.log(a); // undefined
a = 2;

这样来看,代码的执行顺序就变得正常了。预解析的过程好像是变量的声明被“移动”到顶部,这个过程就叫作提升


2. 函数提升

函数声明同样也会被提升,但是跟变量的提升行为有所不同。

实例 3:

fn(); // 输出 undefined

function fn() {
    console.log(a);
    var a = 1;
}

在实例 3 中,在函数声明之前就可以正常调用函数,因为整个函数体都被提升了。值得注意的是,函数内也声明了一个变量 a ,那这个变量会被提升到哪里呢,这就跟作用域有关系了。

每个作用域都会发生提升行为,且只提升到当前作用域的顶部。在函数被调用时,才会对函数内部代码进行预解析。因此实例 3 的代码可以等同于如下形式:

function fn() {
    var a;
    console.log(a); 
    a = 1;
}

fn(); // 输出 undefined

另外,函数表达式的提升行为,是跟普通变量一样的。

实例 4:

fn(); // 报错,只提升了 var fn;  不能对 undefined 进行函数调用

var fn = function() {
    console.log("fn");
}

3. 函数优先

我们知道变量声明和函数声明都会被提升,那么在重复声明的情况下,JavaScript 引擎会怎么处理呢?

预解析过程中,每遇到一个 var 关键字的变量声明,首先会查询当前作用域之前是否已经有了该名称的变量,如果是,则会忽略该声明;如果没有则把该变量声明提升。

所以需要注意的是,预解析时函数首先被提升,然后才到变量。

实例 5:

fn(); // 1
var fn;

function fn() {
    console.log(1);
}

fn = function() {
    console.log(2);
}
fn(); // 2

实例 5 中,虽然 var fn; 出现在 function fn (){...} 之前,但因为首先提升函数,而同名的 var 声明就被忽略了。


尽管同名的 var 声明会被忽略掉,但是后出现的函数声明是能够覆盖前面的。

实例 6:

fn(); // 3
function fn() {
    console.log(1);
}

var fn = function() {
    console.log(2);
}

function fn() {
    console.log(3);
}

虽然这些看起来似乎都是并没有什么用的理论,一般也没谁这么写,但是至少说明了在同一个作用域内重复声明是非常糟糕的,会导致各种莫名其妙的问题,可见良好的编程习惯是多么重要!

二、暂时性死区

前面提到的种种提升行为,并没有出现 letconst 关键字。因为,letconst 并不会表现出变量提升的现象。

ES6 明确规定,代码块({})中如果出现 let 和 const 声明的变量,这些变量的作用域会被限制在代码块内,也就是块级作用域

实例 7:

var a = 1;
if(true){
	a = 2;
    console.log(a); // ?
	let a;
}

实例 7 代码中,在a = 2这里就已经报错:Cannot access 'a' before initialization,意思是无法在初始化之前访问 a,这是为什么呢?

在预解析的时候,JavaScript 引擎当然也会注意到 let 和 const 声明的变量,因为实例 7 的块级作用域中存在变量 a ,便不会继续去外部作用域查找变量。

严格来说, let 和 const 也会有类似“提升“的行为,但跟 var 不同的是,提升的时候变量值并不会默认赋值为 undefined,并且会禁止在声明之前使用这些变量,这就是所谓的暂时性死区

实例 8:

var a = 1;

if(true){
    // 死区开始--------------------------
    // 访问 a 都会报错,不能在声明之前使用
    a = 2;
    console.log(a);
    // 死区结束--------------------------
    let a;
    console.log(a); // undefined
    
    a = 3;
    console.log(a); // 3
}

暂时性死区的设计,也是为了提倡大家先声明,后使用,养成良好的编程习惯。同时推荐大家不管是用哪种声明方式,最好都先声明然后再使用,才能避免变量提升现象给代码带来的负面影响。

暂时性死区是 let 和 const 共同的特性,为了方便起见,上面的例子都只用了 let 关键字。

三、总结

  1. var声明的变量,只提升声明,赋值操作留在原地。
  2. 函数声明提升整个函数体,函数表达式的提升行为和变量一致。
  3. 同作用域中,如果函数声明和 var 声明同名,只提升函数声明,忽略 var声明。
  4. letconst 有暂时性死区,必须先声明后使用。
- END -

感谢你花费宝贵的时间阅读本文,文章在撰写过程中难免有疏漏和错误,欢迎你在下方留言指出文章的不足之处;如果觉得这篇文章对你有用,也欢迎你点赞和留下你的评论哦。

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值