Javascript执行上下文

原文章资料:Understanding Execution Context and Execution Stak in Javascript

什么是 Excution Context

简单的说,执行上下文是 Javascript 代码计算和被执行环境的抽象概念。Javascript 代码在哪里执行,哪里就是执行上下文。

Excution Context 的种类

  • 全局执行上下文 —— 最基础的执行上下文,一个运行的 Javascript 程序只存在一个全局执行上下文。写在函数体之外的代码都处在全局执行上下文。他做了两件事情:创建了一个全局对象(浏览器中是window对象,node环境中是global),并把全局对象赋值给 this。
  • 函数执行上下文 —— 每当函数被调用,函数内部就会生成一个新的执行上下文。每个函数都有自己的执行上下文,但是只有当函数被调用的时候,才会被创建。
  • eval 函数执行上下文 —— eval 函数中的代码也有自己的执行上下文,但是 eval 并不常用。

Excution Stack

执行栈,又称“调用栈”,是一个先进后出的结构(LIFO, Last in First Out),用来存放代码执行过程中所有的执行上下文。

Javascript 引擎在执行代码时,创建并把全局执行上下文压入 Excution Stack;每当执行到函数调用的时候,就会创建新的函数执行上下文,然后从栈顶端推入栈中。

Javascript 引擎从顶端开始执行函数,当函数被执行完成后,它的执行上下文就会被推出栈,然后再去执行栈中的下一个上下文。

🌰 简单的例子:

let a = 'Hello World!';
function first() {
  console.log('Inside first function');
  second();
  console.log('Again inside first function');
}
function second() {
  console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
图片来自于原博客

执行上下文是如何创建的

创建上下文分为两步:

  1. 创建阶段
  2. 执行阶段

创建阶段:

LexicalEnvironment:词法环境
VariableEnvironment:变量环境

  1. 创建词法环境组件
  2. 创建变量环境组件

Lexical Environment

A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.

词法环境是用来定义 基于ECMAScript代码词法嵌套结构的具体变量和函数的关联 的具体类型。词法环境由环境记录器和一个只想外部词法环境的 引用(outer) 组成。

每一个 词法环境 由以下结构组成:

  1. Environment Record
  2. Reference to the outer environment
  3. This binding
Environment Record

存储词法环境中 定义的变量和函数。

  1. 声明式环境记录器存储变量、函数和参数。
  2. 对象环境记录器用来定义出现在全局上下文中的变量和函数,并且存储了全局对象包含的方法和属性。

对于 函数内部的词法环境,环境记录器还会记录一个 arguments 对象(存储参数的索引和值的键值对)和参数的长度。这个结构看上去如何下:

function foo(a, b) { var c = a + b }
// argument object
Arguments: {0: 2, 1: 3, length: 2}
Reference to the outer environment

外部环境的引用意味着它可以访问其父级词法环境(作用域)

this 的绑定

在全局执行上下文中,this 的值指向全局对象。(在浏览器中,this引用 Window 对象)。

在函数执行上下文中,this 的值取决于该函数是如何被调用的。如果它被一个引用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined(在严格模式下)。例如:

const person = {
  name: 'peter',
  birthYear: 1994,
  calcAge: function() {
    console.log(2018 - this.birthYear);
  }
}
person.calcAge(); 
// 'this' refers to 'person', because 'calcAge' was called with //'person' object reference
const calculateAge = person.calcAge;
calculateAge();
// 'this' refers to the global window object, because no object reference was given

抽象来说,词法环境的结构类似下面代码表示:

GlobalExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Object",
            // Identifier bindings go here
        }
        outer: <null>,
        this: <global object>
    }
}
FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
            Type: "Declarative",
            // Identifier bindings go here
        },
        outer: <Global or outer function environment reference>,
        this: <depends on how function is called>
    }
}

Variable Environment

它同样是一个词法环境,其环境记录器持有变量声明语句在执行上下文中创建的绑定关系。它有着上面定义的词法环境的所有属性。

在 ES6 中,词法环境组件和变量环境的一个不同就是前者被用来存储函数声明和变量(letconst)绑定,而后者只用来存储 var 变量绑定。

执行阶段

在此阶段,将完成对所有这些变量的分配,并最终执行代码。

例子🌰

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
    var g = 20;
    return e * f * g;
}
c = multiply(20, 30);

当上面的代码执行时,Javascript引擎会创建一个全局执行上下文来执行全局代码。全局上下文创建阶段看上去和这个差不多:

GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}

在变量分配在执行阶段完成,全局执行上下文如下:

GlobalExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          // Identifier bindings go here
          a: 20,
          b: 30,
          multiply: < func >
        }
        outer: <null>,
        ThisBinding: <Global Object>
      },
    VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Object",
          // Identifier bindings go here
          c: undefined,
        }
        outer: <null>,
        ThisBinding: <Global Object>
      }
    }
}

当执行函数 multiply(20, 30),就会创建一个新的函数执行上下文来执行函数代码:

FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          // Identifier bindings go here
          Arguments: {0: 20, 1: 30, length: 2},
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
      },
    VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          // Identifier bindings go here
          g: undefined
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>
      }
    }
}

之后执行上下文来到 执行阶段,这意味着已完成对函数内部变量的分配。

FunctionExectionContext = {
    LexicalEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          // Identifier bindings go here
          Arguments: {0: 20, 1: 30, length: 2},
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>,
      },
    VariableEnvironment: {
        EnvironmentRecord: {
          Type: "Declarative",
          // Identifier bindings go here
          g: 20
        },
        outer: <GlobalLexicalEnvironment>,
        ThisBinding: <Global Object or undefined>
      }
    }
}

函数执行之后,返回值存到了变量 c 中,随后全局词法环境被更新了,全局的代码运行完成,程序结束。

上面我们看到 letconst 声明的变量并没有被初始化,但是 var 定义的变量被设置为 undefined(这对应了 letconst 没有变量提示这一特性)。

这是因为在创建阶段,Javascript 引擎读取了所有的代码,找到了变量和函数的定义,其中函数声明被完整的存在了环境中,而变量被初始化称 undefined (如果是 var 声明的变量)或者保持未初始化(如果是 letconst 声明的变量)。

这就是为什么 var 声明的变量可以在声明之前被访问到,而 letconst 声明的变量不允许被访问(reference error)。

这就是常说的 变量提升(varaible hoisting)

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