作用域是什么
作用域就是变量和函数的可访问范围
// 函数作用域:
// 变量 myVar 在函数内定义,在函数范围之外就无法访问
const myFunction = () => {
var myVar = "Hello World";
console.log(myVar);
};
myFunction();
console.log(myVar); // ReferenceError: myVar is not defined
没有块级作用域带来的问题
ES6之前有两种作用域:全局作用域和函数作用域
其他语言普遍支持块级作用域。一对大括号包裹的一段代码,就形成了一个块
这个时候,没有块级作用域的JS就和其他语言表现不同,有些情况下会产生一些困惑
// 没有块级作用域(变量提升的设计)导致意外覆盖
var a = 1;
function test() {
console.log(a); // 代码写到这的时候,认为是打印了全局的变量a
if (true) {
var a = 2; // 此处声明的a,提升到函数范围的顶部
console.log(a);
}
}
test();
// undefined
// 2
引入 let const
let const 所处的块形成了一个块级作用域
// 块内声明的 a 不会影响外面的 a,执行效果就符合直觉
let a = 1;
function test() {
console.log(a);
if (true) {
let a = 2; // 此处以let声明的 变量a 的可见范围是它所在的大括号
console.log(a);
}
}
test();
// 1
// 2
如何实现对块级作用域的支持
我们知道代码在执行的时候,会有一个调用栈
function test() {
var a = 1;
let b = 2;
console.log(a);
console.log(c);
// 块结构
{
let a = 3;
let b = 4;
console.log(a);
console.log(b);
}
var c = 5
console.log(b);
}
test();
// 1
// undefined
// 3
// 4
// 2
执行这段代码时
- 先入栈一个
全局执行上下文- 其
变量环境里有:函数test
- 其
- 执行 test,入栈一个
函数执行上下文变量环境:var a和var c- let 声明的
变量b会被放到词法环境(词法环境里也维护一个栈结构来管理块级作用域下的变量)
- 遇到一个
块结构- 继续向
词法环境的栈结构添加内容(let a 以及 let b)
- 继续向
在块结构里执行 console.log 时,先到函数执行上下文中的词法环境的栈里查找变量,如果再去函数执行上下文的变量环境中找
这样一来就实现了对块级作用域的支持(并且兼容之前的 var 声明的变量)
整个流程里有两处对栈结构的利用:调用栈、词法环境中记录块级内容的栈结构。它们的共同点都是,在当前层工作完成后返回上一层
本文参考了 李兵的《浏览器工作原理与实践》