0

0

了解JS中的执行上下文、 执行栈、事件循环

青灯夜游

青灯夜游

发布时间:2020-07-06 10:39:17

|

2297人浏览过

|

来源于csdn

转载

本篇文章带大家了解一下javascript中的执行上下文和执行栈、事件循环。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

了解JS中的执行上下文、 执行栈、事件循环

下面的这些概念,无论是执行上下文、 还是执行栈,它在规范中的概念都很抽象,很多内容的理解实际靠的都是想象力,若有错误之处,还请指正。

执行上下文

简而言之,执行上下文(Execution Context)是正在运行的可执行代码所处环境的抽象,用于追踪一段代码中变量的求值。这是我总结过来的概念,可能有些不准确,也可以参考真正的规范定义。

不过总的来说,有三个关键点:

  • 只有可执行代码才会有执行上下文

  • 执行上下文是有状态的:运行状态(Perform)、挂起状态(Suspend)以及恢复(Resume)。处于Perfrom状态的执行上下文称之为运行时执行上下文(Running Execution Context)

  • 执行上下文完全不等价于词法环境,硬说关系,也只是前者引用了后者而已。

  • 执行一个JS脚本时,可以有多个执行上下文存在,但是 运行时上下文只有唯一一个(异步也是如此,至于为什么提了四个……三大天王有四个不是常识么……)。

并且ES规范中规定,可执行代码有下面几个:

  • 全局代码

  • 函数代码

  • Eval语句

  • 模块代码

换言之,看以下代码:

  
  var g=111
  function f(){
      
      console.log(g);
      for(let i =0; i < 10; i++){
          console.log(i);
      }
  }
  f();  /// (*)

如果上面的代码运行,只会产生两个执行上下文:

  • 全局

  • 函数f

但是如果将标注(*)的行注释掉,那么最终只有一个执行上下文,因为函数f根本就不会执行,自然就不会有相应的执行上下文了。里面唯一一个迷惑的是,就是for-loop了,但它根本不是可执行代码,所以它是函数执行上下文的一部分

执行上下文的重要组成部分

一个执行上下文,可以抽象为:

ExecutionContext = {
    State: <>
    LexEnv = {
        This:< ... > ,
        OuterEnv:< ... > ,
        DecRec:{
            //... identifiername-variable
        }      
    }
    VaEnv = {
        This:< ... > ,
        OuterEnv:< ... > ,
        VarRec:{
            //... identifiername-variable
        }      
    }
}

事实上,在一个执行上下文中有两个相当重要的组件:LexicalEnvironmentComponent(词法环境组件)和VariableEnvironmentComponent(变量环境组件)。词法环境组件指向当前代码中的词法环境LexEnv), 变量环境组件指向当前代码变量环境VarEnv)。

关于执行上下文不得不说的二三事中,有一个很重要的部分就是作用域链,但是在执行上下文中并没有看到相关内容。不过作用域链的确存在,它就在[[Scope]]内部属性中,通过浏览器可以直接看到。

不过也可以这样理解,在一个执行上下文被创建时,不仅会创建当前词法环境的LexEnv,也会创建LexEnv.OutEnvLexEnv.OutEnv.OutEnv…,直至延伸到全局为止。

执行上下文的创建与销毁

1、创建一个新执行上下文(ExecutionContext , EC)

2、创建当前词法环境(LexEnv 和 VarEnv )

3、将该执行上下文的 LexicalEnvironmentComponent 和 VariableEnvironmentComponent 指向当前环境下的 LexEnvVarEnv中。

4、将新执行上下文推入 执行栈中,并成为运行时执行上下文

5、对可执行代码块内的标识符进行实例化和初始化:

  • 收集当前词法环境内所有声明的标识符归入DecRec中,所有var声明的标识符归入VarNames集合中,在此阶段会进行标识符名检测,若与let/const/...声明的标识符与VarNames中的标识符重复, 报错。

  • DecRec中的标识符进行实例化,并设为uninitializedVarNames中的标识符绑定在ObjRec中,实例化后直接初始化为undefined

  • 对于function声明的函数,将直接指向函数对象,并也会绑定到ObjRec中,这是浏览器默认行为

    Shell脚本编写基础 中文WORD版
    Shell脚本编写基础 中文WORD版

    Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。它虽然不是Linux系统核心的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Linux系统

    下载

6、运行代码。

  • 非var声明的标识符会在声明处进行初始化(默认为undefined)。

  • 完成所有变量的赋值,并可能会一直在变化。

7、运行完毕从 执行栈 中弹出。

备注:

  • 关于This绑定,大部分情况可以用过去的说法解释,然而某些情况下却不尽然。
  • 闭包我会在下一篇介绍。
  • 执行上下文,我个人认为并不如何重要,但是却能在许多情形下起到极为关键的作用,所以还是有必要去深入认识一下。
  • 关于执行上下文和词法环境的关系,最多是前者引用了后者,仅此而已。诚然,有许多情况没必要用执行上下文来说明,但是永远避免不了违和感

执行栈与事件循环

执行栈(Execution Stack)就是由执行上下文构成的堆栈,类似于Call Stack

1、当Javascript引擎遇到一段可执行代码时,新建一个执行上下文。

2、将它推入执行栈中。并设置为运行时执行上下文。

  • 如果存在其他执行上下文。
  • 那么将当前执行上下文挂起
  • 然后再将新执行上下文推入执行栈中。

3、执行上下文运行完毕,弹出销毁恢复并将原执行上下文设为运行时。

总觉得这些没什么好说的,但是水一下吧

执行栈最重要的部分并非是执行栈概念本身,而是与任务队列的关系,它是事件循环的入门关键概念之一

众所周知,Javascript语言是单线程的,此处的执行栈就相当于主线程的调用栈,也是唯一一个调用栈,至于什么是主线程可以查阅相关资料,这里有些超纲了……

那么javascript是如何实现异步的

确切来说,这不是Javascript核心的部分,它是结合浏览器API(如Web Worker, Browser-context了解一下)实现的

事件循环中(事件处理过程),有两个极其重要的概念:

  • 任务序列: Task Quenue
  • 事件: Event

这两个概念,是抽象滴。

在Javascript中,一个任务也可以称之为事件,通常是一个函数回调,由许多任务组成的队列,就是所谓的任务序列了。任务序列有很多分类,例如:作业序列(Job Quenue)、消息序列(Message Quenue),本质没区别。

不必再深入了解,现在需要记住的是:一个任务序列中的任务如果想要被执行,就必须将它取出放入执行栈中。

举一个抽象点的例子:

例如下面的代码:

  
      var temp = 10;
      
      console.log('push task1');
      setTimeout(function task1(){
        temp+=10;
        console.log(temp+'task1 okay! ');
      },1000)
      
      console.log('taskquenue=[task1]; push task2');
      setTimeout(function task2(){
        temp*=10;
        console.log(temp+'task2 okay! ');
      },500) 
     
      console.log('taskquenue=[task1,task2]; push task3');
      setTimeout(function task3(){
        temp*= -0.2;
        console.log(temp+'task3 okay! ');
      },1500)
      console.log('taskquenue=[task1, task2,task3]');

输出如下:

push task1
taskquenue=[task1]; push task2
taskquenue=[task1,task2]; push task3
taskquenue=[task1, task2,task3]
100task2 okay! 
110task1 okay! 
-22task3 okay!

setTimeout是一个定时器,它能够将任务放到任务队列中。如图:

  • 添加作业task1
    在这里插入图片描述
  • 添加作业task2
    在这里插入图片描述
  • 添加作业task3
    在这里插入图片描述

执行到此处, task1task2task3都被放入了任务队列; 然后执行栈全部执行完毕后,开始处理任务队列中的任务。

为什么任务队列中的任务必须在执行栈空时后执行呢?

  • 这里我也不清楚,这毕竟真正涉及了底层内容;只是有些明白为何这样,不过担心误导他人,就算了。
  • 一般而言, 关于任务序列的相关概念没有如此简单,还涉及了很多东西,例如阻塞调度(Schedule)等,这些方面可以参考其他诸如C++Java这类多线程的语言或是看看操作系统这方面的内容。
  • 如果只是简单的业务实现,不需要了解这么多底层的东西。

现在开始处理任务吧:

  • 处理task2
    在这里插入图片描述
  • 处理task1
    在这里插入图片描述
  • 处理task3
    在这里插入图片描述

好了,一个事件循环就这么结束了。
然后Javascript引擎进入休眠阶段Javascript引擎永不结束!),等待有新的任务执行,然后开始下一个事件循环。

备注:
  • 这里只是一个简单例子
  • 事件循环可以有多个任务队列
  • 任务序列共分为两种: 微任务序列和宏任务序列
  • 我们的script代码,就是宏任务序列之一。

最后: Javascript引擎

这是我精读Javascript系列第三篇,猝不及防的就到了事件循环,看起来一下子就深入好多好多…… 但是我觉得这才是最合理程安排,大多的文档都把任务序列调用栈给分开了,但是在~~_____~~,它们本应该就是一体,不应该以任何方便的理由将它们分开

深入Javascript, 除了看规范,最好也看下JS引擎的实现文档,有些进阶内容,不在规范中,而是在这些文档里(请自己Google查找,百度也能搜索到)。

如果对Javascript引擎比较感兴趣的,可以参考:

  • MDN About_Javascript
  • Chromium V8参考
  • Github V8

虽然,极其不建议新手一下子看V8源代码,那种头痛欲裂又浑身颤抖不止的感觉实在是过瘾之极啊……

相关教程推荐:JavaScript视频教程

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
mysql标识符无效错误怎么解决
mysql标识符无效错误怎么解决

mysql标识符无效错误的解决办法:1、检查标识符是否被其他表或数据库使用;2、检查标识符是否包含特殊字符;3、使用引号包裹标识符;4、使用反引号包裹标识符;5、检查MySQL的配置文件等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

183

2023.12.04

Python标识符有哪些
Python标识符有哪些

Python标识符有变量标识符、函数标识符、类标识符、模块标识符、下划线开头的标识符、双下划线开头、双下划线结尾的标识符、整型标识符、浮点型标识符等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

287

2024.02.23

java标识符合集
java标识符合集

本专题整合了java标识符相关内容,想了解更多详细内容,请阅读下面的文章。

259

2025.06.11

c++标识符介绍
c++标识符介绍

本专题整合了c++标识符相关内容,阅读专题下面的文章了解更多详细内容。

125

2025.08.07

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

531

2023.09.20

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

397

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

575

2023.08.10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

397

2023.07.18

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

9

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 4.4万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.6万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号