Javascript基础系列之执行上下文

前言

本文翻译自what-is-the-execution-context-in-javascript

概述

当Javascript代码执行的时候,在哪个执行环境中是非常重要的。它决定了当前作用域链VO(变量对象)this指向。在本文中,目的就是为了更深入了解执行上下文,并且看完以后对解释器工作原理有个更清晰的认识

执行上下文

执行上下文形成一般有以下几种方式(不考虑es6块级作用域)

  • Global Code :默认的全局环境(代码最开始执行的环境)
  • Function Code: 函数内部环境
  • Eval Code:暂不考虑

也就是说,在代码中存在两种环境,全局环境(作用域)和函数环境(作用域)。下面,看个例子(网络图)

EC_01.jpg

代码中存在一个全局作用域和对个函数作用域,函数内可以获取全局作用域中变量,反之则不行。这和解析器中执行栈有关系

执行栈

在浏览器中,Javascript是单线程的,也就是说同时只有一个事件发生,其他的事件暂时保存一个地方,这个地方就是执行栈

是数据结构中的一种,具有先进后出的特点,入栈出栈都是在栈的一端(栈顶)操作,效率高[时间复杂度为O(1)]

下图就是一个抽象的单线程栈图(网络图)

EC_02.jpg

正如大家所知,浏览器第一次加载脚本的时候,默认就入的就是全局上下文,接着全局上下文入栈(一直在栈低)。当执行代码进入到内部函数的时候,又会创建一个新的上下文,接着新的上下文入栈(在栈顶)。而浏览器每次只会执行栈顶上下文(当前上下文),一旦执行结束,就被弹出栈,如此循环,知道栈清空为止。下面例子展示一个递归函数以及他的调用栈

(function foo(i) {
    if (i === 3) {
        return;
    }
    else {
        foo(++i);
    }
}(0));
EC_03.gif

执行栈中新的上下文入栈前,会记录当前上下文执行情况的相关信息,以便下次执行的时候继续执行

执行上下文细节

我们已经知道每当一个新的函数被调用,一个新的上下文会被创建并压入执行栈。然而在Javascirpt解析器内部,一个上下文创建有两个阶段

  • 创建阶段(进入上下文阶段)
  • 激活/执行代码(执行阶段)

1. 创建阶段

  • 创建作用域链(chain scope)
  • 创建变量,函数,arguments对象,参数
  • 决定this指向

2. 激活/执行代码

  • 变量赋值,函数引用,执行代码

从概念上,我们可以把上下文看成一个拥有三个属性的对象

executionContextObj = {
    'scopeChain': {/*变量对象和父上下文的变量*/},
    'variableObject': { /*函数arguments和参数,内部变量和函数声明*/ },
    'this': {}
}

Variable Object(VO)

executionContextObj对象当函数调用时候被创建,此时函数还没有执行。此时解析器创建executionContextObj对象,并且扫描函数的传入的parameters(形参)和arguments(实参)、函数声明,变量声明,扫描结果都保存在executionContextObj对象的variableObject

整个解析器执行代码步骤如下

    1. 找到当前调用函数的代码
    1. 在执行函数代码之前,创建执行上下文(execution context)
    1. 进入创近阶段
    • 初始化作用域链
    • 创建VariableObject对象
      • 创建arguments对象,检查上下文找中的参数,初始化属性和属性值
      • 扫描上下文中的函数声明
        • 每找到一个函数声明,就在VariableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用
        • 如果上述函数名已经存在于VariableObject下,那么对应的属性值会被新的引用所覆盖
      • 扫描上下文中的变量声明
        • 每找到一个变量声明,就在VariableObject下面用变量名建立一个属性,属性值为undefined
        • 如果变量名已经存在VariableObject中,直接忽略
    • 确定上下文中this指向
    1. 代码执行阶段
    • 执行函数体中的代码,一行一行地运行代码,给VariableObject中的变量属性赋值

看看下面一个例子

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

    };
    function c() {

    }
}

foo(22);

当调用foo(22)时,调用阶段executionContextObj看起来如下

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

当创建阶段完毕,进入执行代码阶段,代码执行完后,executionContextObj看起来如下

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

变量声明提升是怎么实现的

在一些文章中提到,在Javascipt中变量和函数声明会被提升到当前作用域顶端。但是并没有解释详细细节,但是此时我们应该是很清晰地知道原因。看下面示例代码

(function() {
    console.log(typeof foo); // function pointer
    console.log(typeof bar); // undefined
    var foo = 'hello',
        bar = function() {
            return 'world';
        };
    function foo() {
        return 'hello';
    }
}());​

上面自执行函数调用的时候,就会创建执行上下文,例子中最上面可以访问foo以及bar变量,值分别为一个函数引用和undefined

  • 为什么我们可以在声明f变量前就可以获取foo
    • 因为在进入执行上下文创建阶段,代码还没有执行,这个时候就处理了arguments,函数声明和变量声明,被初始化他们的值,函数声明就在VariableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用,变量声明就在VariableObject下面用变量名简历一个属性,值为undefied。所以当执行代码的时候,其实foo和bar已经声明,所以可以拿到
  • foo被声明了两次,为什么foo是执行一个函数而不是undefined
    • 尽管foo被声明了两次,但是在Variable Object中函数声明先被创建,同时因为Variable Object中如果属性名已经存在,会面声明就会被忽略,所以foo是函数引用,而不是undefind

总结

现在我们对Javascipt中解析器执行代码已经有一个很清晰的概念,也知道了执行上下文和执行栈,这样可以帮助我们写出更高质量的Javascipt代码

参考文档

相关文章

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

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,133评论 0 13
  • 原文:http://dmitrysoshnikov.com/ecmascript/javascript-the-c...
    jaysoul阅读 473评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,638评论 18 139
  • 什么是事务? 事务(transaction)是由一系列操作序列构成的程序执行单元,这些操作要么都做,要么都不做,是...
    snoweek阅读 394评论 0 0
  • 曾几何时,当你再去看前男友微博,和他现女友微博的时候,心静远比起初知道才分手不到一周,他俩就好上的心静得多。虽...
    走丢的姑凉阅读 129评论 0 0