深入javascript之执行上下文

前言:深入js对学习框架很重要,希望这一系列文章会对你有帮助(持续更新中)

深入系列:深入javascript之原型和原型链

深入系列:深入javascript之作用域


一,相关概念

        EC : 执行上下文

        ECS : 执行环境栈

        VO : 变量对象

        AO : 活动对象

        scope chain :作用域链


二,执行上下文

        javascript运行的代码环境有三种:

                全局代码:代码默认运行的环境,最先会进入到全局环境中

                函数代码:在函数的局部环境中运行的代码

                Eval代码:在Eval()函数中运行的代码

        全局上下文是最外围的一个执行环境,web浏览器中被认为是window对象。在初始化代码时会先进入全局上下文中,每当一个函数被调用时就会为该函数创建一个执行上下文,每个函数都有自己的执行上下文。看一段代码大家就会明白:

        function f1() {
            var f1Context = 'f1 context';
            function f2() {
                var f2Context = 'f2 context';
                function f3() {
                    var f3Context = 'f3 context';
                    console.log(f3Context);
                }
                f3();
                console.log(f2Context);
            }
            f2();
            console.log(f1Context);
        }
        f1();

        这段代码有4个执行上下文:全局上下文和f1(),f2(),f3()属于自己的执行上下文。

        全局上下文拥有f1(),f1()的上下文中有变量f1Context和f2(),f2()的上下文有变量f2Context和f3(),f3()上下文有变量f3Context。

        在这我们了解下执行环境栈ECS,一段代码所有的执行上下文都会被推入栈中等待被执行,因为js是单线程,任务都为同步任务的情况下某一时间只能执行一个任务,执行一段代码首先会进入全局上下文中,并将其压入ECS中,执行f1()会为其创建执行上下文压入栈顶,f1()中有f2(),再为f2()创建f2()的执行上下文,依次,最终全局上下文被压入到栈底,f3()的执行上下文在栈顶,函数执行完后,ECS就会弹出其上下文,f3()上下文弹出后,f2()上下文来到栈顶,开始执行f2(),依次,最后ECS中只剩下全局上下文,它等到应用程序退出,例如浏览器关闭时销毁。(有点多,大家应该改能理解,下面总结帮助大家梳理一下)

        总结:(执行上下文就用EC替代)

                1. 全局上下文压入栈顶

                2. 执行某一函数就为其创建一个EC,并压入栈顶

                3. 栈顶的函数执行完之后它的EC就会从ECS中弹出,并且变量对象(VO)随之销毁

                4. 所有函数执行完之后ECS中只剩下全局上下文,在应用关闭时销毁

        

        大家再看一道道题:

        function foo(i) {
            if(i  == 3) {
                return;
            }
            foo(i+1);
            console.log(i);
        }
        foo(0);

        大家明白执行上下文的进栈出栈就应该知道结果为什么是2,1,0

        ECS栈顶为foo(3)的的上下文,直接return弹出后,栈顶变成foo(2)的上下文,执行foo(2),输出2并弹出,执行foo(1),输出1并弹出,执行foo(0),输出0并弹出,关闭浏览器后全局EC弹出,所以结果为2,1,0。

        大家应该对执行上下文稍微有了认识,刚才提到VO,我们来了解什么是VO。


三,VO/AO

        VO(变量对象)

                创建执行上下文时与之关联的会有一个变量对象,该上下文中的所有变量和函数全都保存在这个对象中。

        AO(活动对象)

                进入到一个执行上下文时,此执行上下文中的变量和函数都可以被访问到,可以理解为被激活。

        谈到了上下文的创建和执行,我们来看看EC建立的过程:

                建立阶段:(函数被调用,但是还未执行函数中的代码)

                        1. 创建arguments,变量,函数

                        2. 建立作用域链

                        3. 确定this的值

                执行阶段:变量赋值,函数引用,执行代码

        执行上下文为一个对象,包含VO,作用域链和this

        executionContextObj = {  
           variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },  
           scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ },  
           this: {}  
        }

        具体过程:

                1. 找到当前上下文调用函数的代码

                2. 执行代码之前,先创建执行上下文

                3. 创建阶段:

                        3-1. 创建变量对象(VO):  

                               1. 创建arguments,检查当前上下文的参数,建立该对象下的属性和属性值

                               2. 扫描上下文的函数申明:

                                   1. 每扫描到一个函数什么就会在VO里面用函数名创建一个属性,为一个指针,指向该函数在内存中的地址

                                   2. 如果函数名在VO中已经存在,对应的属性值会被新的引用覆盖

                               3. 扫描上下文的变量申明:

                                   1. 每扫描到一个变量就会用变量名作为属性名,其值初始化为undefined

                                   2. 如果该变量名在VO中已经存在,则直接跳过继续扫描

                        3-2. 初始化作用域链

                        3-3. 确定上下文中this的指向

                4. 代码执行阶段

                        4-1. 执行函数体中的代码,给VO中的变量赋值

        看代码理解:

        function foo(i) {  
            var a = 'hello';  
            var b = function privateB() {};  
            function c() {}  
        }  
        foo(22);

        调用foo(22)时创建上下文包括VO,作用域链,this值

        以函数名作为属性值,指向该函数在内存中的地址;变量名作为属性名,其初始化值为undefined

        注意:函数申明先于变量申明

        fooExecutionContext = {  
            variableObject: {  
                arguments: {  
                    0: 22,  
                    length: 1  
                },  
                i: 22,  
                c: pointer to function c(),  
                a: undefined,  
                b: undefined  
            },  
            scopeChain: { ... },  
            this: { ... }  
        } 

        创建阶段结束后就会进入代码执行阶段,给VO中的变量赋值

        fooExecutionContext = {  
            variableObject: {  
                arguments: {  
                    0: 22,  
                    length: 1  
                },  
                i: 22,  
                c: pointer to function c(),  
                a: 'hello',  
                b: pointer to function privateB()  
            },  
            scopeChain: { ... },  
            this: { ... }  
        }


四,变量提升

        很多人都知道变量提升,变量值为undefined时就说是变量提升造成的,现在大家应该就知道为什么了

        function foo() {
            console.log(f1);    //f1() {}
            console.log(f2);    //undefined
            
            var f1 = 'hosting';
            var f2 = function() {}
            function f1() {}
        }
        foo();

        为什么能在申明之前就能调用也就是所谓的变量提升:

       因为调用foo()时会创建VO,初始VO中变量值等有一系列的过程,所有变量初始化值为undefined,所以console.log(f2)的值为undefined。并且函数申明先于变量申明,所以console.log(f1)的值为f1()函数而不为hosting。


五,总结

        1. 调用函数是会为其创建执行上下文,并压入执行环境栈的栈顶,执行完毕弹出,执行上下文被销毁,随之VO也被销毁

        2. EC创建阶段分创建阶段和代码执行阶段

        3. 创建阶段初始变量值为undefined,执行阶段才为变量赋值

        4. 函数申明先于变量申明

        5. 参考文章:点击打开链接

        6. 欢迎补充指正,下篇会为大家带来《深入javascript之作用域链和闭包》

        7. 更多深入系列请看:深入javascript之原型和原型链深入javascript之作用域


猜你喜欢

转载自blog.csdn.net/yucihent/article/details/79768210
今日推荐