原文javascript
若是你是或者你想要成为一名js开发者,那么你必须了解js程序内部的运做。理解执行上下文和执行堆栈对于理解js的其它概念(如提高,范围和闭包)相当重要。java
正确地理解执行上下文和执行堆栈将帮助你更好地使用js开发应用。es6
废话少说,让咱们开始吧:)数组
<center> * </center>浏览器
简单来讲,执行上下文是预估和执行当前环境下js代码的抽象概念。每当在js中运行代码时,它都在执行上下文中运行。数据结构
(译者:emmm,就是执行上下文包含了追踪当前正在执行的代码的所有状态。)闭包
在js中有三种执行类型ide
执行堆栈在其它语言中被称为“调用栈”,是一种先进后出的一种数据结构,在代码执行期间被用于存储全部的执行上下文。函数
当js引擎开始解析js代码时,会先建立全局执行上下文而且放在当前执行堆栈中。每当引擎遇到函数调用的代码时,都会建立该函数的上下文并推入当前执行堆栈中。this
引擎执行位于执行堆栈顶部的方法。当方法执行完毕,执行堆栈pop掉最顶部的上下文,接着引擎继续执行堆栈顶部的方法。
用代码示范一下:
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');
<center>上述代码的执行上下文堆栈。</center>
当上述代码在浏览器内加载,js引擎就会建立一个全局执行上下文而且把它推入当前执行堆栈中。当调用first()时,js为该函数建立一个新的执行上下文,而且把它推入到当前执行堆栈。
当second()方法被first()调用,js引擎为该方法建立一个新的执行上下文并把它推入当前执行堆栈。当second()执行完毕,这个方法的上下文就被执行堆栈推出,而且执行下一个执行上下文,也就是first()。
当first()执行完毕,重复以上步骤。一旦执行了全部代码,JavaScript引擎就会从当前堆栈中删除全局执行上下文。
直到如今,咱们已经知道js引擎如何管理执行上下文的了,如今让咱们了解下执行上下文如何被js建立的。
建立执行上下文有两个阶段:1)建立阶段,2)执行阶段(译者:???懵逼脸)。
在执行任何JavaScript代码以前,执行上下文将经历建立阶段。在建立阶段会发生三件事:
(译者:VariableEnvironment和LexicalEnvironment译者也是第一次听到,惭愧,大学没学过编译原理,在js中还有个this绑定,彷佛是js特有)
所以,执行上下文在概念上能够这样表示,以下:
ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, }
在全局执行上下文中,this值指向全局对象(在浏览器内是window对象)。
在函数执行上下文中,this的值取决于函数的调用的时候的状况。若是它由对象引用调用,this值就是该对象,不然this值指向全局或者为undefined(在严格模式下)。
例如:
let 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 let calculateAge = person.calcAge; calculateAge(); // 'this' refers to the global window object, because no object reference was given
官方es6文档对词法环境有以下解释:
词汇环境是一种规范类型,用于根据ECMAScript代码的词法嵌套结构定义标识符与特定变量和函数的关联。词汇环境由environment record(译者:实在不知道咋翻)和外部词汇环境的可能为null的引用组成。
(译者:硬翻的,有点怪)
简而言之,词汇环境是一种包含标识符变量映射的结构(此处标识符指的是变量/函数的名称,变量是对实际对象【包括函数类型的对象】或原始值的引用)。
如今,词法环境由两部分组成:
(1)environment record
(2)外部环境的引用
一、environment record是存放变量和函数声明的一个地方
二、对外部环境的引用意味着它能够访问其外部词汇环境。
有两种类型的词法环境:
笔记——对于函数环境,environment record还包含一个arguments对象,该对象包含索引和传递给函数的参数之间的映射以及传递给函数的参数的长度(数量)。例如,下面函数的参数对象以下所示:
function foo(a, b) { var c = a + b; } foo(2, 3); // argument object Arguments: {0: 2, 1: 3, length: 2},
environment record也有两种类型:
抽象地说,词法环境在伪代码中看起来像这样:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here } outer: <null> } } FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here } outer: <Global or outer function environment reference> } }
它也是一个词法环境,其EnvironmentRecord包含由此执行上下文中的VariableStatements建立的绑定。
如上所述,变量环境也是一个词汇环境,所以它具备上面定义的词法环境的全部属性。
在es6,词法环境和变量环境的不一样在于前者用于存储函数声明和变量(let和const)绑定,然后者用于存储变量(var)的绑定。
来看个例子:
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
而后执行上下文会像这样:
GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here c: undefined, } outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // Identifier bindings go here g: undefined }, outer: <GlobalLexicalEnvironment> } }
笔记——函数multiply调用时才会建立函数执行上下文。
正如你所注意到的同样,let和const定义的变量没有绑定任何值,但var定义的变量为undefined
这是由于在建立阶段,扫描代码寻找变量和函数声明时,函数声明彻底存储在环境中,但变量最初设置为undefined(var)或保持为为初始化(let、const)。
(译者:就是var会声明提高,而let和const不会)
这就是为何你能够在变量声明前访问到var定义的变量,而访问let和const定义的变量则会抛出引用错误。
这就是js的变量提高。
这是整篇文章中最简单的部分。 在此阶段,完成对全部这些变量的分配,最后执行代码。
笔记——在执行阶段,若是js引擎在源代码声明的实际位置找不到let变量的值,那么它将为其分配undefined值。
如今,咱们已经了js的部分执行原理,虽然理解了这些概念不必定能让你成为出色的js开发者,可是明白了上述的概念能让你更好理解js的其它概念,例如变量提高、闭包。