原文连接,欢迎关注个人博客前端
我相信不少前端初学者一开始都会被执行上下文这个概念弄晕,或者说似懂非懂。对于工做两年的我来讲,说来实在惭愧,虽然知道它大概是什么,但总以为没有一个更为清晰的认识(没法把它的工做过程描述清楚),所以最近特地温习了一遍,写下了这篇文章git
要说清它的大致工做流程,须要提早说明三个基本概念,分别是thread of exection(线程)
、variable envirnoment(变量环境)
、call Stack(调用栈)
,这些概念咱们或多或少接触过,接下来我会经过一段示例代码,和一系列图片,进一步解释这三个概念在执行上下文的运做流程。github
const num = 2; function addOne(input) { const output = input + 1; return output; } const result = addOne(2);
在运行上面这些代码前,js 引擎作的第一件是就是建立一个global execution context
,也就是全局执行上下文:编程
先看图中的黑色箭头,它表示线程thread
的执行顺序,众所周知 js 是单线程的,它会一行行、从上往下去执行代码;而右边的global memory
,它用于存储当前上下文中的数据,因为线程目前处于全局上下文环境,故加了个global
的前缀。数据结构
在这段代码中,第一行咱们声明了一个名为num
的不可变变量,并赋值为4
, 所以global memory
中就会分配内存,存储这个变量:闭包
接着继续,当线程执行到第二行时,问题就来了:咱们建立了一个addOne
的变量,并把一个函数赋值于它,那在global memory
里,到底存的是个啥?为了解答这个问题,我特地打印了一下:函数式编程
function addOne(input) { const output = input + 1; return output; } console.log(addOne);
看,咱们居然把函数里的内容完完整整打印出来了,很明显,它存的是一个函数内部的“文本信息”。函数
其实很容易理解,当执行第二行的时候,该函数并无被调用,所以线程不会马上解析里面的内容,而是把它内部的信息以“文本内容”的形式保存下来,当须要执行的时候,才去解析变量里的函数内容,这也很好地解析了为何函数内的异常仅会在函数被调用时才抛出来。post
所以这时global execution context
长这样:spa
因为addOne
里保存的是函数内容,目前对于线程而言它是未知的,所以咱们这里特地用一个带有输入输出箭头的函数图标,表明它是一个未被解析的函数。
咱们继续执行第三步:仍是建立了一个变量result
,但此时它被赋予undefined
,由于线程暂时没法从addOne
这个函数里获知它的返回值。
因为addOne
函数被调用了,线程会从刚刚保存的addOne
变量中取出内容,去解析、执行它。这时 js 就建立了一个新的执行上下文——local execution context
,即当前的执行上下文,这是一个船新的上下文,所以我特地用一个新图片去描述它:
首先这个memory
我加了个local
前缀,表面当前存储的都是此上下文中的变量数据。不管是上述的global memory
,亦或是如今、或将来的local memory
,咱们能够用一个更为专业的术语variable envirnoment
去描述这种存储环境。
此外,这个黑箭头我特地画了个拐角,意味它是从全局上下文进来的,local memory
首先会分配内存给变量input
,它在调用时就被2
赋值了,紧接着又建立了一个output
标签并把计算结果3
赋值给它。最后,当线程遇到离开上下文的标识——return
,便离开上下文,并把ouput
的结果一并返回出去。
此时,这个上下文就没用了(被执行完了),因而乎垃圾回收便盯上了它,选择一个恰当的时机把里面的local memory
删光光。
这时候有同窗会问道:你这个只是普通场景,那闭包怎么解释呢?
其实闭包是个比较大的话题,这里也能够简单描述下:当return
的是一个函数的话,它返回的不只是函数自己,还会把local memory
中被引用的变量做为此函数的附加属性一并返回出去,这就比如印鱼喜欢吸附在鲨鱼身上通常,鳝鱼不管去哪都带着它,所以,不管这个函数在哪里被调用,它都能在它自己附带的local memory
中找到那个变量。若是你把返回的函数console.log
出来,也是可以找到它的,这里就不详说,关于闭包的更多概念(包括在函数式编程中的使用),有兴趣的童鞋能够看看这篇文章:Partial & Curry - 函数式编程
go on,回到了global execution context
,result
再也不孤零零地undefined
,而是拿到了可爱的3
:
到这里,咱们的线程就完成了全部工做,能够歇息了,等等.....好像漏了什么没讲.....对,就是Call Stack
!
刚刚咱们全篇在讲解thread of exection
多么努力地一行行解析执行代码,variable envirnoment
多么勤快地存储变量,那call stack
干了什么?是在偷懒吗?
其实并非,call stack
起到了很是关键的做用:有了它,线程随时能够知道本身目前处于哪一个上下文。
试想一下,咱们在写代码的过程当中每每喜欢在各类函数内穿插着各类子函数,好比递归,所以勤劳的线程就得不断地进入上下文、退出上下文、再进入、再进入、再退出、再进入,长此以往线程根本就不知道本身处于哪一个上下文中,也不知道应该在哪一个memory
中取数据,全都乱套了,所以必须经过一种方式去跟踪、记录目前线程所处的环境。
而call stack
就是一种数据结构,用于“存储”上下文,经过不断推入push
、推出pop
上下文的方式,跟踪线程目前所处的环境——线程无需刻意记住本身身处何方,只要永远处于最顶层执行上下文,就是当前函数执行的正确位置
程序一开始运行的时候,call stack
先会push
个global execution context
:
接着咱们在上述代码第三行调用了addOne
,call stack
马上将addOne
的上下文push
进去,待执行到return
标识后,再pop
出来:
一样的,即便在复杂的状况,只要遵循push
、pop
以及时刻处于最顶层上下文的原则,线程就能够一直保持在正确的位置上:
值得一提的是,call stack
层数是有上限的,所以稍加不注意,你写的递归可能会形成栈溢出
了。
简单来讲,上下文就是个能够用于执行代码的环境,与它相关的有三个重要的概念: