【全局】:javascript
【函数】:html
【执行上下文/执行环境】组成:前端
【参考】https://www.jianshu.com/p/76ed896bbf91java
有时也称环境,执行环境定义了变量或函数有权访问的其余数据 ,决定了它们各自的行为。而每一个执行环境都有一个与之相关的变量对象,环境中定义的全部变量和函数都保存在这个对象中。git
当JavaScript解释器初始化执行代码时,它首先默认进入全局执行环境,今后刻开始,函数的每次调用都会建立一个新的执行环境。
当javascript代码被浏览器载入后,默认最早进入的是一个全局执行环境。
当在全局执行环境中调用
执行一个函数时,程序流就进入该被调用函数内,此时JS引擎就会为该函数建立一个新的执行环境,而且将其压入到执行环境堆栈的顶部。浏览器老是执行当前在堆栈顶部的执行环境,一旦执行完毕,该执行环境就会从堆栈顶部被弹出,而后,进入其下的执行环境执行代码。这样,堆栈中的执行环境就会被依次执行而且弹出堆栈,直到回到全局执行环境。github
建立
和执行
两个阶段一、在建立阶段,解析器首先会建立一个`变量对象`【variable object】(函数中称为`活动对象`【activation object】),它由定义在执行环境中的变量、函数声明、和参数组成。在这个阶段,做用域链会被初始化,this的值也会被最终肯定。 二、在执行阶段,代码被解释执行。 具体过程:每次调用函数,都会建立新的执行上下文。在JavaScript解释器内部,每次调用执行上下文,分为两个阶段: 2.1 建立阶段【如果函数,当函数被调用,但未执行任何其内部代码以前】 在进入执行上下文阶段,只会将有 var,function修饰的变量或方法添加到变量对象中。 2.1.1 建立做用域链(Scope Chain) 2.1.2 建立变量对象(变量,函数和参数) 2.1.3 肯定this的指向 2.2 激活/代码执行阶段: 2.2.1 变量赋值 2.2.2 函数引用, 2.2.3 解释/执行其余代码
函数的全部形参 (若是是函数上下文)
由名称和对应值组成的一个变量对象的属性被建立
没有实参,属性值设为 undefined数组
函数声明
由名称和对应值(函数对象(function-object))组成一个变量对象的属性被建立
若是变量对象已经存在相同名称的属性,则彻底替换这个属性浏览器
变量声明
由名称和对应值(undefined)组成一个变量对象的属性被建立;
若是变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性闭包
能够将每一个执行上下文抽象为一个对象并有三个属性:函数
executionContextObj = { scopeChain: { /* 变量对象(variableObject)+ 全部父执行上下文的变量对象*/ }, variableObject: { /*函数 arguments/参数,内部变量和函数声明 */ }, this: {} }
一、查找调用函数的代码。 二、执行函数代码以前,先建立执行上下文。 三、进入建立阶段: 3.1 初始化做用域链: 3.2 建立变量对象: 3.2.1 建立arguments对象,检查上下文,初始化参数名称和值并建立引用的复制。 3.2.2 扫描上下文的函数声明: 为发现的每个函数,在变量对象上建立一个属性——确切的说是函数的名字——其有一个指向函数在内存中的引用。 若是函数的名字已经存在,引用指针将被重写。 3.2.3 扫描上下文的变量声明: 为发现的每一个变量声明,在变量对象上建立一个属性——就是变量的名字,而且将变量的值初始化为undefined 若是变量的名字已经在变量对象里存在,将不会进行任何操做并继续扫描。 3.3 求出上下文内部“this”的值。 四、激活/代码执行阶段: 在当前上下文上运行/解释函数代码,并随着代码一行行执行指派变量的值。
function foo(i) { var a = 'hello'; var b = function privateB() { }; function c() { } } foo(22);
一、建立阶段:foo(22)函数调用时
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, this: { ... } }
二、执行阶段:执行流进入函数而且激活/代码执行阶段
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: 'hello', b: pointer to function privateB() }, this: { ... } }
注意:
解释:
执行上下文(execution context)属性:
【注】:
变量对象是与执行上下文相关的数据做用域,存储了在上下文中定义的变量和函数声明。
全局上下文中的变量对象就是全局对象
在函数上下文中,咱们用活动对象(activation object, AO)来表示变量对象。
AO & VO 区别与联系
活动对象和变量对象实际上是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,因此才叫 activation object 呐,而只有被激活的变量对象,也就是活动对象
上的各类属性才能被访问。
活动对象是在进入函数上下文时刻被建立的,它经过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。
AO = VO + function parameters + arguments AO 还包含函数的 parameters,以及 arguments 这个特殊对象
未进入执行阶段以前,变量对象(VO)中的属性都不能访问!可是进入执行阶段以后,变量对象(VO)被激活转变为了活动对象(AO),里面的属性都能被访问了,而后开始进行执行阶段的操做。
它们其实都是同一个对象,只是处于执行上下文的不一样生命周期
词法做用域(lexical scoping)是指,函数在执行时,使用的是它被定义时的做用域,而不是这个函数被调用时的做用域 函数的做用域在函数定义的时候就决定了。 这是由于函数有一个内部属性 [[scope]],当函数建立的时候,就会保存全部父变量对象到其中,你能够理解 [[scope]] 就是全部父变量对象的层级链,可是注意:[[scope]] 并不表明完整的做用域链! 当函数激活时,进入函数上下文,建立 VO/AO 后,就会将活动对象添加到做用链的前端。至此,做用域链建立完毕。
demo:
function foo() { function bar() { ... } } // 函数建立时,各自的[[scope]]为: foo.[[scope]] = [ globalContext.VO ]; bar.[[scope]] = [ fooContext.AO, globalContext.VO ];
var scope = "global scope"; function checkscope(){ var scope2 = 'local scope'; return scope2; } checkscope();
执行过程以下:
// 1.checkscope 函数被建立,保存做用域链到 内部属性[[scope]] checkscope.[[scope]] = [ globalContext.VO ]; // 2.执行 checkscope 函数,建立 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈 ECStack = [ checkscopeContext, globalContext ]; // 3.checkscope 函数并不马上执行,开始作准备工做,第一步:复制函数[[scope]]属性建立做用域链 checkscopeContext = { Scope: checkscope.[[scope]], } // 4.第二步:用 arguments 建立活动对象,随后初始化活动对象,加入形参、函数声明、变量声明 checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: checkscope.[[scope]], } // 5.第三步:将活动对象压入 checkscope 做用域链顶端 checkscopeContext = { AO: { arguments: { length: 0 }, scope2: undefined }, Scope: [AO, [[Scope]]] } // 6.准备工做作完,开始执行函数,随着函数的执行,修改 AO 的属性值 checkscopeContext = { AO: { arguments: { length: 0 }, scope2: 'local scope' }, Scope: [AO, [[Scope]]] } // 7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出 ECStack = [ globalContext ];
内部函数引用了外部函数的变量,在外部函数上下文被销毁后,其中的变量仍然能够被其内部函数引用
由于:
fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO], }
对的,就是由于这个做用域链,f 函数依然能够读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即便 checkscopeContext 被销毁了,可是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然能够经过 f 函数的做用域链找到它,正是由于 JavaScript 作到了这一点,从而实现了闭包这个概念。
说明:
我的总结,笔记,有误请指出,谢谢指教~
ps: 文章格式暂时没时间处理,以后优化【捂脸】
参考:
一、http://www.cnblogs.com/neusc/p/5771150.html
二、https://yanhaijing.com/javascript/2014/04/29/what-is-the-execution-context-in-javascript/
三、https://github.com/mqyqingfeng/Blog/issues/8
四、https://github.com/mqyqingfeng/Blog/issues/6