一文彻底搞懂作用域

1,616 阅读2分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

什么是作用域

几乎所有编程语言最基本的功能之一,就是能够储存变量当中的值,并且能在之后对这个值进行访问或修改。事实上,正是这种储存和访问变量的值的能力将状态带给了程序。
若没有了状态这个概念,程序虽然也能够执行一些简单的任务,但它会受到高度限制,做不到非常有趣。
这些问题说明需要一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量。这套规则被称为作用域。
也就是说作用域负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并有一套规则,确定当前执行的代码对这些标识符是否有访问权限。

词法作用域

词法作用域简单来说就是定义在词法阶段的作用域。
那什么是词法阶段呢?
它是代码编译的一个步骤,在这个阶段,编译器会把字符串分解成一个个词法单元。
词法单元就是对于编程语言来说有意义的代码块。例如:

var name = "snail"

就会被分解为 varnamesnail

接下来进入语法分析阶段,这些词法单元会被转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。
最后是代码生成阶段,AST 会被转换为可执行代码。

所以词法作用域是由写代码时将变量和块作用域写在哪里来决定的。

函数作用域

属于这个函数的全部变量都可以在整个函数的范围内使用及复用,如果有嵌套的子函数,也可以在子函数中使用。这样属于函数的变量存储及访问的规则就是函数作用域。

我们把每一个函数的作用域想象成一个气泡,则内部的子函数的作用域对应的气泡就是当前气泡内的一个小气泡,同时当前函数作用域的气泡也被包含在一个更大的气泡内。

块级作用域

块级作用域,顾名思义就是属于一个代码块的作用域,但是很遗憾,ES6 之前,JavaScript 中其实并没有块级作用域。

console.log('1',i) // undefined
for(var i = 0;i<3;i++){}
console.log('2',i) // 3

这里我们在 for 循环中定义变量 i,其实是只想在 for 循环内部的上下文中使用 i,但是在 JavaScript 中,var 声明的变量此时会被绑定到外部作用域中,也就是常说的变量提升,有时候,这会给我们带来一些麻烦。
例如循环陷阱:

var result = [];
for(var i = 0;i<10;i++){
result[i] = function(){
  console.log(i)
}
}
result[0](); // 10
result[1](); // 10

例如变量覆盖:

var name = 'snail'
if(true){
  var name = 'running snail'
}
console.log(name) // running snail

ES6 之前,变量作用域的基本单元就是函数作用域,所以,当我们需要创建一个块作用域,最普遍的方法除了普通的函数声明之外,就是立即调用函数表达式(IIFE)。例如:

var name = 'snail'
;(function(){
  var name = 'running snail'
  console.log(name) // running snail
})()
console.log(name)   // snail

基于这样的一个问题,ES6 中引入了 letconst 声明变量的方式,通过 letconst 声明的变量只在当前代码块内有效。
所以它们实际上为 JavaScript 新增了块级作用域。

console.log('1',i) // ReferenceError: i is not defined
for(let i = 0;i<3;i++){}

动态作用域

实际上 JavaScript 并不具有动态作用域,它只有词法作用域。原因如下:

词法作用域和动态作用域的区别是:
词法作用域是在写代码或者说变量定义时确定的;
动态作用域是在运行时确定的;

词法作用域关注函数在何处声明,而动态作用域关注函数在何处调用。
又因为 JavaScript 是需要编译再执行的,所以它属于词法作用域,而非动态作用域。

但是 this 机制很像动态作用域。因为 this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

作用域链

当一个代码块或者函数嵌套在另一个代码块或者函数中时,就发生了作用域的嵌套。因此,当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。
我们把这之中的每一个作用域看作一环,作用域之间层层嵌套就形成了一个链,这就是作用域链。

好了,以上就是本文总结的作用域相关知识,你学废了吗? <( ̄︶ ̄)>

如有任何问题或建议,欢迎留言讨论!👏🏻👏🏻👏🏻