前言

  我们知道JavaScript 是单线程的编程语言,只能同一时间内做一件事,按顺序来处理事件,但是在遇到异步事件的时候,js线程并没有阻塞,还会继续执行,这又是为什么呢?本文来总结一下js 的事件循环机制。

正文

  1、JavaScript是单线程的

  JavaScript 是一种单线程的编程语言,只有一个调用栈,决定了它在同一时间只能做一件事。在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码的时候,如果遇到了异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。因此JS又是一个非阻塞、异步、并发式的编程语言。

  2、同步和异步

  同步和异步的关系就类似于我们在餐厅排队吃饭的时候,每个人必须挨个的排队来进行买饭这个操作,而在这个过程中十分无聊,这时候我们可以边排着队边玩下手机,不需多久就排到了我们买饭。这个排队过程就是JS中的一个同步操作,玩手机就像一个异步操作。同步和异步的差别就在于排队买饭和玩手机这两个任务的执行顺序的不同。

  同步: 指的是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是处于阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。

  异步: 指的是不进入主线程,某个异步任务可以执行了,该任务才会进入主线程执行。执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。

console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
setTimeout(() => {
console.log(3);
}, 0);
setTimeout(() => {
console.log(4);
}, 0);
console.log(5);
  上面的代码会打印  1 》 5 》 2 》 3 》4,为什么会产生这样的结果,我们来看下事件循环。

  3、事件循环

  事件循环过程可以简单描述为:

  a、函数入栈,当 Stack 中执行到异步任务的时候,就将他丢给 WebAPIs ,接着执行同步任务,直到 Stack 为空;

  b、在此期间 WebAPIs 完成这个事件,把回调函数放入 CallbackQueue (任务队列)中等待;

  c、当执行栈为空时,Event Loop 把 Callback Queue中的一个任务放入Stack中,回到第1步。

  事件循环(Event Loop) 是让 JavaScript 做到既是单线程,又绝对不会阻塞的核心机制,也是 JavaScript 并发模型(Concurrency Model)的基础,是用来协调各种事件、用户交互、脚本执行、UI 渲染、网络请求等的一种机制。在执行和协调各种任务时,Event Loop 会维护自己的事件队列。

  事件队列是一个存储着待执行任务的队列,其中的任务严格按照时间先后顺序执行,排在队头的任务将会率先执行,而排在队尾的任务会最后执行。事件队列每次仅执行一个任务,在该任务执行完毕之后,再执行下一个任务,一个任务开始后直至结束,不会被其他任务中断。执行栈则是一个类似于函数调用栈的运行容器,当执行栈为空时,JS 引擎便检查事件队列,如果不为空的话,事件队列便将第一个任务压入执行栈中运行。

  任务队列:在JavaScript中,异步任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务:

  宏任务的例子很多,包括创建主文档对象、解析HTML、执行主线(或全局)JavaScript代码,更改当前URL以及各种事件,如页面加载、输入、网络事件和定时器事件。从浏览器的角度来看,宏任务代表一个个离散的、独立工作单元。运行完任务后,浏览器可以继续其他调度,如重新渲染页面的UI或执行垃圾回收。

  而微任务是更小的任务。微任务更新应用程序的状态,但必须在浏览器任务继续执行其他任务之前执行,浏览器任务包括重新渲染页面的UI。微任务的案例包括promise回调函数、DOM发生变化等。微任务需要尽可能快地、通过异步方式执行,同时不能产生全新的微任务。微任务使得我们能够在重新渲染UI之前执行指定的行为,避免不必要的UI重绘,UI重绘会使应用程序的状态不连续。

  当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务对列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务。每次宏任务执行完毕,都会去判断微任务队列是否产生新任务,若存在就优先执行微任务,否则按序执行宏任务。

  事件循环通常至少需要两个任务队列:宏任务队列和微任务队列。两种队列在同一时刻都只执行一个任务。

console.log("script start");

setTimeout(function () {
console.log("setTimeout");
}, 0); Promise.resolve()
.then(function () {
console.log("promise1");
})
.then(function () {
console.log("promise2");
}); console.log("script end");

  按照上面的内容,分析执行步骤:

  1、宏任务:执行整体代码(相当于<script>中的代码):

    输出: script start

    遇到 setTimeout,加入宏任务队列,当前宏任务队列(setTimeout)

    遇到 promise,加入微任务,当前微任务队列(promise1)

    输出:script end

  2、微任务:执行微任务队列(promise1)

    输出:promise1,then 之后产生一个微任务,加入微任务队列,当前微任务队列(promise2)

    执行 then,输出promise2

    执行渲染操作,更新界面。

    宏任务:执行 setTimeout

    输出:setTimeout

  注意:new Promise(..)中的代码,也是同步代码,会立即执行。只有then之后的代码,才是异步执行的代码,是一个微任务。

console.log("script start");

setTimeout(function () {
console.log("timeout1");
}, 10); new Promise((resolve) => {
console.log("promise1");
resolve();
setTimeout(() => console.log("timeout2"), 10);
}).then(function () {
console.log("then1");
}); console.log("script end");

  步骤解析:

  当前任务队列:微任务: [], 宏任务:[<script>]

  宏任务:

    输出: script start

    遇到 timeout1,加入宏任务

    遇到 Promise,输出promise1,直接 resolve,将 then 加入微任务,遇到 timeout2,加入宏任务。

    输出script end

  宏任务第一个执行结束

  当前任务队列:微任务[then1],宏任务[timeou1, timeout2]

  微任务:

    执行 then1,输出then1

  微任务队列清空

  当前任务队列:微任务[],宏任务[timeou1, timeout2]

  宏任务:

    输出timeout1

    输出timeout2

  当前任务队列:微任务[],宏任务[timeou2]

  微任务:

    为空跳过

  当前任务队列:微任务[],宏任务[timeou2]

  宏任务:

    输出timeout2

  注意:async 和 await 其实就是 Generator 和 Promise 的语法糖。async 函数和普通 函数没有什么不同,他只是表示这个函数里有异步操作的方法,并返回一个 Promise 对象

async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
// Promise 写法
async function async1() {
console.log("async1 start");
Promise.resolve(async2()).then(() => console.log("async1 end"));
}

  下面例子:

async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
async1();
setTimeout(() => {
console.log("timeout");
}, 0);
new Promise(function (resolve) {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log("script end");

  步骤解析:

  当前任务队列:宏任务:[<script>],微任务: []

  宏任务:

    输出:async1 start

    遇到 async2,输出:async2,并将 then(async1 end)加入微任务

    遇到 setTimeout,加入宏任务。

    遇到 Promise,输出:promise1,直接 resolve,将 then(promise2)加入微任务

    输出:script end

  当前任务队列:微任务[promise2, async1 end],宏任务[timeout]

  微任务:

    输出:promise2

    promise2 出队

    输出:async1 end

    async1 end 出队

    微任务队列清空

  当前任务队列:微任务[],宏任务[timeout]

  宏任务:

    输出:timeout

    timeout 出队,宏任务清空

写在最后

  以上就是本文的全部内容,希望给读者带来些许的帮助和进步,方便的话点个关注,小白的成长之路会持续更新一些工作中常见的问题和技术点。

js--事件循环机制的更多相关文章

  1. js事件循环机制辨析

     对于新接触js语言的人来说,最令人困惑的大概就是事件循环机制了.最开始这也困惑了我好久,花了我几个月时间通过书本,打代码,查阅资料不停地渐进地理解他.接下来我想要和大家分享一下,虽然可能有些许错误的 ...

  2. JS 事件循环机制 - 任务队列、web API、JS主线程的相互协同

    一.JS单线程.异步.同步概念 从上一篇说明vue nextTick的文章中,多次出现“事件循环”这个名词,简单说明了事件循环的步骤,以便理解nextTick的运行时机,这篇文章将更为详细的分析下事件 ...

  3. js 事件循环机制 EventLoop

    js 的非阻塞I/O  就是由事件循环机制实现的 众所周知  js是单线程的 也就是上一个任务完成后才能开始新的任务 那js碰到ajxa和定时器.promise这些异步任务怎么办那?这时候就出现了事件 ...

  4. Node.js 事件循环机制

    Node.js 采用事件驱动和异步 I/O 的方式,实现了一个单线程.高并发的 JavaScript 运行时环境,而单线程就意味着同一时间只能做一件事,那么 Node.js 如何通过单线程来实现高并发 ...

  5. js事件循环机制 (Event Loop)

    一.JavaScript是单线程单并发语言 什么是单线程 主程序只有一个线程,即同一时间片断内其只能执行单个任务. 为什么选择单线程? JavaScript的主要用途是与用户互动,以及操作DOM.这决 ...

  6. js事件循环机制(Event Loop)

    javascript从诞生之日起就是一门  单线程的  非阻塞的  脚本语言,单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务,非阻塞靠的就是 event lo ...

  7. js事件循环机制

    本文参考链接:https://www.jianshu.com/p/cf47bc0bf2ab 一.先搞懂两个东西:堆和栈 栈由操作系统自动分配释放,用于存放函数的参数值.局部变量等一些基本的数据类型,其 ...

  8. JS:事件循环机制、调用栈以及任务队列

    点击查看原文 写在前面 js里的事件循环机制十分有趣.从很多面试题也可以看出来,考察简单的setTimeout也就是考察这个机制的. 在之前,我只是简单地认为由于函数执行很快,setTimeout执行 ...

  9. js的事件循环机制:同步与异步任务(setTimeout,setInterval)宏任务,微任务(Promise,process.nextTick)

    javascript是单线程,一切javascript版的"多线程"都是用单线程模拟出来的,通过事件循环(event loop)实现的异步. javascript事件循环 事件循环 ...

  10. JS JavaScript事件循环机制

    区分进程和线程 进程是cpu资源分配的最小单位(系统会给它分配内存) 不同的进程之间是可以同学的,如管道.FIFO(命名管道).消息队列 一个进程里有单个或多个线程 浏览器是多进程的,因为系统给它的进 ...

随机推荐

  1. 【C# 并发编程】开端

    1,异步编程 异步编程就是使用future模式(又称promise)或者回调机制来实现(Non-blocking on waiting). 如果使用回调或事件来实现(容易callback hell), ...

  2. 【C# 程序集】在.net中使用GAC 全局程序集缓存

    原文地址:https://blog.alswl.com/2011/01/gac/ GAC GAC是什么?是用来干嘛的?GAC的全称叫做全局程序集缓存,通俗的理解就是存放各种.net平台下面需要使用的d ...

  3. mysql-8.0.12 安装使用教程

    Microsoft Windows [版本 10.0.15063] (c) 2017 Microsoft Corporation.保留所有权利. C:\WINDOWS\system32>D: D ...

  4. c# 自定义控件-提示框(弹框)

    分带取消按钮和不带取消按钮的 调用方法: frmMessageBox frm = new frmMessageBox("提示", "数据连接失败,请重试!", ...

  5. anaconda及jupyter notebook的了解及使用方法(1)

    今日内容 anaconda软件使用 jupyter notebook基本使用及快捷键 numpy anaconda软件使用 1.进入anaconda主页点击jupyter启动即可 呼起一个jupyte ...

  6. git--新建分支&提交代码

    本地文件都删除后,本地分支也随之被删除了: 重新clone:git clone - (-为代码在git上的地址,不同协议会有不同形式的地址,例如:HTTPS.SSH,我用HTTPS) 进入克隆完成的代 ...

  7. 微服务从代码到k8s部署应有尽有系列(十三、服务监控)

    我们用一个系列来讲解从需求到上线.从代码到k8s部署.从日志到监控等各个方面的微服务完整实践. 整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中 ...

  8. JAVA基础01----第1章Java开发环境搭建

    一. Java开发环境中涉及的名词:JDK,JRE,JVM A:什么是JVM JVM是java虚拟机(JVM Java Virtual Machine),java程序需要运行在虚拟机上,不同平台有自己 ...

  9. 【行业Tip】两化融合

    工业4.0 智能制造规划 两化融合是信息化和工业化的高层次的深度结合, 是指以信息化带动工业化.以工业化促进信息化,走新型工业化道路:两化融合的核心就是信息化支撑,追求可持续发展模式.

  10. tp5实现邮件发送(无注释)

    HTML代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...