本文主要介绍JavaScript程序内部的执行机制

首先先了解什么是执行上下文

执行上下文就是当前JavaScript代码被解析和执行是所在环境的抽象概念,JavaScript中运行任何的代码都是在执行上下文中运行。

执行上下文的类型,总共有三类

  • 全局执行上下文:这是默认的,最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。共有两个过程:1.创建有全局对象,在浏览器中这个全局对象就是window对象。2.将this指针指向这个全局对象。一个程序中只能存在一个执行上下文。
  • 函数执行上下文:每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在多个函数执行上下文,这些函数执行上下文按照特定的顺序执行一系列步骤,后文具体讨论。
  • Eval函数执行上下文:运行在eval函数中的代码也获得了自己的执行上下文,但由于Eval较为少用到,也不建议使用,就不去详细讨论了。。。(eval方法是在运行时对脚本进行解释执行,而普通的javascript会有一个预处理的过程。所以会有一些性能上的损失;eval也存在一个安全问题,因为它可以执行传给它的任何字符串,所以永远不要传入字符串或者来历不明和不受信任源的参数。

执行栈

执行栈,也叫调用栈,具有LIFO(Last in, First out 后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。

当JavaScript引擎首次读取脚本时,会创建一个全局执行上下文并将其Push到当前执行栈中。每当发生函数调用时,引擎都会为该函数创建一个新的执行上下文并Push到当前执行栈的栈顶。

引擎会运行执行上下文在执行栈栈顶的函数,根据LIFO规则,当此函数运行完成后,其对应的执行上下文将会从执行栈中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');

运行结果:

当上述代码在浏览器中加载时,JavaScript 引擎会创建一个全局执行上下文并且将它推入当前的执行栈。当调用 first() 函数时,JavaScript 引擎为该函数创建了一个新的执行上下文并将其推到当前执行栈的顶端。

当在 first() 函数中调用 second() 函数时,Javascript 引擎为该函数创建了一个新的执行上下文并将其推到当前执行栈的顶端。当 second() 函数执行完成后,它的执行上下文从当前执行栈中弹出,上下文控制权将移到当前执行栈的下一个执行上下文,即 first() 函数的执行上下文。

first() 函数执行完成后,它的执行上下文从当前执行栈中弹出,上下文控制权将移到全局执行上下文。一旦所有代码执行完毕,Javascript 引擎把全局执行上下文从执行栈中移除。

执行上下文是如何被创建的

到目前为止,我们已经看到了 JavaScript 引擎如何管理执行上下文,现在就让我们来理解 JavaScript 引擎是如何创建执行上下文的。

执行上下文分两个阶段创建:1)创建阶段; 2)执行阶段

创建阶段

在任意的JavaScript代码被执行前,执行上下文处于创建阶段。在创建阶段总共发生了三件事情:

  1. 确定this的值,也被称为This Binding;
  2. LexicaEnvironment(词法环境)组件被创建;
  3. VariableEnvironment(变量环境)组件被创建。

因此,执行上下文可以在概念上表示如下:

ExecutionContext = {
ThisBinding = <this value>,
LexicalEnvironment = { ... },
VariableEnvironment = { ... },
}

This Biling:

在全局执行上下文中,this 的值指向全局对象,在浏览器中,this 的值指向 window 对象。

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

let person = {
name: 'peter',
birthYear: 1994,
calcAge: function() {
console.log(2018 - this.birthYear);
}
} person.calcAge();
// 'this' 指向 'person', 因为 'calcAge' 是被 'person' 对象引用调用的。 let calculateAge = person.calcAge;
calculateAge();
// 'this' 指向全局 window 对象,因为没有给出任何对象引用

词法环境(Lexical Environment):

官方ES6文档将词法环境定义为:

  • 词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符与特定变量和函数的关联关系。词法环境由环境记录(environment record)和可能为空引用(null)的外部词法环境组成。

简而言之,词法环境是一个包含标识符变量映射的结构。(这里的标识符表示变量/函数的名称,变量是对实际对象【包括函数类型对象】或原始值的引用)

在词法环境中,有两个组成部分:(1)环境记录(environment record) (2)对外部环境的引用

  1.环境记录是存储变量和函数声明的实际位置
  2. 对外部环境的引用意味着它可以访问其外部词法环境

词法环境有两种类型:
  • 全局环境(在全局执行上下文中)是一个没有外部环境词法环境的词法环境。全局环境的外部环境引用为null。它拥有一个全局对象(window对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this的值指向这个全局对象。
  • 函数环境,用户在函数中定义的变量被存储在环境记录中,包含了arguments对象。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。
注意:对于函数环境而言,环境记录还包含了一个arguments对象,该对象包含了索引和传递给函数的参数之间的映射以及传递给函数的参数的长度(数量)。例如下面函数的arguments对象:
function foo(a, b) {
var c = a + b;
}
foo(2, 3); // arguments 对象
Arguments: {0: 2, 1: 3, length: 2},

环境记录同样也有两种类型:

  • 声明性环境记录存储变量,函数和参数。一个函数环境包含声明性环境记录。
  • 对象环境记录用于定义在全局执行上下文中出现的变量和函数的关联。全局环境包含对象环境记录。

抽象地说,词法环境的伪代码中看起来像这样:

GlobalExectionContext = {  // 全局执行上下文
LexicalEnvironment: { // 词法环境
EnvironmentRecord: { // 环境记录
Type: "Object", //全局环境
// 标识符绑定在这里
outer: <null> // 对外部环境的引用
}
} FunctionExectionContext = { //函数执行上下文
LexicalEnvironment: { //词法环境
EnvironmentRecord: { //环境记录
Type: "Declarative", //函数环境
// 标识符绑定在这里
outer: <Global or outer function environment reference> //对外部环境的引用
}
}

变量环境:

变量环境是一个词法环境,因此它具有上面定义的词法环境的所有属性。

在 ES6 中,词法环境(LexicalEnvironment 组件)和 变量环境(VariableEnvironment 组件)的区别在于前者用于存储函数声明和变量( 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",
// 标识符绑定在这里
a: < uninitialized >,
b: < uninitialized >,
multiply: < func >
}
outer: <null>
}, VariableEnvironment: {
EnvironmentRecord: {
Type: "Object",
// 标识符绑定在这里
c: undefined,
}
outer: <null>
}
} FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
Arguments: {0: 20, 1: 30, length: 2},
},
outer: <GlobalLexicalEnvironment>
}, VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative",
// 标识符绑定在这里
g: undefined
},
outer: <GlobalLexicalEnvironment>
}
}

注意:只有在遇到函数multiply的调用时才会创建函数执行上下文。

变量提升的原因:在创建阶段,函数声明存储在环境中,而变量会被设置为 undefined(在 var 的情况下)或保持未初始化(在 let 和 const 的情况下)。所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined),但如果在声明之前访问 let 和 const 定义的变量就会提示引用错误的原因。这就是所谓的变量提升。

执行阶段

此阶段,完成对所有变量的分配,最后执行代码。

如果JavaScript引擎在源代码中声明的实际位置找不到 let 变量的值,那么将为其分配 undefined 值。

参考:

JavaScript js调用堆栈(一)的更多相关文章

  1. JavaScript js调用堆栈(二)

    本文主要介绍JavaScript的内存空间 var a = 20; var b = 'abc'; var c = true; var d = { m: 20 } 首先需要对栈(stack),堆(hea ...

  2. JavaScript js调用堆栈(三)

    本文主要深入介绍JavaScript内存机制 内存模型 JS内存空间分为栈(stack),堆(heap),池(一般也会归类为栈中),其中栈存放变量,堆存放复杂对象,池存放常量. 注:闭包中的变量并不保 ...

  3. 【HANA系列】SAP HANA XS使用JavaScript(JS)调用存储过程(Procedures)

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列]SAP HANA XS使用Jav ...

  4. 【HANA系列】【第六篇】SAP HANA XS使用JavaScript(JS)调用存储过程(Procedures)

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列][第六篇]SAP HANA XS ...

  5. JS调用堆栈

    调用栈 JavaScript 是一门单线程的语言,这意味着它只有一个调用栈,因此,它同一时间只能做一件事.如果我们运行到一个函数,它就会将其放置到栈顶.当从这个函数返回的时候,就会将这个函数从栈顶弹出 ...

  6. [JavaScript]JS调用PHP和PHP调用JS的方法举例

    http://blog.csdn.net/pleasecallmewhy/article/details/8592571 body { background: #C7EDCC !important; ...

  7. js调用php和php调用js的方法举例

    js调用php和php调用js的方法举例1 JS方式调用PHP文件并取得php中的值 举一个简单的例子来说明: 如在页面a.html中用下面这句调用: <script type="te ...

  8. JS调用PHP 和 PHP调用JS的方法举例

    http://my.oschina.net/jiangchike/blog/220988 1.JS方式调用PHP文件并取得PHP中的值举一个简单的例子来说明:如在页面test_json1中用下面这句调 ...

  9. JavaScript是如何工作的:引擎,运行时和调用堆栈的概述!

    摘要: 理解JS执行原理. 原文:JavaScript是如何工作的:引擎,运行时和调用堆栈的概述! 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 本文是旨在深入研究JavaScrip ...

随机推荐

  1. React.js 小书 Lesson8 - 组件的组合、嵌套和组件树

    作者:胡子大哈 原文链接:http://huziketang.com/books/react/lesson8 转载请注明出处,保留原文链接和作者信息. 继续拓展前面的例子,现在我们已经有了 Heade ...

  2. 在 Azure Web 应用中创建 Java 应用程序

    本分步指南将通过 Azure Web 应用帮助您启动并运行示例 Java 应用程序.除 Java 外,Azure Web 应用还支持其他语言,如 PHP..NET.Node.JS.Python.Rub ...

  3. 洛谷P1966 火柴排队(逆序对)

    题意 题目链接 Sol 不算很难的一道题 首先要保证权值最小,不难想到一种贪心策略,即把两个序列中rank相同的数放到同一个位置 证明也比较trivial.假设\(A\)中有两个元素\(a, b\), ...

  4. css3 border-image及连续的图像边框

    border-image 它是下面几个值的简写: border-image-source // 使用绝对或相对地址url,引入图片 border-image-slice   //切割图片,取值支持:& ...

  5. Git回退到指定节点的版本

    1.获取某个历史版本的id(即change-id,每个版本唯一) 方法1:使用git log命令查看所有的历史版本,输入q便可退出. git log 方法2:使用gitk图形化界面查看节点信息.(在安 ...

  6. SharePoint 2013 - Add-ins

    1. App Web & Host Web The special website to which the app is deployed is called an App Web. The ...

  7. ArcGIS软件操作——地图制图

    ArcGIS软件操作系列二(地图制图) 2016年毕业,参加工作,除了平时出差,大部分时间都在使用ArcGIS处理数据.制图,在此,先将一些制图的小心得撰写出来,希望能与各位共同交流. 1 数据准备: ...

  8. ViewPager+fragment的使用

    如图我在一个继承FragmentActivity的类中嵌套了3个fragment分别能实现3个不同的界面,默认展现第一个,在第一个的fragment中有个ViewPager在ViewPager中嵌套了 ...

  9. CSS3中的Flexbox弹性布局(二)

    flexbox详解 flexbox的出现是为了解决复杂的web布局,因为这种布局方式很灵活.容器的子元素可以任意方向进行排列.此属性目前处于非正式标准. flex布局模型不同于块和内联模型布局,块和内 ...

  10. Siebel 基础入门--权限控制

    企业应用最基本的要求就是只授予用户在他工作职责范围内的权限,一般而言,这种权限都包含两种,一种是对于相应的功能的可见性(或者形象地说,菜单的可见 性,这个是应用层面界面的,这种权限在Siebel里称为 ...