作用域和作用域链

142 阅读5分钟

作用域概念 (在程序运行时代码中的某些特定部分中变量、函数和对象的可访问性

作用域是当前的执行上下文,在其中的和表达式“可见”(可被访问)。如果一个变量或表达式不在当前的作用域中,那么它是不可用的。作用域也可以堆叠成层次结构,子作用域可以访问父作用域,反过来则不行。

从使用上看: 作用域是在程序执行过程中某些变量、函数、对象的可访问范围, 在哪些地方可以被使用。

从存储上看:作用域是一个对象, 他将能够被访问的数据存放在这个对象中, 可以被访问的变量、函数、对象都是这个里面的成员。

作用域就是代码的执行环境,全局作用域就是全局执行环境,函数作用域就是函数的执行环境,它们都是栈内存。 作用域链的本质是 js 底层的变量查找机制。

有哪些作用域

JavaScript 的作用域分以下四种:

  • 全局作用域:脚本模式运行所有代码的默认作用域
  • 模块作用域:模块模式中运行代码的作用域(在 ES2015 中已经实现)
  • 函数作用域:由函数创建的作用域

此外,用 let 或 const 声明的变量属于额外的作用域:

  • 块级作用域:用一对花括号(一个代码块)创建出来的作用域

由于函数会创建作用域,因而在函数中定义的变量无法从该函数外部访问,也无法从其他函数内部访问,函数的作用域在声明的时候已经被创建了

函数作用域

函数在声明这个函数的时候就已经创建, js 引擎在调用函数时才临时创建的一个作用域对象。其中保存函数的局部变量,而函数调用完,函数作用域对象就释放了。

在声明这个函数时, 函数会产生一个 [[scope]] , 他指向当前的执行上下文, 当函数被调用时, 会创建一个自己的作用域, 这个作用域与外界是隔离的, 外界无法访问该作用域中的内容。 那函数怎么访问外部的变量呢, 上一章节介绍了函数在执行时会创建一个函数执行上下文, 并将这个执行上下文压入执行栈, 函数执行上下文中有 3 个 component, 其中有个 outter 用于记录外部环境变量, 函数产生的 [[scope]] 与 outter 指向同一个执行上下文, 这也是变量查找机制的底层逻辑, 下面一张图让大家更好的理解。

image.png

在执行函数的时候, 他会先找自己 scope 作用域中的数据, 如果自己作用域中没有,会沿着作用 outter 记录的环境变量中查找, outter 和函数声明时记录的 [[scope]] 执向同一个执行上下文, 他会在全局对象中查找。在上述例子中,将输出 1 2 3

全局作用域

本质上就是一个对象

  • 在 Web 浏览器中,全局作用域被认为是 window 对象,因此所有全局变量和函数都是作为 window 对象的属性和方法创建的。
  • 在 Node环境中,全局作用域是 global 对象。

块级作用域 (在 ES 2015 中被实现)

用一对花括号(一个代码块)创建出来的作用域。

在非严格模式下, var 在代码块中不具备作用域, 使用 const、 let 创建的变量具备块级作用, 使用let、 const 声明的变量的在代码块外的作用域中不能访问。

块级作用域在如下情况被创建:

  1. 在一个函数内部
  2. 在一个代码块(由一对花括号包裹)内部
var x = 1;
let y = 1;

if (true) {
  var x = 2;
  let y = 2;
}

console.log(x);
// Expected output: 2

console.log(y);
// Expected output: 1

 {
     let variable4 = 4
 }
 console.log(variable4); // ReferenceError

但是使用let时有几点需要注意

  • 声明变量不会提升到代码块顶部,即不存在变量提升(变量提升只是现象, 本质还是 JS 底层对变量的处理机制)
  • 禁止重复声明同一变量(在执行上下文创建时, 会记录 let \ const 等声明变量的位置信息等)
  • for循环语句中()内部,即圆括号之内会建立一个隐藏的作用域(增强文本空间, 这个空间被连接在全局对象上),该作用域不属于 for 后边的 {} 中,并且只有 for 后边的 {} 产生的块作用域能够访问这个隐藏的作用域,这就使循环中 绑定块作用域有了妙用

可以用一道经典面试来辅助理解:

image.png

图中的全局对象和全局 scope 是被抽象出来的, 具体可以看上一篇文章

作用域链

作用域链本质就是 JS 底层变量查找机制。在作用中, 首先会查找当前作用域中是否存在变量, 如果不存在就会沿着作用域链查找, 直到查找到变量, 如果没有查找到, 会返回 undefined.

函数作用作用域链可以使用这张图解释 image.png

对于块级作用域会查找当前,从整体上看,如下图所示关系

image.png