JavaScript 中的执行上下文和执行栈实例讲解


Posted in Javascript onFebruary 25, 2021

JavaScript - 原理系列

​ 在日常开发中,每当我们接手一个现有项目后,我们总喜欢先去看看别人写的代码。每当我们看到别人写出很酷的代码的时候,我们总会感慨!写出这么优美而又简洁的代码的兄弟到底是怎么养成的呢?

​ 我要怎样才能达到和大佬一样的水平呢!好了,废话不多说,让我们切入今天的主题。

一、执行上下文

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

​ 在运行JavaScript代码时,每当需要执行代码时,执行代码会先进入一个环境(浏览器、Node客户端),这时就会为该环境创建一个执行上下文,它会在你运行代码前做一些准备工作,如确定作用域,创建全局、局部变量对象等。

执行上下文的分类

  • 全局执行上下文:

​ 这是默认的、最基础的执行上下文。不在任何函数中的代码都位于全局执行上下文中。

它做了两件事:

  • 创建一个全局对象,在浏览器中这个全局对象就是 window 对象。

this 指针指向这个全局对象。一个程序中只能存在一个全局执行上下文。

  • 函数执行上下文:

​ 每次调用函数时,都会为该函数创建一个新的执行上下文。每个函数都拥有自己的执行上下文,但是只有在函数被调用的时候才会被创建。一个程序中可以存在任意数量的函数执行上下文。每当一个新的执行上下文被创建,它都会按照特定的顺序执行一系列步骤,具体过程将在本文后面讨论。

  • Eval 函数执行上下文:

​ 运行在 eval 函数中的代码也获得了自己的执行上下文,但由于 Javascript 开发人员不常用 eval 函数,所以在这里不再讨论。

执行上下文的数量限制(堆栈溢出)

​执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景。

下面是示例代码:

// 递归调用自身
function foo() {
  foo();
}
foo();
// 报错:Uncaught RangeError: Maximum call stack size exceeded

Tips:

​ JS是“单线程”的,每次只执行一段代码

二、执行栈

​ JS中的执行栈,也就是在其它编程语言中所说的“调用栈”,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。

​ 当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部。

​ 引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。

栈数据结构

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 中的执行上下文和执行栈实例讲解

​ 当上述代码在浏览器加载时,浏览器的JavaScript 引擎会创建一个全局执行上下文并把它压入当前执行栈。当遇到函数调用时,JavaScript 引擎为该函数创建一个新的执行上下文并把它压入当前执行栈的顶部。

当从 first()函数内部调用 second()函数时,JavaScript 引擎为 second() 函数创建了一个新的执行上下文并把它压入当前执行栈的顶部。当 second()函数执行完毕,它的执行上下文会从当前栈弹出,并且控制流程到达下一个执行上下文,即 first() 函数的执行上下文。

​ 当 first() 执行完毕,它的执行上下文从栈弹出,控制流程到达全局执行上下文。一旦所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文。

The Creation Phase

​在 JavaScript 代码执行前,执行上下文将经历创建阶段。在创建阶段会发生三件事:

  1. this 值的决定,即我们所熟知的 This 绑定。
  2. 创建词法环境组件。
  3. 创建变量环境组件。

所以执行上下文在概念上表示如下:

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

This 绑定:

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

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

let foo = {
 baz: function() {
 console.log(this);
 }
}
foo.baz();  // 'this' 引用 'foo', 因为 'baz' 被
       // 对象 'foo' 调用
let bar = foo.baz;
bar();    // 'this' 指向全局 window 对象,因为
       // 没有指定引用对象

词法环境

官方的 ES6 文档把词法环境定义为

​ 词法环境是一种规范类型,基于 ECMAScript 代码的词法嵌套结构来定义标识符和具体变量和函数的关联。一个词法环境由环境记录器和一个可能的引用外部词法环境的空值组成。


​ 简单来说词法环境是一种持有标识符—变量映射的结构。(这里的标识符指的是变量/函数的名字,而变量是对实际对象[包含函数类型对象]或原始数据的引用)。

​ 现在,在词法环境的内部有两个组件:(1) 环境记录器和 (2) 一个外部环境的引用。

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

词法环境有两种类型:

  • 全局环境(在全局执行上下文中)是没有外部环境引用的词法环境。全局环境的外部环境引用是 null。它拥有内建的 Object/Array/等、在环境记录器内的原型函数(关联全局对象,比如 window 对象)还有任何用户定义的全局变量,并且 this的值指向全局对象。
  • 在函数环境中,函数内部用户定义的变量存储在环境记录器中。并且引用的外部环境可能是全局环境,或者任何包含此内部函数的外部函数。

环境记录器也有两种类型(如上!):

  1. 声明式环境记录器存储变量、函数和参数。
  2. 对象环境记录器用来定义出现在全局上下文中的变量和函数的关系。

简而言之

  • 在全局环境中,环境记录器是对象环境记录器。
  • 在函数环境中,环境记录器是声明式环境记录器。

注意

​对于函数环境,声明式环境记录器还包含了一个传递给函数的 arguments 对象(此对象存储索引和参数的映射)和传递给函数的参数的 length。

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

GlobalExectionContext = {
 LexicalEnvironment: {
  EnvironmentRecord: {
   Type: "Object",
   // 在这里绑定标识符
  }
  outer: <null>
 }
}

FunctionExectionContext = {
 LexicalEnvironment: {
  EnvironmentRecord: {
   Type: "Declarative",
   // 在这里绑定标识符
  }
  outer: <Global or outer function environment reference>
 }
}

变量环境:

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

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

​ 在 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);

执行上下文看起来像这样:

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 时,函数执行上下文才会被创建。

可能你已经注意到 letconst 定义的变量并没有关联任何值,但 var 定义的变量被设成了 undefined

​这是因为在创建阶段时,引擎检查代码找出变量和函数声明,虽然函数声明完全存储在环境中,但是变量最初设置为 undefinedvar 情况下),或者未初始化(letconst 情况下)。

​这就是为什么你可以在声明之前访问 var 定义的变量(虽然是 undefined),但是在声明之前访问 letconst 的变量会得到一个引用错误。

这就是我们说的变量声明提升。

执行阶段

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

注意

​ 在执行阶段,如果 JavaScript 引擎不能在源码中声明的实际位置找到 let 变量的值,它会被赋值为 undefined

结论

​我们已经讨论过 JavaScript 程序内部是如何执行的。虽然要成为一名卓越的 JavaScript 开发者并不需要学会全部这些概念,但是如果对上面概念能有不错的理解将有助于你更轻松,更深入地理解其他概念,如变量声明提升,作用域和闭包。

参考文章:

https://juejin.cn/post/6844903682283143181

https://www.jianshu.com/p/6f8556b10379

https://juejin.cn/post/6844903704466833421

到此这篇关于JavaScript 中的执行上下文和执行栈实例讲解的文章就介绍到这了,更多相关JavaScript 中的执行上下文和执行栈内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
基于Jquery的仿Windows Aero弹出窗(漂亮的关闭按钮)
Sep 28 Javascript
一个基于jQuery的树型插件(OrangeTree)使用介绍
May 03 Javascript
移动设备web开发首选框架:zeptojs介绍
Jan 29 Javascript
JavaScript从数组中删除指定值元素的方法
Mar 18 Javascript
jQuery获取上传文件的名称的正则表达式
May 21 Javascript
基于Bootstrap的UI扩展 StyleBootstrap
Jun 17 Javascript
node通过npm写一个cli命令行工具
Oct 12 Javascript
React Native中TabBarIOS的简单使用方法示例
Oct 13 Javascript
Node.js 使用AngularJS的方法示例
May 11 Javascript
React Native中Mobx的使用方法详解
Dec 04 Javascript
如何实现小程序tab栏下划线动画效果
May 18 Javascript
Antd的table组件表格的序号自增操作
Oct 27 Javascript
解决await在forEach中不起作用的问题
Feb 25 #Javascript
JS实现百度搜索框
Feb 25 #Javascript
基于JavaScript实现随机点名器
Feb 25 #Javascript
JavaScript仿京东轮播图效果
Feb 25 #Javascript
Vue基本指令实例图文讲解
Feb 25 #Vue.js
使用webpack和rollup打包组件库的方法
Feb 25 #Javascript
vue常用高阶函数及综合实例
Feb 25 #Vue.js
You might like
Windows IIS PHP 5.2 安装与配置方法
2009/06/08 PHP
浅析PHP安装扩展mcrypt以及相关依赖项(PHP安装PECL扩展的方法)
2013/07/05 PHP
php分页函数示例代码分享
2014/02/24 PHP
PHP定时任务获取微信access_token的方法
2016/10/10 PHP
PHP中SERIALIZE和JSON的序列化与反序列化操作区别分析
2016/10/11 PHP
Ubuntu 16.04中Laravel5.4升级到5.6的步骤
2018/12/07 PHP
laravel model 两表联查示例
2019/10/24 PHP
PHP 对象继承原理与简单用法示例
2020/04/21 PHP
iframe 上下滚动条如何默认在下方实现原理
2012/12/10 Javascript
JS实现在网页中弹出一个输入框的方法
2015/03/03 Javascript
nodejs中的fiber(纤程)库详解
2015/03/24 NodeJs
jQuery scrollFix滚动定位插件
2015/04/01 Javascript
省市联动效果的简单实现代码(推荐)
2016/06/06 Javascript
d3.js实现简单的网络拓扑图实例代码
2016/11/06 Javascript
Node.js中如何合并两个复杂对象详解
2016/12/31 Javascript
最常用的jQuery表单验证(简单)
2017/05/23 jQuery
详解EasyUi控件中的Datagrid
2017/08/23 Javascript
浅谈vue的踩坑路
2017/08/31 Javascript
JavaScript实现数字前补“0”的五种方法示例
2019/01/03 Javascript
js实现数字从零慢慢增加到指定数字示例
2019/11/07 Javascript
解决vue2中使用elementUi打包报错的问题
2020/09/22 Javascript
js实现星星海特效的示例
2020/09/28 Javascript
通过cmd进入python的步骤
2020/06/16 Python
Python基于内置函数type创建新类型
2020/10/22 Python
celery在python爬虫中定时操作实例讲解
2020/11/27 Python
Python调用SMTP服务自动发送Email的实现步骤
2021/02/07 Python
HTML5 canvas 基本语法
2009/08/26 HTML / CSS
英国汽车和货车租赁网站:Hertz英国
2016/09/02 全球购物
中国最大的潮流商品购物网站:YOHO!BUY有货
2017/01/07 全球购物
Chain Reaction Cycles芬兰:世界上最大的在线自行车商店
2017/12/06 全球购物
在印度上传处方,在线订购药品:Medlife
2019/03/28 全球购物
业务员岗位职责范本
2013/12/15 职场文书
电台编导求职信
2014/05/06 职场文书
消防安全标语
2014/06/07 职场文书
看上去很美观后感
2015/06/10 职场文书
2016年教代会开幕词
2016/03/04 职场文书