资讯专栏INFORMATION COLUMN

深入javascript之执行上下文

ky0ncheng / 1632人阅读

摘要:在初始化代码时会先进入全局上下文中,每当一个函数被调用时就会为该函数创建一个执行上下文,每个函数都有自己的执行上下文。来看一段代码这段代码有个执行上下文全局上下文和,,属于自己的执行上下文。

聊聊js的执行上下文

一,相关概念
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: { ... }    
    }
四,变量提升
    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. 函数申明先于变量申明
    

参考:
深入js之执行上下文
Execution Context

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93939.html

相关文章

  • JavaScript深入执行下文

    摘要:深入系列第七篇,结合之前所讲的四篇文章,以权威指南的为例,具体讲解当函数执行的时候,执行上下文栈变量对象作用域链是如何变化的。前言在深入之执行上下文栈中讲到,当代码执行一段可执行代码时,会创建对应的执行上下文。 JavaScript深入系列第七篇,结合之前所讲的四篇文章,以权威指南的demo为例,具体讲解当函数执行的时候,执行上下文栈、变量对象、作用域链是如何变化的。 前言 在《Jav...

    gougoujiang 评论0 收藏0
  • JavaScript深入作用域链

    摘要:下面,让我们以一个函数的创建和激活两个时期来讲解作用域链是如何创建和变化的。这时候执行上下文的作用域链,我们命名为至此,作用域链创建完毕。 JavaScript深入系列第五篇,讲述作用链的创建过程,最后结合着变量对象,执行上下文栈,让我们一起捋一捋函数创建和执行的过程中到底发生了什么? 前言 在《JavaScript深入之执行上下文栈》中讲到,当JavaScript代码执行一段可执行代...

    waltr 评论0 收藏0
  • JavaScript深入闭包

    摘要:深入系列第八篇,介绍理论上的闭包和实践上的闭包,以及从作用域链的角度解析经典的闭包题。定义对闭包的定义为闭包是指那些能够访问自由变量的函数。 JavaScript深入系列第八篇,介绍理论上的闭包和实践上的闭包,以及从作用域链的角度解析经典的闭包题。 定义 MDN 对闭包的定义为: 闭包是指那些能够访问自由变量的函数。 那什么是自由变量呢? 自由变量是指在函数中使用的,但既不是函数参数也...

    caige 评论0 收藏0
  • JavaScript深入执行下文

    摘要:深入系列第三篇,讲解执行上下文栈的是如何执行的,也回答了第二篇中的略难的思考题。 JavaScript深入系列第三篇,讲解执行上下文栈的是如何执行的,也回答了第二篇中的略难的思考题。 顺序执行? 如果要问到 JavaScript 代码执行顺序的话,想必写过 JavaScript 的开发者都会有个直观的印象,那就是顺序执行,毕竟: var foo = function () { ...

    codecraft 评论0 收藏0
  • 【进阶1-2期】JavaScript深入执行下文栈和变量对象

    摘要:本计划一共期,每期重点攻克一个面试重难点,如果你还不了解本进阶计划,点击查看前端进阶的破冰之旅本期推荐文章深入之执行上下文栈和深入之变量对象,由于微信不能访问外链,点击阅读原文就可以啦。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第一期,本周的主题是调用堆栈,今天是第二天。 本计划一共28期,每期...

    Richard_Gao 评论0 收藏0
  • JavaScript深入变量对象

    摘要:深入系列第四篇,具体讲解执行上下文中的变量对象与活动对象。下一篇文章深入之作用域链本文相关链接深入之执行上下文栈深入系列深入系列目录地址。 JavaScript深入系列第四篇,具体讲解执行上下文中的变量对象与活动对象。全局上下文下的变量对象是什么?函数上下文下的活动对象是如何分析和执行的?还有两个思考题帮你加深印象,快来看看吧! 前言 在上篇《JavaScript深入之执行上下文栈》中...

    Zachary 评论0 收藏0

发表评论

0条评论

ky0ncheng

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<