标签 : javascriptjavascript
当 JavaScript 代码执行一段可执行代码时,会建立对应的上下文(execution context)并将该上下文压入上下文栈(context stack)中。java
上下文包含如下3个重要属性:node
name | - | |
---|---|---|
变量对象(VO, variable object) | 当前函数定义的变量、函数、参数 | |
做用域链(Scope chain) | 源代码定义时造成的做用域链 | |
this |
上下文是一个抽象概念,为了便于理解咱们假设上下文是一个对象而且包含VO、Scope、this这三个属性:数组
function foo (c) { let a = 1 let b = function () {} } // foo函数的上下文 fooContext = { VO: { arguments: { // 实参 c: undefind, length: 0 }, a: 1, // 变量 b: reference to function (){} // 函数 }, Scope: [VO, globalContext.VO], // 做用域链 this: undefind // 非严格模式下为 this }
因此上下文是函数运行时的环境或者说是依赖资源的集合,它决定了函数运行时能够获取到哪些变量、函数。浏览器
执行上下文(EC): 若是函数处于正在执行状态则该函数的上下文称为执行上下文, 与此同时若是函数处于非执行状态则为(普通)上下文。因此执行上下文
只是上下文
的不一样状态,本质上它们没有区别。函数
上下文栈又称为执行栈(ECS), 浏览器中 javascript 解析器自己是单线程的,即同一时间只能处理一个上下文及对应的代码段,因此 javascript 解析引擎使用上下文栈来管理上下文。全部的上下文建立后会保存在上下文栈队列里。栈底为全局上下文,栈顶为当前正在执行的上下文。this
一个上下文就是一个执行单元, javascript 以栈的方式管理执行单元。页面初始化的时候首先会在栈底压入全局上下文,而后根据规则执行到可执行函数时会将函数的上下文压入上下文栈
中, 被压入的上下文包含有该函数运行时所需的资源(变量对象、做用域链、this),这些资源提供给函数运行时的表达式使用。spa
执行上下文能够理解为函数运行时的环境。同时执行上下文也是一个不可见的概念。线程
javascript 中有3种运行环境:code
window
, 在 node 环境中是global
,当页面初始化时会将全局上下文压入上下文栈;一个运行环境会对应一个上下文。位于栈顶的上下文执行完毕后会自动出栈,依次向下直至全部上下文运行完毕,最后浏览器关闭时全局上下文被销毁。为了好理解来举个栗子:
let i = 0 function foo () { i++ console.log(i, 'foo') } function too () { i++ console.log(i, 'too') foo() } function don () { i++ console.log(i, 'don') too() } don() // 1 "don" // 2 "too" // 3 "foo"
上面代码的逻辑就是先执行don(),而后是too()、foo()。执行到foo()时的上下文栈是这样的:
咱们假设上下文栈为一个数组:ECStack
:
ECStack = []
javascript 载入完成后首先解析执行的是全局代码,因此初始化的时候会向上下文栈中 push 全局上下文,咱们用globalContext
来表示。
ECStack = [ globalContext ]
全局做用域在整个代码运行阶段会一直存在,直至页面关闭时 ECStack
会被请空,从而globalContext
则被销毁。
全局上下文建立的时候进行变量提高、生成变量对象等操做,然后会执行当前上下文中的可执行代码(函数、表达式)。遇到函数调用的时候会向上下文栈中push
该函数的上下文。
function foo () { console.log('foo') } function too () { console.log('too') foo() } function don () { too() } don()
执行逻辑能够理解为:
javascript 解析器不断递归直到 foo 函数执行完...foo 函数上下文被弹出...而后回溯到globalContext
上下文...等待...当事件的回调函数被激活后,执行回调函数。( 这里涉及到 javascript 的执行机制和事件循环,请关注后续文章^_^)
执行逻辑的伪代码以下:
// 伪代码 // don() ECStack.push(<don> functionContext); // 在don中调用了too, push too的上下文到上下文栈里 ECStack.push(<fun2> functionContext); // 在too中调用了foo, push foo的上下文到上下文栈里 ECStack.push(<fun3> functionContext); // foo执行完毕, 弹出上下文 ECStack.pop(); // too执行完毕, 弹出上下文 ECStack.pop(); // don执行完毕, 弹出上下文 ECStack.pop(); // 非全局上下文执行完毕被弹出后会一直停留在全局上下文里,直至页面关闭
须要注意的是,上下文与做用域(scope)是不一样的概念。上下文是一个运行时概念,浏览器运行后执行 js 代码,将不一样的上下文加入上下文栈中,顶层的上下文对应的代码块执行完后又将该上下文销毁。 而做用域是一个静态概念,根据所在代码片断的位置及词法关系确立的,无论浏览器运行与否,源代码的做用域关系、变量的访问权限依然不变。