是难点也是基础:js执行上下文及其运行原理

是难点也是基础:js执行上下文及其运行原理

在生活中,同样的文字放在不同的场合,会有不同甚至完全相反的意思,比如让人头疼的汉语四六级(搞笑)。这个说话的场合,其实就是我们说话的语境,也是说话谈论的前提。同样的,对程序语言进行“解读”的时候,也必须在特定的语境中,这个语境就是javascript中的执行上下文。

执行上下文是什么?

以上是javascript上下文的通俗理解,标准的说法是,执行上下文(Execution Context)是javascript代码执行的环境,js代码一定是在执行上下文中执行的。

在js中,执行上下文的类型有以下三重:

全局执行上下文

全局执行上下文是JS代码最开始运行时的默认环境。如果是浏览器的话,就会创建一个全局的window对象,这也是当前页面所有js代码的基础环境,一个页面只会有一个全局执行上下文。

函数执行上下文

程序中有许多的函数,函数在被调用的时候就会创建对应的执行上下文,而且是每次调用都会创建新的执行环境。

eval函数执行上下文

js的eval函数执行其内部的代码会创建属于自己的执行上下文,已不被推荐使用,所以在这里仅作了解,不再详细讨论它了。

综合起来,从执行上下文的角度来看js运作过程就是:当页面加载时,JavaScript运行时首先会进入全局环境,生成全局上下文(一个)。程序中各种函数的依次调用,就会进入函数执行环境,对应就会生成该函数的执行上下文。

JS中管理多个执行上下文的方式

以上可知,js代码运行过程中除了最开始的全局执行上下文,还有有各种函数的调用产生的函数执行上下文,那么js是怎么管理这些上下文的呢?

首先,我们要知道的之前提到的一个知识,js是单线程的。意思是js不能同时干两件事情,必须是一个一个的执行。那么这么多的执行上下文是必然要按一定的顺序执行,这个顺序是怎么决定的呢? 现实中解决这类问题的方法是叫号,摇号,但js中这么整就完全乱套了。因为js的函数调用是有逻辑关系的,那如何保证这种逻辑关系能够正确执行呢?

答案是:栈。

在JavaScript中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈(Call Stack)。栈遵循”先进后出,后进先出”的规则来进行存放取出管理,也称LIFO (“Last In First Out”) 规则。通俗的来说,栈就是只有一个开口的容器,往里放东西和往外拿东西都是通过这个出口,最新放进去的东西在最底部,所以要最后一个拿出来,最后放进去的东西(最靠近容容器开口)反而在最上面,第一个拿出来。

执行栈(函数调用栈)的工作过程

有了以上的知识准备,我们来模拟下页面从打开到关闭的整个过程中,js如何使用执行栈来管理众多执行上下文的。

整个过程的就是三个操作的不断重复:入栈、执行、出栈入栈:程序执行进入一个执行环境时,它的执行上下文就会被创建,并被压入执行栈中;执行:被压入栈而处于”栈顶”的是当前正在执行函数的执行上下文,当函数调用完成后,它就会从栈顶被推出;出栈:程序执行完成时,它的执行上下文就会被销毁,并从栈顶被推出(出栈),控制权交由下一个执行上下文。js就是通过这种方式从而完成一段又一段代码和功能的。

第一步,进入全局上下文环境,全局执行上下文入栈,因为是第一个,所以在栈底部;

第二步,执行代码,调用函数funA,创建函数funA的执行上下文,并入栈;

第三步,执行函数funA内部代码,调用函数funB,创建函数funB的执行上下文,并入栈;

第四步,执行函数funB内部代码,该执行上下文结束,funB执行上下文出栈;

第五步,执行执行函数funA内部代码调用funB函数之后代码,funA执行上下文出栈;

此时页面未关闭,执行栈中仅留下全局执行上下文在栈底。

这就是整个执行上下文的管理过程。

需要特殊说明的是:因为JS执行中页面加载就会进入全局环境,所以处于”栈底的永远是全局环境的执行上下文”,这个全局会在页面关闭的时候被取出。在出栈的过程中,正常情况下当函数调用完成后,它就会从栈顶被推出,但是如果有闭包就会阻止该操作,也就是执行上下文不会被释放。

相比之前,是不是对闭包有了更深的理解呢!理解执行上下文,对于理解作用域以及作用域链都有很大的帮助。前端虽广,保持学习,终将拿下!

发表评论