深入理解JS执行机制

简介: 深入理解JS执行机制

前言            

JavaScript是一门单线程的非阻塞脚本语言,同一时刻只允许一个代码段执行。在单线程的机制下,执行异步任务时,在等待结果返回的这个时间段,后面的代码就无法执行了。


JS在执行代码时,遇到异步任务之后还有同步任务的场景时,它并不会等待异步任务执行完,而是先执行同步任务,那么JS是如何做到这一点的呢?


本篇文章将详细讲解上述问题,欢迎各位感兴趣的开发者阅读本文。


事件循环


单线程


讲事件循环之前,我们先来理解下为什么JS不设计成多线程的。


我们做个假设,如果JS是多线程的,因为JS有DOM API可以操作DOM,此时如果有两个线程在操作同一个DOM,线程1删除了这个dom节点,线程2要操作这个dom,就会产生矛盾,到底以哪个线程为主。


为了避免这种情况的出现,JS就被设计成了单线程


宏任务与微任务


JS引擎把所有任务分为两类:宏任务、微任务。


宏任务有:


  • script整体代码
  • setTimeout、setInterval
  • I/O
  • UI渲染
  • postMessage
  • MessageChannel
  • requestAnimationFrame
  • setImmediate(Node.js 环境)


微任务有:


  • new Promise.then()
  • MutaionObserver
  • process.nextTick(Node.js 环境)


执行规则


文章一开头我们了解到了单线程的弊端,JS是通过事件循环机制(EventLoop)来解决这一弊端的,接下来我们来看下EventLoop的执行规则:


  • 所有代码作为宏任务进入主线程执行栈,开始执行
  • 执行过程中,同步代码会立即执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 当前宏任务执行完成出队,读取微任务队列,有则执行,直至全部执行完毕
  • 执行浏览器ui进程渲染
  • 检查是否有webworker任务,有则执行
  • 本轮宏任务执行完成,回到第2步,继续执行,直至宏任务与微任务队列全部清空


举例说明


我们了解完它的执行规则后,接下来我们举个例子来说明下,如下所示:


console.log("1"); // 1 同步代码:立即执行 [1]
setTimeout(function() {
  console.log("2"); // 3 同步代码执行执行 输出2
  process.nextTick(function() {
    console.log("3"); // 4 进入微任务队列 [3]
  });
  new Promise(function(resolve) {
    console.log("4"); // 3 同步代码执行执行 输出4
    resolve();
  }).then(function() {
    console.log("5"); // 4 进入微任务队列 [3, 5]
  });
});
process.nextTick(function() {
  console.log("6"); // 2 进入微任务队列 [6]
});
new Promise(function(resolve) {
  console.log("7"); // 1 宏任务:立即执行 [1, 7]
  resolve();
}).then(function() {
  console.log("8"); // 2 进入微任务队列 [6, 8]
});
setTimeout(function() {
  console.log("9"); // 5 宏任务:立即执行 [9]
  process.nextTick(function() {
    console.log("10"); // 6 进入微任务队列 [10]
  });
  new Promise(function(resolve) {
    console.log("11"); // 5 宏任务:立即执行 [9, 11]
    resolve();
  }).then(function() {
    console.log("12"); // 6 进入微任务队列 [10, 12]
  });
});
// 执行顺序:1 7 6 8 2 4 3 5 9 11 10 12


process.nextTick()为node中的方法,你可以把它理解为与promise一样的微任务,promise的executor函数中的同步代码会立即执行。


我们来分析下上述代码的执行顺序,如下图所示:

640.png

                                      image.png


运行结果如下所示:

640.png

                                            image.png


当你把上述示例代码啃透后,那么你也就理解js的事件循环机制了。

当然,你可能没有那么快就啃透这个例子,这种概念性的东西,掌握它最好的办法就是:将示例代码放到编辑器里,对照着事件循环的执行规则,一行一行的去读代码,大脑过一遍,猜测运行结果,然后再去执行代码判断执行结果是否与你猜的一致。

最后,举一反三,去网上找一些事件循环的面试题多加练习,慢慢的你就把这个知识点啃透了,Good Luck!


代码地址


本文为《JS原理学习》系列的第5篇文章,本系列的完整路线请移步:JS原理学习 (1) 》学习路线规划


本系列文章的所有示例代码,请移步:js-learning


写在最后


至此,文章就分享完毕了。


我是神奇的程序员,一位前端开发工程师。


如果你对我感兴趣,请移步我的个人网站(www.kaisir.cn),进一步了解。


  • 公众号无法外链,如果文中有链接,可点击下方阅读原文查看😊
相关文章
|
存储 JavaScript 前端开发
深入理解JavaScript中的事件循环(Event Loop):机制与实现
【10月更文挑战第12天】深入理解JavaScript中的事件循环(Event Loop):机制与实现
509 3
|
缓存 JavaScript 前端开发
常见的 JavaScript 隔离机制
常见的 JavaScript 隔离机制
413 154
|
JavaScript 安全 前端开发
乾坤js隔离机制
乾坤js隔离机制
|
Web App开发 JSON JavaScript
Node.js 中的中间件机制与 Express 应用
Node.js 中的中间件机制与 Express 应用
|
JavaScript 安全 中间件
深入浅出Node.js中间件机制
【10月更文挑战第36天】在探索Node.js的奥秘之旅中,中间件的概念如同魔法一般,它让复杂的请求处理变得优雅而高效。本文将带你领略这一机制的魅力,从概念到实践,一步步揭示如何利用中间件简化和增强你的应用。
|
消息中间件 JavaScript 中间件
深入浅出Node.js中间件机制
【10月更文挑战第24天】在Node.js的世界里,中间件如同厨房中的调料,为后端服务增添风味。本文将带你走进Node.js的中间件机制,从基础概念到实际应用,一探究竟。通过生动的比喻和直观的代码示例,我们将一起解锁中间件的奥秘,让你轻松成为后端料理高手。
195 1
|
移动开发 JavaScript 前端开发
【JavaScript】JS执行机制--同步与异步
【JavaScript】JS执行机制--同步与异步
276 1
|
JSON JavaScript 中间件
深入浅出Node.js中间件机制
本文将带你探索Node.js中一个核心概念——中间件机制。我们将通过浅显的语言和生动的比喻,揭示中间件如何作为请求和响应之间的“交通枢纽”,在应用程序中起到至关重要的作用。从基础原理到实际应用,你将了解到中间件不仅简化了代码结构,还提高了开发效率,是Node.js开发不可或缺的一部分。
198 1
|
设计模式 JavaScript 中间件
深入浅出Node.js中间件机制
【8月更文挑战第31天】在Node.js的世界里,中间件如同魔法般存在,它让复杂的请求处理变得井然有序。本文将带你领略中间件的奥秘,从原理到实战,一步步揭开它的神秘面纱。你将学会如何运用中间件来构建强大而灵活的后端应用,就像拼乐高一样有趣。
|
设计模式 JavaScript 前端开发
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
JavaScript的继承机制基于原型链,它定义了对象属性和方法的查找规则。每个对象都有一个原型,通过原型链,对象能访问到构造函数原型上的方法。例如`Animal.prototype`上的`speak`方法可被`Animal`实例访问。原型链的尽头是`Object.prototype`,其`[[Prototype]]`为`null`。继承方式包括原型链继承(通过`Object.create`)、构造函数继承(使用`call`或`apply`)和组合继承(结合两者)。ES6的`class`语法是语法糖,但底层仍基于原型。继承选择应根据需求,理解原型链原理对JavaScript面向对象编程至关重要
367 7
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略