![7fa414290bf190e5f9074bd444f36cc2.png](https://i-blog.csdnimg.cn/blog_migrate/73d9ebac051fe6a7a4b96269d9507dc1.jpeg)
在这篇文章中,将深入了解JavaScript最基本的部分之一,即执行上下文。在这篇文章的最后,你应该更清楚地了解解释器在做什么,为什么在声明函数/变量之前可以使用它们,以及它们的值是如何确定的。
1.执行上下文是什么
当代码在JavaScript中运行时,执行代码的环境非常重要,计算结果如下:
全局代码——第一次执行代码的默认环境。
函数代码——每当执行流进入函数体时。
Eval代码——在内部Eval函数中执行的文本。
您可以在网上阅读许多参考范围的资源,为了使本文更容易理解,让我们将术语执行上下文看作当前代码所处的环境/范围。
现在,说得够多了,让我们看一个包含全局和函数/本地上下文计算代码的示例。
![04d0ebdcd3274827b8b019da25cbdf85.png](https://i-blog.csdnimg.cn/blog_migrate/a593ef4236ecb58305b339ccb25f2580.jpeg)
这里没有什么特别的,我们有1个全局上下文由紫色边框表示,3个不同的函数上下文由绿色、蓝色和橙色边框表示。
只能有一个全局上下文,可以从程序中的任何其他上下文访问它。
您可以有任意数量的函数上下文,每个函数调用都会创建一个新的上下文,这将创建一个私有范围,在这个范围内声明的任何内容都不能从当前函数范围之外直接访问。
在上面的例子中,一个函数可以访问它当前上下文之外声明的变量,但是外部上下文不能访问它内部声明的变量/函数。
为什么会这样?
这段代码究竟是如何计算的?
![a90425b808e2e8b9f3f2fcb9b64cc679.png](https://i-blog.csdnimg.cn/blog_migrate/f13f1a2132ab964a21ce57c5e8d6bba0.jpeg)
2.执行上下文堆栈
浏览器中的JavaScript解释器是作为单个线程实现的。
这实际上意味着在浏览器中一次只能发生一件事,其他操作或事件在所谓的执行堆栈中排队。
下图是单线程堆栈的抽象视图:
![a0e765e2abc096a5e8469981d435ea7c.png](https://i-blog.csdnimg.cn/blog_migrate/98c44ed3c91ae41c391a68ff76707214.jpeg)
我们已经知道,当浏览器第一次加载脚本时,默认情况下它会进入全局执行上下文。
如果在全局代码中调用一个函数,程序的序列流输入被调用的函数,创建一个新的执行上下文并将该上下文推到执行堆栈的顶部。
如果你在当前函数中调用另一个函数,同样的事情也会发生。
代码的执行流进入内部函数,该函数创建一个新的执行上下文,该上下文被推到现有堆栈的顶部。
浏览器将始终执行位于堆栈顶部的当前执行上下文,一旦函数完成执行当前执行上下文,它将从堆栈顶部弹出,将控制权返回到当前堆栈中的下一个上下文。
下面的例子展示了一个递归函数和程序的执行堆栈:
![b1f4c1355e3ee27fe3585135653605b4.gif](https://i-blog.csdnimg.cn/blog_migrate/df1260ddd37ea6e1a7ed60468e11a67f.gif)
代码简单地调用自己3次,将i的值增加1。
每次调用函数foo时,都会创建一个新的执行上下文。
一旦上下文完成执行,它就会弹出堆栈并返回到它下面的上下文,直到再次到达全局上下文。
关于执行堆栈,有5个要点需要记住:
单线程的。
同步执行。
1全局上下文。
无限的函数上下文。
每个函数调用都会创建一个新的执行上下文,甚至是对自身的调用。
![34896d2af2d7ed56ebadaf1f174c1fb6.png](https://i-blog.csdnimg.cn/blog_migrate/a84a67e10a5d619e3563e19f8a287309.jpeg)
3.详细执行上下文
现在我们知道,每次调用一个函数时,都会创建一个新的执行上下文。
然而,在JavaScript解释器中,对执行上下文的每次调用都有两个阶段:
创建阶段[当函数被调用,但在它执行任何内部代码之前]:
创建范围链
创建变量、函数和参数
确定“this”的值
激活/代码执行阶段:
为函数赋值、引用并解释/执行代码。
![74ffb1fe44c5e9f7eefb015d3abeaf2a.png](https://i-blog.csdnimg.cn/blog_migrate/ec7bbcc72813d124729010e3082192de.jpeg)
4.激活/可变对象[AO/VO]
这个executioncontext tobj是在调用函数时创建的,但在实际函数执行之前。
这被称为第一阶段,即创造阶段。
在这里,解释器通过扫描传入的参数、局部函数声明和局部变量声明来创建executioncontext tobj。
扫描的结果成为executioncontext tobj中的变量对象。
下面是解释器如何计算代码的伪概述:
找到一些代码来调用函数
在执行函数代码之前,创建执行上下文
进入创作阶段:初始化作用域链。
创建变量对象:创建arguments对象,检查参数的上下文,初始化名称和值,并创建一个引用副本。
扫描上下文中的函数声明:
对于找到的每个函数,在变量对象中创建一个属性,该属性是准确的函数名,它在内存中有一个指向该函数的引用指针。
如果函数名已经存在,则引用指针值将被覆盖。
扫描上下文变量声明:
对于找到的每个变量声明,在变量对象中创建一个属性,即变量名,并将值初始化为undefined。
如果变量对象中已经存在变量名,那么什么也不要做,继续扫描。
确定上下文中“this”的值。
激活/代码执行阶段:
在上下文中运行/解释函数代码,并在代码逐行执行时分配变量值。
让我们看一个例子:
![df43d38e8c9d018467905239cba10277.png](https://i-blog.csdnimg.cn/blog_migrate/525c253d7e0df333d0cddfa3399bebed.jpeg)
在调用foo(22)时,创建阶段如下所示:
![651d6bc50a6cf5f755b90879d07698d3.png](https://i-blog.csdnimg.cn/blog_migrate/eb4713f96b1c2bb8956693f52a30b078.jpeg)
如你所见,创建阶段处理定义属性的名称,而不是为属性赋值,只有形式参数/参数例外。
创建阶段完成后,执行流进入函数,函数完成执行后的激活/代码执行阶段如下:
![ee598a9fda68197764f2624b21edaf63.png](https://i-blog.csdnimg.cn/blog_migrate/fa309fc019b69656adda5583f8708c36.jpeg)
总结:
希望现在你已经很好地掌握了JavaScript解释器如何评估代码。
理解执行上下文和堆栈可以让您知道为什么你的代码要计算不同的值,而这些值是你最初没有预料到的。
![487c16fd966bf6c0378a53362070a42f.png](https://i-blog.csdnimg.cn/blog_migrate/3c6e6b97fc507561073ed474bed2ff74.jpeg)