新生代总结 JavaScript 运行机制解析

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 新生代总结 JavaScript 运行机制解析

image.png

📢 大家好,我是小丞同学,一名准大二的前端爱好者


📢 这篇文章将带你一起学习理解 JavaScript 运行机制


📢 愿你忠于自己,热爱生活


引言

在一些面试中,我们或许会被问到这样的问题


简述一下 JavaScript 的运行机制?


还有可能会被问这样的代码

setTimeout(function () {
    console.log('定时器开始啦')
});
new Promise(function (resolve) {
    console.log('马上执行for循环啦');
    for (var i = 0; i < 10000; i++) {
        i == 99 && resolve();
    }
}).then(function () {
    console.log('执行then函数啦')
});

这些虽然看起来很深奥很复杂,但是如果你了解了 JavaScript 的运行机制,这些问题都能够一一化解  先附上本文的纲要,本文将会从这三个方面去解析 JavaScript 的运行机制

image.png

首先我们来谈谈 JavaScript 的单线程


1. 为什么是单线程?

众所周知,JavaScript 是一门单线程的语言,也因此带来了很多诟病,那么单线程如此不堪,为什么不把它设计成多线程的呢?


其实这个问题就出现在了 JavaScript 的应用场景上,我们通常采用 JavaScript 来操作 DOM 元素,这在现在来看没什么问题。但是我们想一想,如果 JavaScript 变成了一门多线程的语言,那会发生什么呢?


想象一下下面的场景


一段 JS 代码删除 DOM 元素,一段 JS 代码更改 DOM 元素样式,它们一起被执行了,这会发生什么?


先别说浏览器该怎么处理了,我都不知道该如何处理,那浏览器就会崩溃掉 …


为了避免这样的情况, JavaScript 被设计成了一门单线程的语言


单线程就意味着,一次只能执行一个任务,其他任务都需要排队等待


但是为了能有多线程的功能,有了很多的尝试


在 HTML5 中提出了 web worker 标准,它提供了一套完整的 API 去允许在主线程以外去执行另一个线程,但是这不意味着 JavaScript 从此拥有了多线程的能力,同时我们也不能用它来操作 DOM 元素。


在 JavaScript 中还有着独特执行机制,它将主线程中的任务分为同步任务和异步任务


2. 为什么需要异步?

为了能够解决单线程带来的代码阻塞等问题


JS 是单线程的,我们可以想象成有一个售票窗口,有很多人在窗口排队办理业务,而 JS 只能一个一个处理,那如果有一个客户的需求很多,办理业务的时间很长,那么这条队伍的其他人就只能干等着了,就相当于代码阻塞了,也就是浏览器假死,等待代码执行


因此有了同步任务和异步任务的概念


就是需要通过这样来区分,将那些办理业务时间长的分出来,等到其他客户处理完毕之后再统一处理


关于同步任务和异步任务是这样解释的


同步任务:是在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务,例如:console.log

异步任务:不进入主线程、通过事件循环机制处理,在任务队列中注册回调函数最终拿到结果,例如:setTimeout

了解了什么是同步,什么是异步,我们来一道非常简单的题目

console.log(1);
setTimeout(()=>{console.log(2);},0)
console.log(3);

结果输出 1 3 2  原因是 setTimeout 是异步任务,需要在同步代码执行之后再执行  接下来我们聊聊运行机制的核心:事件循环  3. 事件循环 首先我们用一张图来理解事件循环

image.png

它的运行机制如下:


所有同步任务在主线程上执行,形成一个执行栈,也就是上图蓝色箭头表示

主线程以外有一个异步任务队列(红色箭头),会等到异步任务返回结果后将它放入任务队列

当主线程中执行栈代码执行完毕,也就是同步任务执行完毕,就会开始读取任务队列代码,再放入执行栈中执行

不断地重复上面三步,这就是事件循环

用图形来描绘的话,就是上图中的三个黑色箭头,连成的闭环


也就是说:只要主线程执行栈空了,就会去读取任务队列,这个过程是循环不断的,这种运行机制就叫做事件循环


了解了事件循环,我们对前面那题做一个简单的升级

console.log(1);
setTimeout(()=>{console.log(2);},0)
setTimeout(()=>{console.log(3);},1000)
setTimeout(()=>{console.log(4);},0)
console.log(5);

这次输出了 1 – 5 – 2 – 4 – 3


可能会有人会对 3 的输出有疑惑,首先定时器都是异步任务,会先被放入异步任务队列当中,需要等待异步任务返回结果后,再将回调函数放入任务队列当中,等待主线程来执行,因此,2 和 4 会在 3 之前输出


4. 异步任务队列细节

常见的会放入异步任务队列的事件


DOM 事件

Promise

Ajax 请求

setTimeout 和 setlnterval

文件上传

至于加入异步任务队列的时间,是需要根据当前异步任务而定的,不是说拿到异步任务直接添加到任务队列里面,是要等到当前异步任务执行完成返回结果,才将其放到任务队列里


就拿 setTimeout 来说,是需要等待定时结束再将回调加入任务队列的


也可以结合下图理解


image.png

image.png

了解了任务队列,我们需要再谈一谈异步任务当中,又被细分出来的宏任务和微任务


5. 宏任务和微任务

宏任务队列可以有多个,微任务队列只有一个


那么什么是宏任务,什么是微任务呢?


宏任务有:HTML解析、鼠标事件、键盘事件、网络请求、执行主线程js代码和定时器

微任务有:promise.then,DOM 渲染,async,process.nextTick

那它是怎么被执行的呢?


当执行栈中的同步任务执行完毕后,先执行微任务


微任务队列执行完毕后,会读取宏任务


执行宏任务的过程中,遇到微任务,再加入微任务队列


宏任务执行完后,再次读取微任务队列,依次循环


画个图来辅助理解一下


用一句简单的话来总结:微任务永远在宏任务执行之前被执行完毕


image.png

image.png

特别注意的是:由于代码的入口就是一个 script 标签。因此,全局任务属于宏任务  6. 实战 在了解了这么多后,我们来看一到经典的面试题

console.log("1");
setTimeout(function () {
    console.log("2");
    new Promise(function (resolve) {
        console.log("3");
        resolve();
    }).then(function () {
        console.log("4");
    });
});
new Promise(function (resolve) {
    console.log("5");
    resolve();
}).then(function () {
    console.log("6");
});
setTimeout(function () {
    console.log("7");
});
setTimeout(function () {
    console.log("8");
    new Promise(function (resolve) {
        console.log("9");
        resolve();
    }).then(function () {
        console.log("10");
    });
});
new Promise(function (resolve) {
    console.log("11");
    resolve();
}).then(function () {
    console.log("12");
});
console.log("13");

答案是:1 – 5 – 11 – 13 – 6 – 12 – 2 – 3 – 4 – 7 – 8 – 9 – 10


第一轮循环


从全局任务入口,首先打印日志 1

遇到宏任务 setTimeout ,交给异步处理模块,记为setTimeout1

再遇到 promise 对象,打印日志 5 ,将 promise.then 加入微任务队列,记做 p1

又遇到 setTimeout 交给异步处理模块,记为 setTimeout2

又遇到 setTimeout 交给异步处理模块,记为 setTimeout3

遇到 promise 对象,打印日志 11 ,将 promise.then 加入微任务队列,记做 p2

遇到打印语句,直接打印日志 13

本轮循环共打印:1 – 5 – 11 – 13


当前循环结果


image.png

第二轮循环


首先执行微任务队列 p1 和 p2 ,先进先出,先打印 6 再打印 12

微任务事件处理完毕,开始执行宏任务 setTimeout1

遇到打印语句,直接打印日志 2

又遇到 promise 对象,打印日志 3,将 promise.then 加入微任务队列,记做 p3

第二轮循环结束


当前运行图为

image.png

第三轮循环  首先执行微任务队列,打印日志 4 微任务处理完毕,执行宏任务 setTimeout2 遇到打印语句,直接输出 7 本轮循环结束

image.png

第四轮循环  微任务队列为空,执行宏任务 setTimeout3 遇到打印语句,打印日志 8 遇到 promise 对象,执行打印语句,打印 9 将 promise.then 加入微任务队列 记做 p4

image.png

第五轮循环  首先清空微任务队列,执行打印语句,打印 10 执行完毕 以上就是关于 JavaScript 运行机制的全部内容,希望能有所收获


相关文章
|
24天前
|
JavaScript API
深入探索fs.WriteStream:Node.js文件写入流的全面解析
深入探索fs.WriteStream:Node.js文件写入流的全面解析
|
8天前
|
JavaScript
js 解析 byte数组 成字符串
js 解析 byte数组 成字符串
|
22天前
|
Rust JavaScript 前端开发
Rust! 无VDom! 尤雨溪解析 Vue.js 2024 新特性
Rust! 无VDom! 尤雨溪解析 Vue.js 2024 新特性
|
26天前
|
JavaScript 前端开发 UED
Javaweb之javascript的小案例的详细解析
通过上述步骤,我们得到了一个动态更新的实时时钟,这个简单的JavaScript案例展示了定时器的使用方法,并讲解了如何处理日期和时间。这个案例说明了JavaScript在网页中添加动态内容与交互的能力。对于涉足JavaWeb开发的学习者来说,理解和运用这些基础知识非常重要。
34 11
|
18天前
|
JavaScript 前端开发 API
Javaweb之javascript的BOM对象的详细解析
BOM为Web开发提供了强大的API,允许开发者与浏览器进行深入的交互。合理使用BOM中的对象和方法,可以极大地增强Web应用的功能性和用户体验。需要注意的是,BOM的某些特征可能会在不同浏览器中表现不一致,因此在开发过程中需要进行仔细的测试和兼容性处理。通过掌握BOM,开发者能够制作出更丰富、更动态、更交互性的JavaWeb应用。
13 1
|
24天前
|
运维 Cloud Native JavaScript
云端新纪元:云原生技术深度解析深入理解Node.js事件循环及其在异步编程中的应用
【8月更文挑战第27天】随着云计算技术的飞速发展,云原生已成为推动现代软件开发和运维的关键力量。本文将深入探讨云原生的基本概念、核心价值及其在实际业务中的应用,帮助读者理解云原生如何重塑IT架构,提升企业的创新能力和市场竞争力。通过具体案例分析,我们将揭示云原生技术背后的哲学思想,以及它如何影响企业决策和操作模式。
|
25天前
|
JSON JavaScript 前端开发
JS逆向 AST 抽象语法树解析与实践
JS逆向 AST 抽象语法树解析与实践
20 2
|
25天前
|
JavaScript 前端开发 安全
JS 混淆解析:JS 压缩混淆原理、OB 混淆特性、OB 混淆JS、混淆突破实战
JS 混淆解析:JS 压缩混淆原理、OB 混淆特性、OB 混淆JS、混淆突破实战
32 2
|
25天前
|
算法 JavaScript 前端开发
国标非对称加密:RSA算法、非对称特征、js还原、jsencrypt和rsa模块解析
国标非对称加密:RSA算法、非对称特征、js还原、jsencrypt和rsa模块解析
105 1
|
17天前
|
自然语言处理 前端开发 JavaScript
Javaweb之javascript的详细解析
通过明确JavaScript的定位,掌握其核心概念和相关技术栈,在实现交互丰富的Web应用时,JavaScript就能够发挥它不可替代的作用。随着前后端分离趋势的推进,JavaScript在现代 Web 开发中变得更加重要,不仅限于传统的 JavaWeb 应用,而是广泛应用于各种类型的前端项目。
11 0

推荐镜像

更多