写在开头
我们可以知道在ES6面世之前是没有块级作用域这一说法的,那么块级作用域是如何实现的呢?块级作用域又是如何和变量提升共存的呢?
执行上下文
执行上下文相信大家都不陌生。当 JavaScript 代码执行的时候,会进入不同的执行上下文。不同的执行上下文就是不同代码块的执行环境。
调用栈
聊到执行上下文的话就一定离不开调用栈。调用栈是用来管理函数调用关系的一种数据结构,用来装js代码中的各种执行上下文。函数调用的特点是:越早被调用的函数,越晚返回。 栈完美的符合了这个条件。
let a = '111';
function foo() {
console.log('222');
baz();
console.log('333');
}
function baz() {
console.log('444');
}
foo();
console.log('555');
//222 444 333 555
这段代码的调用栈大概就是这个样子。代码的执行都是先有全局执行上下文,然后调用foo时将foo压入栈中,调用baz时将baz压入栈中,待执行结束后依次出栈。
块级作用域是如何实现的呢?
先来看一段代码。
function foo() {
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a); //1
console.log(b); //3
}
console.log(b); //2
console.log(c); //4
console.log(d); //undefined
}
foo()
首先我们可以知道的是调用栈在foo执行的时候是这样的:
那么foo的执行上下文是怎么样的呢?这个时候我们又要注意 var 和 let 了。其实 var 声明的变量是存放在变量环境的,let声明的变量是存放在词法环境的,而且内部的let定义的变量会另外开辟作用域并存放变量置栈顶。此时我们可以画出foo执行上下文的图示。
根据打印结果我们可以知道, 变量的查找方式是从词法环境的作用域栈顶开始向下查找,找到了,就返回值,找不到,就继续去变量环境中查找。
总结
块级作用域是通过词法环境的栈结构来实现的,变量提升是通过变量环境实现的,两者的实现是不会互相影响的,于是就同时实现了块级作用域与变量提升。