06作用域链

Review

  1. 2023-02-11 18:42

作用域链就是将一个个作用域串起来,实现变量查找的路径。讨论作用域链,实际就是在讨论按照什么路径查找变量的问题。

==作用域就是存放变量和函数的地方==,全局环境有全局作用域,全局作用域中存放了全局变量和全局函数。每个函数也有自己的作用域,函数作用域中存放了函数中定义的变量。

函数作用域和全局作用域 #

每个函数在执行时都需要查找自己的作用域,我们称为函数作用域,在执行阶段,在执行一个函数时,当该函数需要使用某个变量或者调用了某个函数时,便会优先在该函数作用域中查找相关内容。

var x = 4
var test

function test_scope() {
    var name = 'foo'
    console.log(name)
    console.log(type)
    console.log(test)
    var type = 'function'
    test = 1
    console.log(x)
}

test_scope()  

从Chrome执行,可以观察到:

参考图中右侧的 Scope 项,然后点击展开该项,这个 Local 就是当前函数 test_scope 的作用域。在 test_scope 函数中定义的变量都包含到了 Local 中,如变量 name、type,另外系统还为我们添加了另外一个隐藏变量 this,V8 还会默认将隐藏变量 this 存放到作用域中。

如果在当前函数作用域中没有查找到变量,那么 V8 会去全局作用域中去查找,这个查找的线路就称为作用域链

全局作用域和函数作用域类似,也是存放变量和函数的地方,但是它们还是有点不一样:

  1. 全局作用域是在 V8 启动过程中就创建了,且一直保存在内存中不会被销毁,直至 V8 退出
  2. 函数作用域是在执行该函数时创建的,当函数执行结束之后,函数作用域就随之被销毁掉

V8 启动之后就进入正常的消息循环状态,这时候就可以执行代码了,比如执行到上面那段脚本时,V8 会先解析顶层 (Top Level) 代码,我们可以看到,在顶层代码中定义了变量 x,这时候 V8 就会将变量 x 添加到全局作用域中。

作用域链是怎么工作的? #

var name = '极客时间'
var type = 'global'


function foo(){
    var name = 'foo'
    console.log(name)
    console.log(type)
}


function bar(){
    var name = 'bar'
    var type = 'function'
    foo()
}
bar()

首先当 V8 启动时,会创建全局作用域,全局作用域中包括了 this、window 等变量,还有一些全局的 Web API 接口,创建的作用域如下图所示:

V8 启动之后,消息循环系统便开始工作了,这时候,我输入了这段代码,让其执行。

V8 会先编译顶层代码,在编译过程中会将顶层定义的变量和声明的函数都添加到全局作用域中,当 V8 执行 bar 函数的时候,同样需要经历两个阶段:编译和执行。在编译阶段,V8 会为 bar 函数创建函数作用域,同样,在编译 foo 函数的过程中,会创建 foo 函数的作用域,最终创建效果如下图所示:

好了,这时候我们就有了三个作用域了,分别是全局作用域、bar 的函数作用域、foo 的函数作用域。

现在我们就可以将刚才提到的问题转换为作用域链的问题了:foo 函数查找变量的路径到底是什么?

  1. 沿着 foo 函数作用域–>bar 函数作用域–> 全局作用域 ;
  2. 还是,沿着 foo 函数作用域—> 全局作用域?

因为 JavaScript 是基于词法作用域(也将词法作用域称为静态作用域)的,词法作用域就是指,查找作用域的顺序是按照==函数定义时==的位置来决定的。bar 和 foo 函数的外部代码都是全局代码,所以无论你是在 bar 函数中查找变量,还是在 foo 函数中查找变量,其查找顺序都是按照当前函数作用域–> 全局作用域这个路径来的。

由于我们代码中的 foo 函数和 bar 函数都是在全局下面定义的,所以在 foo 函数中使用了 type,最终打印出来的值就是全局作用域中的 type。

和静态作用域相对的是动态作用域,动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用。,作用域链是基于调用栈的,而不是基于函数定义的位置的。

==简单的理解为 this 是看函数的调用位置,作用域 是看函数的声明位置。==