块作用域
之前我们了解了函数作用域,其也是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函数执行上下文依次销毁。
块级作用域 es6引入的概念
块级作用域同样拥有scope属性
scope:
-
全局作用域
-
函数作用域
-
块级作用域 花括号包裹的就是一个块级 花括号里面可以访问外面,外面无法访问里面
请看下面实例:
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。
js是如何支持块级作用域的
到这里相信大家都对块级作用域有了一定的了解了,那么想想js是如何做到既要支持变量提升,又要支持块级作用域的呢?
答案就是:块级作用域是通过词法环境的栈结构实现的,而变量提升是通过变量环境来实现的
所以二者存在于不同的“房屋内”,相当于找谁去敲谁的门就行。当然二者也不是完全没有联系,你在词法环境中寻找不到变量时,还可以前往环境变量,其正确的查找方式:从词法环境的作用域栈顶开始向下查找,如果找到了就返回值,如果找不到,就继续去变量环境中查找
两者结合就同时支持变量提升和块级作用域
为什么let声明的就是块级的,var就不是呢?
那么我们就可以回答一开始提出的问题了,因为let声明是在词法变量中创建了一个作用域(执行上下文),不同于var声明所创建变量环境。
小白浅解,若有错误,请在评论指出,谢谢!