通过一道题 了解函数执行和作用域链

引言:在我的前一篇文章里,提到了变量在JS里是如何被存储的。本文会接着介绍函数在JS里存储的一个过程。通过文本,你会了解到作用域链、AO等概念。

没有看过我上一篇写过的文章,可以通过 浏览器执行原理、V8引擎阅读

一道作用域的题目

var n = 100

function foo() {
  n = 200
  console.log(n) //A
}

foo()

console.log(n) //B

复制代码

这段代码里A、B两处的答案都是200。如果你是一个初学者,不知道JS引擎如何存储函数和变量,大概率是不会做对的。 我想你的答案可能会是 200 100 或者 100 100等等……

想要了解答案为什么是200 200我们则必须了解JS引擎是如何存储函数的。

JS引擎存储函数的过程

还是从JS代码到AST抽象语法树这个过程(编译阶段)。

我们看下面这个图

image.png

编译之前,JS引擎还是会创建执行上下文栈ECStack (excution context stack)和GFC。 在GFC里面有个变量对象(VO),VO指向了GO(全局对象Gloabal object)。

GEC开始编译,GO里会读到变量n并且给他赋值为undefined,读到函数foo。此时我们会给foo赋值一个地址(也叫做引用,图中的0xa00),这个地址指向这个函数的一个空间。注意,这个空间叫做存储函数空间,js引擎在内存自行开辟的,有多少函数就开辟多少函数存储空间。 在这个存储空间中,存放着当前函数的作用域、他的上一层作用域、函数的执行体(代码块)。

GEC开始执行,先将n的值变为100,然后碰到foo()。ECStack会帮助我们创建一个函数执行上下文(Funtional Excution Context)它里面也有VO,VO指向一个名为AO的东西(Activation Object)活跃对象。AO里面保存有该函数的变量,以及子函数(存放的是地址)。此处AO里没有存任何东西,因为没有变量被定义过。

FEC开始执行,碰到n = 10。由于这句语句并没有定义任何变量(比如var n = 10),也就是说在AO里找不到值,那他就会顺着作用域链(scope chain)往上找。他的上层作用域,毫无疑问,就是GO(全局对象)。GO里找到了n,它的值为100,通过执行 n = 10,GO里的n就变为了200。代码A处打印200。

image.png

函数执行完,FEC就销毁了(弹出栈)。GEC接着执行代码,console.log(n)此处GO里的值n为200,也就是说B处答案为200。

image.png

作用域链 scope chain

scope chain 由当前的VO 和 ParentScope组成

我们以刚才的代码为例。如果我们在AO里找不到某个需要的变量(上述代码的变量n),就需要顺着作用域链向上找,此处我们的作用域链的上一层ParentScope就是GO了。

转自本人掘金

Supongo que te gusta

Origin juejin.im/post/7074868974072102942
Recomendado
Clasificación