目录
栈和队列
单线程和异步
javascript是怎么执行的?
浏览器中的事件环
Node中的事件环
尾巴
首页 web前端 js教程 Javascript事件环该如何理解?(图文)

Javascript事件环该如何理解?(图文)

Sep 08, 2018 pm 05:46 PM
javascript node.js

本篇文章给大家带来的内容是关于Javascript事件环该如何理解?(图文),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

栈和队列

在计算机内存中存取数据,基本的数据结构分为栈和队列。

栈(Stack)是一种后进先出的数据结构,注意,有时候也管栈叫做“堆栈”,但是“堆”又是另一种复杂的数据结构,它和栈完全是两码事。栈的特点是操作只在一端进行,一般来说,栈的操作只有两种:进栈和出栈。第一个进栈的数据总是最后一个才出来。

228930078-5b927d2d18e12_articlex.jpg

队列(Queue)和栈类似,但它是先进先出的数据结构,插入数据的操作从队列的一端进行,而删除的操作在另一端。

3800084321-5b927d3888aef_articlex.jpg

通俗的比喻栈就像是一个立好的桶,先放入栈的数据会放在桶底,出栈时会在桶口一一将数据取出,所以最先放入栈的数据总是最后一个才能取出。而队列就像是一个水管,最先放入队列的数据会第一个从队列的另一端流出,这是它们最大的区别。

在javascript中,函数的执行就一个典型的入栈与出栈的过程:

function fun1() {
    function fun2() {
        function fun3() {
            console.log('do it');
        }
        fun3();
    }
    fun2();
}
fun1();
登录后复制

在程序执行时,首先将fun1,fun2,fun3依次入栈,而在调用函数时,是先将fun3调用(出栈),再是fun2和fun1,试想一下,如果fun1先出栈,那么函数fun2和fun3必将丢失。

单线程和异步

在javascript这门语言中程序是单线程的,只有一个主线程,这是为什么?因为不难想像,最初javascript的设计是跑在浏览器中的脚本语言,如果设计成多线程,两个线程同时修改DOM那以谁的为准呢?所以javascript为单线程,在一个线程中代码会一句一句向下走,直到程序跑完,若中间有较为费时的操作,那也只能等着。

单线程的设计使得语言的执行效率很差,为了利用多核心CPU的性能,javascript语言支持异步代码,当有较为费时的操作时,可将任务写为异步执行,当一个异步任务还没有执行完时,主线程会将异步任务挂起,继续执行后面的同步代码,之后再回过头来看,如果有异步任务运行完了再执行它。

这种执行代码的方式其实很符合我们生活中的很多场景,比如小明同学下班回家了,他很渴,想烧水泡茶,如果是同步的执行方式那就是烧水,在水没开时小明像个傻子似的等着,等水开了再泡茶;若是异步执行,小明先开始烧水,然后就去干点别的事,比如看会电视、听听音乐,等水烧开了再去泡茶。明显第二种异步方式效率更高。

常见的异步操作都有哪些?有很多,我们可以罗列几个常见的:

  • Ajax

  • DOM的事件操作

  • setTimeout

  • Promise的then方法

  • Node的读取文件

我们先来看一段代码:

//示例1
console.log(1);
setTimeout(function () {
    console.log(2);
}, 1000);
console.log(3);
登录后复制

这段代码非常简单,把它们放在浏览器中执行结果如下:

1
3
2
登录后复制
登录后复制

因为setTimeout函数延时了1000毫秒执行,因此先输出1和3,而2是过了1000毫秒之后再输出,这很合逻辑。

我们稍稍改动一下代码,将setTimeout的延时时间改为0:

//示例2
console.log(1);
setTimeout(function () {
    console.log(2);
}, 0); //0毫秒,不延时
console.log(3);
登录后复制

运行结果:

1
3
2
登录后复制
登录后复制

为什么延时了0毫秒还是最后输出的2?先别急,我们再来看一段代码:

//示例3
console.log(1);
setTimeout(function () {
    console.log(2);
}, 0);
Promise.resolve().then(function(){
    console.log(3);
});
console.log(4);
登录后复制

运行结果:

1
4
3
2
登录后复制

以上三段代码,如果你能正确的写出结果,并且能说明白为什么这样输出,说明你对javascript的事件环理解的很清楚,如果讲不出来,我们就一起聊聊这里面发生了什么,其实很有意思。

javascript是怎么执行的?

一开始先简单聊了聊基本的数据结构,它和我们现在说的事件环有什么关系么?当然有,首先要明确的一点是,javascript代码的执行全都在栈里,不论是同步代码还是异步代码,这个一定要清楚。

而代码我们大体上分为了同步代码和异步代码,其实异步代码还可以再分为两类:宏任务微任务

先别管什么是宏任务和微任务,往往这种高大上的术语不利于我们理解,我们先这么认为:宏,即是宏观的、大的;微即微观的、小的。

javascript是解释型语言,它的执行过程是这样的:

  1. 从上到下依次解释每一条js语句

  2. 若是同步任务,则压入一个栈(主线程);如果是异步任务,就放到一个任务队列里

  3. 开始执行栈里的同步任务,直到将栈里的所有任务都走完,此时栈清空了

  4. 回过头看异步队列里如果有异步任务完成了,就生成一个事件并注册回调,压入栈中

  5. 再返回第3步,直到异步队列都清空,程序运行结束

语言描述的费劲,不如看图:

2349316301-5b927d54852a8_articlex.png

通过以上的步骤可以看到,不论是同步还是异步,只要是执行的时候都是要在栈里执行的,而一遍又一遍的回头检查异步队列,这种执行方式 就是所谓的“事件环”。

明白了javascript的执行原理,我们就不难理解之前的第二段代码,为什么setTimeout为0时会最后执行,因为setTimeout是异步代码,必须要等所有的同步代码都执行完,才会执行异步队列。即使setTimeout执行得再快,它也不可能在同步代码之前执行。

浏览器中的事件环

聊了这么多,我们好像还没有说宏任务和微任务的话题呢,上面说了,异步任务又分为微任务和宏任务,那它们又是一个怎样的执行机制呢?

注意!微任务和宏任务的执行方式在浏览器和Node中有差异,有差异!重要的事我们多说几遍,以下我们讨论的是在浏览器的环境里。

在浏览器的执行环境中,总是先执行小的、微任务,再执行大的、宏任务,回过头再看看第三段代码,为什么Promise的then方法在setTimeout之前执行?其根本原理就是因为Promise的then方法是一个微任务,而setTimeout是一个宏任务。

接下来我们借用阮一峰老师的一张图来说明:

3367633254-55e66d3548437_articlex.png

其实,以上这张图示我们可以再将它细化一点,这个图上的异步队列只画了一个,也就是说没有区分微任务队列和宏任务队列。我们可以脑补一下,在此图上多加一个微任务队列,当javascript执行时再多加一个判断,如果是微任务就加到微任务队列里,宏任务就加到宏任务队列里,在清空队列时,浏览器总会优先清空“微任务”。这样就把浏览器的事件环撤底说全了。

最后来一个大考,以下代码的运行结果是什么:

<script type="text/javascript">
    setTimeout(function () {
        console.log(1);
        Promise.resolve().then(function () {
            console.log(2);
        });
    });
    setTimeout(function () {
        console.log(3);
    });
    Promise.resolve().then(function () {
        console.log(4);
    });
    console.log(5);
</script>
登录后复制

将此代码拷到chrome中跑一下,结果是:

5
4
1
2
3
登录后复制

不妨我们试着分析一下为什么是这个结果,首先输出5,因为console.log(5)是同步代码,这没什么可说的。

之后将前两个setTimeout和最后一个Promise放入异步队列,注意它们的区分,此时执行完了同步代码之后发现微任务和宏任务队列中都有代码,按浏览器的事件环机制,优先执行微任务,此时输出4。

然后执行宏任务队列里的第一个setTimeout,输出1。

此时,setTimeout中又有一个Promise,放入微任务队列。

再次清空微任务队列,输出2。

最后宏任务队列里还有最后一个setTimeout,输出3。

Node中的事件环

而Node中的事件环又和浏览器有些许的不同,在node.js的官方文档中有专门的描述,其中文档中有一张图,详细的说明了它的事件环机制,我们把它拿出来:

3169881340-5b927d849391a_articlex.png

可以看到,node.js中的事件环机制分为了6个阶段,其中最重要的3个阶段我在上面做了注明:

  • timer阶段,指的就是setTimeout等宏任务

  • poll轮询阶段,如读取文件等宏任务

  • check阶段,setImmediate宏任务

图中每一个阶段都代表了一个宏任务队列,在Node事件环中,微任务的运行时机是在每一个“宏任务队列”清空之后,在进入下一个宏任务队列之间执行。这是和浏览器的最大区别。

还是用代码说话吧,有一道经典的Node.js事件环面试题:

const fs = require('fs');

fs.readFile('./1.txt', (err, data) => {
    setTimeout(() => {
        console.log('timeout');
    });
    setImmediate(() => {
        console.log('immediate');
    });
    Promise.resolve().then(() => {
        console.log('Promise');
    });
});
登录后复制

运行结果:

Promise
immediate
timeout
登录后复制

代码并不复杂,首先使用fs模块读取了一个文件,在回调的内部有两个宏任务和一个微任务,微任务总是优于宏任务执行的,因此先输出Promise。

但是之后的区别为什么先输出immdiate?原因就在于fs读取文件的宏任务在上图中的第4个轮询阶段,当第4个阶段清空队列之后,就该进入第5个check阶段,也就是setImmediate这个宏任务所在的阶段,而不会跳回第1个阶段,因此先输出immedate。

尾巴

最后总结一下,分析完浏览器和Node的事件环发现它们并不简单,但只要记住了它们之间的区别就可以分析出结果。

浏览器事件环是运行完一个宏任务马上清空微任务队列
Node事件环是清空完一个阶段的宏任务队列之后再清空微任务队列

最后,总结一下常见的宏任务和微任务:

宏任务 微任务
setTimeout Promise的then方法
setInterval process.nextTick
setImmediate MutationObserver
MessageChannel

相关推荐:

细说JavaScript事件循环机制-第二讲

javascript实现线程以及事件循环详解

以上是Javascript事件环该如何理解?(图文)的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

如何使用WebSocket和JavaScript实现在线语音识别系统 如何使用WebSocket和JavaScript实现在线语音识别系统 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript实现在线语音识别系统引言:随着科技的不断发展,语音识别技术已经成为了人工智能领域的重要组成部分。而基于WebSocket和JavaScript实现的在线语音识别系统,具备了低延迟、实时性和跨平台的特点,成为了一种被广泛应用的解决方案。本文将介绍如何使用WebSocket和JavaScript来实现在线语音识别系

WebSocket与JavaScript:实现实时监控系统的关键技术 WebSocket与JavaScript:实现实时监控系统的关键技术 Dec 17, 2023 pm 05:30 PM

WebSocket与JavaScript:实现实时监控系统的关键技术引言:随着互联网技术的快速发展,实时监控系统在各个领域中得到了广泛的应用。而实现实时监控的关键技术之一就是WebSocket与JavaScript的结合使用。本文将介绍WebSocket与JavaScript在实时监控系统中的应用,并给出代码示例,详细解释其实现原理。一、WebSocket技

如何利用JavaScript和WebSocket实现实时在线点餐系统 如何利用JavaScript和WebSocket实现实时在线点餐系统 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket实现实时在线点餐系统介绍:随着互联网的普及和技术的进步,越来越多的餐厅开始提供在线点餐服务。为了实现实时在线点餐系统,我们可以利用JavaScript和WebSocket技术。WebSocket是一种基于TCP协议的全双工通信协议,可以实现客户端与服务器的实时双向通信。在实时在线点餐系统中,当用户选择菜品并下单

如何使用WebSocket和JavaScript实现在线预约系统 如何使用WebSocket和JavaScript实现在线预约系统 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript实现在线预约系统在当今数字化的时代,越来越多的业务和服务都需要提供在线预约功能。而实现一个高效、实时的在线预约系统是至关重要的。本文将介绍如何使用WebSocket和JavaScript来实现一个在线预约系统,并提供具体的代码示例。一、什么是WebSocketWebSocket是一种在单个TCP连接上进行全双工

JavaScript和WebSocket:打造高效的实时天气预报系统 JavaScript和WebSocket:打造高效的实时天气预报系统 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的实时天气预报系统引言:如今,天气预报的准确性对于日常生活以及决策制定具有重要意义。随着技术的发展,我们可以通过实时获取天气数据来提供更准确可靠的天气预报。在本文中,我们将学习如何使用JavaScript和WebSocket技术,来构建一个高效的实时天气预报系统。本文将通过具体的代码示例来展示实现的过程。We

简易JavaScript教程:获取HTTP状态码的方法 简易JavaScript教程:获取HTTP状态码的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教程:如何获取HTTP状态码,需要具体代码示例前言:在Web开发中,经常会涉及到与服务器进行数据交互的场景。在与服务器进行通信时,我们经常需要获取返回的HTTP状态码来判断操作是否成功,根据不同的状态码来进行相应的处理。本篇文章将教你如何使用JavaScript获取HTTP状态码,并提供一些实用的代码示例。使用XMLHttpRequest

javascript中如何使用insertBefore javascript中如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用于在DOM树中插入一个新的节点。这个方法需要两个参数:要插入的新节点和参考节点(即新节点将要被插入的位置的节点)。

JavaScript和WebSocket:打造高效的实时图像处理系统 JavaScript和WebSocket:打造高效的实时图像处理系统 Dec 17, 2023 am 08:41 AM

JavaScript是一种广泛应用于Web开发的编程语言,而WebSocket则是一种用于实时通信的网络协议。结合二者的强大功能,我们可以打造一个高效的实时图像处理系统。本文将介绍如何利用JavaScript和WebSocket来实现这个系统,并提供具体的代码示例。首先,我们需要明确实时图像处理系统的需求和目标。假设我们有一个摄像头设备,可以采集实时的图像数

See all articles