你需要知道的块级作用域

327 阅读4分钟

块作用域

之前我们了解了函数作用域,其也是javaScript中常见的作用域,也是最普遍的设计方法,但是其他的作用域也是存在的,其中就包括了块级作用域,甚至在某些方面,块作用域比函数作用域表现更好。

for (var i = 0; i < 10; i++){
console.log(i);
}
console.log(i);//10

看上述代码,我们在for循环头部直接定义了变量 i,可能你只是想在for循环内部使用它,但是却忽略了 i会绑定在外部作用域的事实,在for循环外部同样访问的到 i。最简单的解决办法呢,便是将var换成let,相信大多数人也知道这点,但是为什么就能用let来替换呢?我们一起来探索。

调用栈

概念

首先我们需要知道调用栈这个概念,它是用来管理函数调用关系的一种数据结构,简单来说,拥有栈的功能:先进后出,入栈,出栈等等。调用栈的功能便是用来装js代码中的各种执行上下文,是js引擎追踪函数执行的一个机制。

栈溢出

调用栈本质上还是栈,栈是有大小的,调用栈也会装满,不过这个你得多敲敲才能实现

下面我们来看看调用栈的机制:

var a = 2

function add(b,c){

    return b + c

}

function addALL(b,c){

    var d = 10

    var result = add(b,c)

    return a + result + d;

}

 addALL(3,6)

上面的代码,在全局中,创建了全局上下文,其中包含着变量环境与词法环境。根据全局预编译中变量提升三部曲的原则,变量a,以及函数add,addAll就存在于变量环境中。不过这里需要注意的是,addAll函数的执行才带来了函数add的执行,所以在调用栈中先创建addAll的执行上下文,其中包含了变量d和result,最后才是add函数的执行上下文,在执行完成之后,按照先进后出的原则,从上往下将add函数执行上下文,addAll函数执行上下文依次销毁。

白板(2)_00(1).png

块级作用域 es6引入的概念

块级作用域同样拥有scope属性

scope:

  1. 全局作用域

  2. 函数作用域

  3. 块级作用域 花括号包裹的就是一个块级 花括号里面可以访问外面,外面无法访问里面

请看下面实例:

function foo(){

    var a = 1

    let b = 2

    {

        let b = 3

        var c = 4

        let d = 5

        console.log(a);

        console.log(b);

    }

    console.log(b)//2

    console.log(c)//4

    console.log(d)//error 

}

foo()

var 声明的变量储存在变量环境中,let声明的变量储存在词法环境中。根据函数预编译四部曲,便可以得foo的执行上下文,在此需要注意的是d,因为代码块执行后就销毁,故d被销毁了,不存在变量,所以是error,如果存在却未定义叫做undefined。 2(1)(1).png

js是如何支持块级作用域的

到这里相信大家都对块级作用域有了一定的了解了,那么想想js是如何做到既要支持变量提升,又要支持块级作用域的呢?

答案就是:块级作用域是通过词法环境的栈结构实现的,而变量提升是通过变量环境来实现的

所以二者存在于不同的“房屋内”,相当于找谁去敲谁的门就行。当然二者也不是完全没有联系,你在词法环境中寻找不到变量时,还可以前往环境变量,其正确的查找方式:从词法环境的作用域栈顶开始向下查找,如果找到了就返回值,如果找不到,就继续去变量环境中查找

两者结合就同时支持变量提升和块级作用域

为什么let声明的就是块级的,var就不是呢?

那么我们就可以回答一开始提出的问题了,因为let声明是在词法变量中创建了一个作用域(执行上下文),不同于var声明所创建变量环境。

小白浅解,若有错误,请在评论指出,谢谢!