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

简介: 新生代总结 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 运行机制的全部内容,希望能有所收获


相关文章
|
3月前
|
JavaScript 前端开发 开发者
Nest.js控制器深度解析:路由与请求处理的高级特性
以上就是对 NestJS 控制层高级特性深度解析:从基本概念到异步支持再到更复杂场景下拦截其与管道等功能性组件运用都有所涉及,希望能够帮助开发者更好地理解和运用 NestJS 进行高效开发工作。
358 15
|
3月前
|
JavaScript 前端开发 IDE
TypeScript vs. JavaScript:技术对比与核心差异解析
TypeScript 作为 JavaScript 的超集,通过静态类型系统、编译时错误检测和强大的工具链支持,显著提升代码质量与可维护性,尤其适用于中大型项目和团队协作。相较之下,JavaScript 更灵活,适合快速原型开发。本文从类型系统、错误检测、工具支持等多维度对比两者差异,并提供技术选型建议,助力开发者合理选择。
786 1
|
3月前
|
存储 JavaScript 前端开发
JavaScript 语法全面解析
JavaScript 语法体系丰富且不断更新,从基础的变量声明、数据类型,到复杂的函数、对象、异步语法,每个知识点都需要开发者深入理解并灵活运用。本文梳理的 JS 语法核心内容,可为开发者提供系统的学习框架,后续还需通过大量实践(如编写交互组件、实现业务逻辑)巩固知识,逐步提升 JS 编程能力,应对前端开发中的各类挑战。
|
6月前
|
机器学习/深度学习 JavaScript 前端开发
JS进阶教程:递归函数原理与篇例解析
通过对这些代码示例的学习,我们已经了解了递归的原理以及递归在JS中的应用方法。递归虽然有着理论升华,但弄清它的核心思想并不难。举个随手可见的例子,火影鸣人做的影分身,你看到的都是同一个鸣人,但他们的行为却能在全局产生影响,这不就是递归吗?雾里看花,透过其间你或许已经深入了递归的魅力之中。
285 19
|
7月前
|
JSON 前端开发 Serverless
Mock.js 语法结构全解析
Mock.js 的语法规范介绍,从数据模板定义规范和数据占位符定义规范俩部分介绍, 让你更好的使用 Mock.js 来模拟数据并提高开发效率。
|
9月前
|
数据采集 前端开发 JavaScript
金融数据分析:解析JavaScript渲染的隐藏表格
本文详解了如何使用Python与Selenium结合代理IP技术,从金融网站(如东方财富网)抓取由JavaScript渲染的隐藏表格数据。内容涵盖环境搭建、代理配置、模拟用户行为、数据解析与分析等关键步骤。通过设置Cookie和User-Agent,突破反爬机制;借助Selenium等待页面渲染,精准定位动态数据。同时,提供了常见错误解决方案及延伸练习,帮助读者掌握金融数据采集的核心技能,为投资决策提供支持。注意规避动态加载、代理验证及元素定位等潜在陷阱,确保数据抓取高效稳定。
296 17
|
9月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
9月前
|
存储 JavaScript 前端开发
全网最全情景,深入浅出解析JavaScript数组去重:数值与引用类型的全面攻略
如果是基础类型数组,优先选择 Set。 对于引用类型数组,根据需求选择 Map 或 JSON.stringify()。 其余情况根据实际需求进行混合调用,就能更好的实现数组去重。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
存储 前端开发 JavaScript
JavaScript垃圾回收机制深度解析
【10月更文挑战第21】JavaScript垃圾回收机制深度解析
275 59
|
9月前
|
消息中间件 JavaScript 前端开发
最细最有条理解析:事件循环(消息循环)是什么?为什么JS需要异步
度一教育的袁进老师谈到他的理解:单线程是异步产生的原因,事件循环是异步的实现方式。 本质是因为渲染进程因为计算机图形学的限制,只能是单线程。所以需要“异步”这个技术思想来解决页面阻塞的问题,而“事件循环”是实现“异步”这个技术思想的最主要的技术手段。 但事件循环并不是全部的技术手段,比如Promise,虽然受事件循环管理,但是如果没有事件循环,单一Promise依然能实现异步不是吗? 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您

推荐镜像

更多
  • DNS