JavaScript 声明提升

84 篇文章 0 订阅

JavaScript 声明提升

前言

JS 引擎在在执行 JavaScript 前会有预编译,JS 预编译时会把 JS 中的 var 变量声明和 function 函数声明提前至当前作用域的最前面。
传统艺能,先来看一个题,看看自己是否完全掌握了声明提升:
以下代码的正确输出是?(答案在最好,如果你还不是很清楚的话,建议继续阅读下去。)

var foo = function () {
    console.log(100);
}
function foo() {
    console.log(200);
}
foo();

变量声明提升

变量声明提升是指先调用变量,后声明变量,并不会抛出异常的JS机制

这是因为 声明提前是发生在代码开始运行之前var 声明会提升,let 和 const 声明不提升

JavaScript 的函数作用是指在函数内声明的所有变量在函数体内始终是有定义的,也就是说变量在声明之前已经可用,所有这特性称为 声明提升,即JavaScript函数里的所有声明(只是声明,但不涉及赋值)都被提前到函数体的顶部,而变量赋值操作留在原来的位置

例如

console.log(i);// 声明提升,输出undfined
var i = 1;

这段代码等价于

var i;
console.log(i); // undefined
i = 1; // 初始化不提升

总结就是:声明提升,赋值不提升

函数声明优先

在代码中存在同名变量和函数的声明,那么两者均会提升,最后函数声明会覆盖变量声明。

// 输出:foo () {}
console.log(foo);
var foo = 100;
function foo () {}

以上代码相当于:

function foo() {};
var foo; // 变量提升被函数声明提升覆盖,变量重复声明无效
console.log(foo); // foo() {}
foo = 100;

但是在输出语句 console.log 前如果有赋值语句,则 foo 为变量的值。

var foo = 100;
function foo () {};
console.log(foo); // 100

注意,变量的重复声明是无用的,但函数的重复声明会覆盖前面的声明。

  • 变量的重复声明无用
var foo = 100;
var foo;
console.log(foo); // 100
  • 函数的重复声明会覆盖前面的声明
foo(); // 200
function foo () {
    console.log(100);
}
function foo () {
    console.log(200);
}
  • 函数声明提升优先于变量声明提升
foo(); // 200
var foo = 100;
function foo() {
    console.log(200);
}

以上代码相当于:

function foo() {
    console.log(200);
}
var foo;
foo(); // 200,函数声明提升优先于变量声明提升,变量声明无效
foo = 100;

JS如何支持块级作用域

那么问题来了,ES6 是如何做到既要支持变量提升的特性,又要支持块级作用域的呢?下面从执行上下文的角度来看看原因。

JavaScript 引擎是通过变量环境实现函数级作用域的,那么 ES6 又是如何在函数级作用域的基础之上,实现对块级作用域的支持呢?先看下面这段代码:

function fn(){
    var a = 1
    let b = 2
    {
      let b = 3
      var c = 4
      let d = 5
      console.log(a)
      console.log(b)
      console.log(d)
    }
    console.log(b) 
    console.log(c)
}   
fn()

当这段代码执行时,JavaScript 引擎会先对其进行编译并创建执行上下文,然后再按照顺序执行代码。let 关键字会创建块级作用域,那么 let 关键字是如何影响执行上下文的呢?

(1)创建执行上下文

创建的执行上下文如图所示:
在这里插入图片描述
通过上图可知:

  • 通过 var 声明的变量,在编译阶段会被存放到变量环境中。
  • 通过 let 声明的变量,在编译阶段会被存放到词法环境中。
  • 在函数作用域内部,通过 let 声明的变量并没有被存放到词法环境中。
(2)执行代码

当执行到代码块中时,变量环境中 a 的值已经被设置成了 1,词法环境中 b 的值已经被设置成了 2,这时函数的执行上下文如图所示:
在这里插入图片描述
可以看到,当进入函数的作用域块时,作用域块中通过 let 声明的变量,会被存放在词法环境的一个单独的区域中,这个区域中的变量并不影响作用域块外面的变量,比如在作用域外面声明了变量 b,在该作用域块内部也声明了变量 b,当执行到作用域内部时,它们都是独立的存在。

其实,在词法环境内部,维护了一个栈结构,栈底是函数最外层的变量,进入一个作用域块后,就会把该作用域块内部的变量压到栈顶;当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构。这里的变量是指通过 let 或者 const 声明的变量。

接下来,当执行到作用域块中的console.log(a)时,就需要在词法环境和变量环境中查找变量 a 的值了,查找方式:沿着词法环境的栈顶向下查询,如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,如果没有查找到,那么继续在变量环境中查找。这样变量查找就完成了:
在这里插入图片描述

当作用域块执行结束之后,其内部定义的变量就会从词法环境的栈顶弹出,最终执行上下文如图所示:

在这里插入图片描述
块级作用域就是通过词法环境的栈结构来实现的,而变量提升是通过变量环境来实现,通过这两者的结合,JavaScript 引擎就同时支持了变量提升和块级作用域。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三季人 G

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值