理解 Javascript 执行上下文和执行栈

如果你是一名 JavaScript 开发者,或者想要成为一名 JavaScript 开发者,那么你必须知道 JavaScript 程序内部的执行机制。理解执行上下文和执行栈同样有助于理解其他的 JavaScript 概念如提升机制、作用域和闭包等。

正确理解执行上下文和执行栈的概念将有助于你成为一名更好的 JavaScript 开发人员。

废话不多说,让我们切入正题。

什么是执行上下文

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

执行上下文的类型

执行上下文总共有三种类型:

  • 全局执行上下文:?这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。它做了两件事:1. 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。2. 将?this?指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。
  • 函数执行上下文:?每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤,具体过程将在本文后面讨论。
  • Eval 函数执行上下文:?运行在?eval?函数中的代码也获得了自己的执行上下文,但由于 Javascript 开发人员不常用 eval 函数,所以在这里不再讨论。

执行栈

执行栈,在其他编程语言中也被叫做调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。

当 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‘);

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

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

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

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

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

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

创建阶段

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

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

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

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

This Binding:

在全局执行上下文中,?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?对象如下所示:

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>
}
}

变量环境:

它也是一个词法环境,其?EnvironmentRecord?包含了由?VariableStatements?在此执行上下文创建的绑定。

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

在 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?的调用时才会创建函数执行上下文。

你可能已经注意到了?let?和?const?定义的变量没有任何与之关联的值,但?var?定义的变量设置为?undefined?。

这是因为在创建阶段,代码会被扫描并解析变量和函数声明,其中函数声明存储在环境中,而变量会被设置为?undefined?(在?var?的情况下)或保持未初始化(在?let?和?const?的情况下)。

这就是为什么你可以在声明之前访问?var?定义的变量(尽管是?undefined?),但如果在声明之前访问?let?和?const?定义的变量就会提示引用错误的原因。

这就是我们所谓的变量提升。

执行阶段

这是整篇文章中最简单的部分。在此阶段,完成对所有变量的分配,最后执行代码。

注:在执行阶段,如果 Javascript 引擎在源代码中声明的实际位置找不到?let?变量的值,那么将为其分配?undefined?值。

总结

我们已经讨论了 JavaScript 内部是如何执行的。虽然你没有必要学习这些所有的概念从而成为一名出色的 JavaScript 开发人员,但对上述概念的理解将有助于你更轻松、更深入地理解其他概念,如提升、域和闭包等。

原文链接:https://www.jianshu.com/p/436...

原文地址:https://segmentfault.com/a/1190000016912433

原文地址:https://www.cnblogs.com/lalalagq/p/9943498.html

时间: 2024-10-13 11:46:32

理解 Javascript 执行上下文和执行栈的相关文章

JavaScript进阶之执行上下文和执行栈

js引擎的执行过程 执行上下文和执行栈属于js引擎的执行过程的预编译阶段. 执行上下文(Execution Context) 执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念.可以理解为当执行代码时做的准备工作. 执行上下文按照运行环境被分成三类: 全局执行上下文(JS代码加载完毕后,进入代码预编译即进入全局环境) 函数环境执行上下文(函数调用执行时,进入该函数环境,不同的函数则函数环境不同) eval执行上下文(不建议使用,会有安全,性能等问题) 对于每个执行上下文

深入理解javascript执行上下文(Execution Context)

本文转自:http://blogread.cn/it/article/6178 在这篇文章中,将比较深入地阐述下执行上下文 - Javascript中最基础也是最重要的一个概念.相信读完这篇文章后,你就会明白javascript引擎内部在执行代码以前到底做了些什么,为什么某些函数以及变量在没有被声明以前就可以被使用,以及它们的最终的值是怎样被定义的. 什么是执行上下文 Javascript中代码的运行环境分为以下三种: 全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的

前端进击的巨人(一):执行上下文与执行栈,变量对象

写在开篇 已经不敢自称前端小白,曾经吹过的牛逼总要一点点去实现. 正如前领导说的,自己喝酒吹过的牛皮,跪着都得含着泪去实现. 那么没有年终完美总结,来个新年莽撞开始可好. 进击巨人系列开篇,不忘初心,砥砺前行. 理解执行上下文 执行上下文(Execution Context): 函数执行前进行的准备工作(也称执行上下文环境) 运行JavaScript代码时,当代码执行进入一个环境时,就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作,如确定作用域,创建局部变量对象等. 具体做了什么

理解Javascript的异步

前言 本文2925字,阅读大约需要10分钟. 总括: 本文梳理了异步代码和同步代码执行的区别,Javascript的事件循环,任务队列微任务队列等概念. 原文地址:Understanding Asynchronous JavaScript 公众号:「前端进阶学习」,回复「666」,获取一揽子前端技术书籍 未曾失败的人恐怕也未曾成功过. Javascript是单线程的编程语言,单线程就是说同一时间只能干一件事.放到编程语言上来说,就是说Javascript引擎(执行Javascript代码的虚拟机

JavaScript执行机制

原文 简书原文:https://www.jianshu.com/p/0d2d42fbe1dc 大纲 1.场景分析 2.执行机制相关知识点 3.以实例来说明JavaScript的执行机制 4.相关概念 1.场景分析 /* 以下这段代码的执行结果是什么? 如果依照:js是按照语句出现的顺序执行这个理念, 那么代码执行的结果应该是: //"定时器开始啦" //"马上执行for循环啦" //"执行then函数啦" //"代码执行结束"

关于javascript中的 执行上下文和对象变量

什么是执行上下文 当浏览器的解释器开始执行我们的js代码的时候,js代码运行所处的环境可以被认为是代码的执行上下文,执行上下文(简称-EC)是ECMA-262标准里的一个抽象概念,用于同可执行代码(executable code)概念进行区分.一般来讲,执行上下文可以在以下三种情况产生: 1. 全局上下文(globalContext)   2. function 内部 3. Eval code. 看个例子,包含全局和function内部上下文 紫色框内表示全局的执行上下文,同时内部会有3个不同的

深入理解JavaScript系列(11):执行上下文(Execution Contexts)

简介 从本章开始,我将陆续(翻译.转载.整理)http://dmitrysoshnikov.com/网站关于ECMAScript标标准理解的好文. 本章我们要讲解的是ECMAScript标准里的执行上下文和相关可执行代码的各种类型. 原始作者:Dmitry A. Soshnikov 原始发布: 2009-06-26 俄文原文:http://dmitrysoshnikov.com/ecmascript/ru-chapter-1-execution-contexts/ 英文翻译:Dmitry A.

理解JavaScript的执行机制

一直没有深入了解过JavaScript的事件执行机制,直到看到了这篇文章:<这一次,彻底弄懂JavaScript执行机制> 才发觉熟悉JavaScript的执行机制非常重要. 毕竟在跟进项目中偶尔需要排查为什么会出现函数执行顺序不一样的情况. 感谢作者浅显易懂的文字让我获益匪浅,以下是自己对JavaScript执行机制的理解,全是流水账. 文章主要叙述: 1:单线程和异步任务 2: 异步任务的分类 3:setTimeout 和 setInterval 的执行方式 单线程和异步任务 JavaSc

深入理解JavaScript系列+ 深入理解javascript之执行上下文

http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html http://blog.csdn.net/hi_kevin/article/details/37761919    深入理解javascript之执行上下文(execution context)