一个例子 - 看尽javascript执行过程

本文涵盖的知识点: 执行上下文和执行上下文栈,作用域和作用域链,变量对象,活动对象,this, apply, call等,请事先学习相关基础知识

首先我们写了一段代码如下,很简单对不对,执行步骤可以看到注释,下面我们就一步步分析在执行中做了什么,抓稳扶好哦!!

// -------------- 第1步 ----------------------//
var jason = "global jason";
function jasonzeng(){
    var jason = "local jason";
    function f(){
        return jason;
    }
    // -------------- 第3步 ---------------//
    return f();
    // -------------- 第4步 --------------------//
}
// ------------------- 第2步 --------------------//
jasonzeng();
// --------------- 第5步 ---------------------//

准备阶段: 在代码执行之前,js环境会创建一个执行上下文栈的东西: ExecuteContextStack,这个堆栈结构为真正执行做好了准备,所有执行代码需要的东西都从这个栈里面取, 因为代码还没有执行,所以现在这个栈还是空的

ExecuteContextStack = [];

第一步: 此时开始执行全局代码,此时会创建全局的执行上下文: GlobalContext,同时全局执行上下文被压入执行上下文栈

什么时候创建执行上下文: js的只有在全局代码执行函数执行Eval执行的时候才会创建执行上下文, 说白了,这三种情况会创建独立的作用域,所以需要新创建上下文来保存当前环境的需要的东西

ExecuteContextStack = [
    GlobalContext 
]

初始化全局执行上下文: 此时GlobalContext是什么呢,里面放的就是执行全局代码需要的东西啦,这里面有三个非常重要的东西,变量对象(Variable Object), 作用域(Scope), This, 初始化后如下, 注意这里jasonzeng函数初始化的时内部有个属性[[scope]]同样保存了当前函数的作用域链,这是闭包能实现的根本原因

vo, scope, this 的关系: 最重要的其实是变量对象,这里简称VO, VO决定了当前执行环境里面有什么,而Scope其实是VO的一个从内到外的链式数组,用处就是假如在当前执行环境中需要的对象或函数时,当前VO中没有,就会顺着链条往上找这个对象或函数,而最后的this呢,则是指向正在执行的OV, 注意this不代表就一定指向当前上下文的OV,比如当用call或者apply绑定this到指定对象的时候,这是这个this就指向相应对像执行上下文的OV, 个人理解,欢迎拍砖!!

ExecuteContextStack = [
    globalContext = {
        VO : [
            jason,  
            jasonzeng = {
                [[scope]] : globalContext.VO
            }
        ],
        scope : [
            globalContext.VO
        ],
        this : globalContext.VO
    }
];

第二步: 初始化完全局执行上下文就开始执行了,首先执行的是jasonzeng()函数, 还记得上面说的吗? 在函数执行的时候同时会创建执行上下文jasonzengContext,此时因为要执行函数了,里面变量对象的VO就变成ActiveObject, 简称AO,因为是函数有参数,所以比较特殊,会默认创建一个arguments的数组来保存参数,其他的变量和函数声明 类似,注意现在只是声明,没有创建, 还需要注意的是此时函数的scope作用域链即保存了当前的AO又有上一层全局的VO, 构成完整的作用域链,当jasonzengContext初始化完后也会压入执行上下文栈ExecuteContextStack,现在完整的栈如下

  1. VO和AO的区别: 未进入执行阶段之前,变量对象(VO)中的属性都不能访问!但是进入执行阶段之后,变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作, 它们其实都是同一个对象,只是处于执行上下文的不同生命周期
  2. 作用域链的创建: 其实就是把函数初始化的时候创建的内部变量[[scope]]的链复制到当前函数的执行上下文的scope上面,并且在最前端加入当前的AO构成完整的作用域链
ExecuteContextStack = [
    jasonzengContext = {
        AO : [
            arguments = {
                length : 0
            },
            jason = undefined,
            f = function f() {}
        ],
        Scope : [
            jasonzengContext.AO , globalContext.VO
        ],
        this : undefined
    },
    globalContext = {
        VO : [
            global,
            jason,
            jasonzeng = {
                [[scope]] : globalContext.VO
            }
        ],
        Scope : [
            globalContext.VO
        ],
        this : globalContext.VO
    }
];

第三步: 开始执行jasonzeng函数内部代码,此时jasonzengContext上面的AO对象会根据代码更改为相应的值,比如当执行到f()时候,AO函数内部的jason会被赋值为jason = "local jason", 开始执行f()的时候跟上一步一样,初始化f函数的执行上下文fContext,特别注意的是里面的scope作用域链, 最后再压入执行上下文栈如下

ExecuteContextStack = [
    fContext = {
        AO : [
            arguments = {
                length : 0
            }
        ],
        Scope : [fContext.AO,  jasonzengContext.AO,  globalContext.VO],
        this : undefined
    },
    jasonzengContext = {
        AO : [
            arguments = {
                length : 0
            },
            jason = "local jason",
            f = function f() {}
        ],
        Scope : [
            jasonzengContext.AO , globalContext.VO
        ],
        this : jasonzengContext.AO
    },
    globalContext = {
        VO : [
            global,
            jason,
            jasonzeng = {
                [[scope]] : globalContext.VO
            }
        ],
        Scope : [
            globalContext.VO
        ],
        this : globalContext.VO
    }
];

第四步: 此时进入函数f内部执行了,发现终于没有再内嵌函数了,所以不用再创建执行上下文了,那么执行完f函数后它的执行上下文fContext就肯定不用了撒,然后就从执行上下文栈中弹出,弹出后执行上下文栈如下

ExecuteContextStack = [
    jasonzengContext,
    globalContext
];

第五步: 同上,执行完jasonzeng函数,执行上下文从执行上下文栈中弹出如下, 这样整个代码执行过程就讲述完了,是不是意犹未尽啊

ExecuteContextStack = [
    globalContext
];

总结: 最后总结一下,我们可以看到,在整个代码执行前和执行时和执行后都在对这个执行上下文栈做操作,因为我们在执行时需要的东西都在上面,当前作用域拥有的变量可以从AO上面获取, 闭包获取的变量从作用域链上游获取,this也可以直接获取,这样很多js的各种奇葩现象都可以解释通了,这就是由里及外学习的好处呀,加油各位爱码士!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容