块级作用域

8/12/2022 块级作用域

# 前言

JavaScript存在变量提升的特性,导致了很多缺陷。
ES6引入块级作用域 + let、const来避免这种设计缺陷。

# 作用域

作用域是变量和函数的可见区域,控制变量和函数的可见性和生命周期。
ES6之前:

  • 全局作用域 => 全局作用域中的变量和函数在任何地方都能访问,生命周期跟随页面的生命周期。
  • 函数作用域 => 函数内部的变量和函数只能在函数内部被访问,当函数执行结束,函数作用域会被销毁。

ES6之后:

  • 全局作用域 => 同上
  • 函数作用域 => 同上
  • 块级作用域 => 一对大括号包裹的一段代码可以看作是一个块级作用域,块内定义的变量在外部不可访问。
    块级作用域示例:
// block
{ }

// if
if(1) { }

// while
while(1) { }

// function
function foo() { }

// for loop
for(let i = 0; i < 100; i++) { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 变量提升的缺陷

JavaScript当初没有块级作用域,设计成把作用域内部的变量统一进行提升。
那为何要变量提升?

  • 提高JavaScript执行性能。 JavaScript执行前会先进行编译(编译只进行一次),编译期间进行变量提升。这样避免了执行代码的过程中多次重新解析变量or函数,变量和函数的代码一般是不会改变的,编译一次即可。有一种预编译的感觉,让代码执行起来更快。
  • 增强容错性。 可以说是一把双刃剑,如果写代码出现了先使用后定义,代码依旧能正常执行。

# 缺陷

  1. 变量容易被覆盖。变量提升会把变量的值赋值为undefined
  2. 变量无法销毁。
function foo(){
  for (var i = 0; i < 3; i++) { }
  // for循环结束,i变量按道理应该被销毁了,但是仍然可以读取到。
  // 原因:foo创建执行上下文的时候,i变量已经被提升,就算循环结束,i并不会被销毁。
  console.log(i); 
}
foo()
1
2
3
4
5
6
7

# ES6块级作用域

使用let、const关键字,可以实现块级作用域。 JavaScript在编译阶段:

  • var声明存放到变量环境中。
  • let、const声明存放到词法环境(栈)中,不会被提升到变量环境中。
  • 词法环境内部维护一个小型栈结构,栈底部是函数内部最外层的let变量,每当遇到一个新的块级作用域,压入栈内;每当块级作用域执行完毕,会从栈顶弹出。
  • 变量查找过程:词法环境内部块级作用域栈顶 => 栈顶向下 => 变量环境(对象)。

# 暂时性死区

let name = 'mobs'
{
  // Uncaught ReferenceError: Cannot access 'name' before initialization
  console.log(name) 
  let name = 'mobs'
}
1
2
3
4
5
6

在块级作用域中,从开头 ~ let name = 'mobs'代码之间会形成一个暂时性死区,如果在这中间去访问变量name,会报初始化之前不能访问name的错误。

# 总结

  • var和let定义的变量分别存在于变量环境和词法环境,互不影响,变量提升仍然有效产生变量环境。
  • 词法环境内部通过去维护let定义变量的块级作用域。
  • 变量查找规则,先从词法环境栈顶向下寻找,继续到变量环境中查找。
Last Updated: 10/25/2022, 10:40:29 AM