JS运行机制

简介: 本文阐述了浏览器端和node端的js运行机制执行的过程,还进行了两者的运行机制比较,以及同步任务和异步任务的说明,两种异步任务的必要性,以及各自有哪些回调,部分回调的优先级。

网络异常,图片无法展示
|

本文阐述了浏览器端和node端的js运行机制执行的过程,还进行了两者的运行机制比较,以及同步任务和异步任务的说明,两种异步任务的必要性,以及各自有哪些回调,部分回调的优先级。


JS运行机制复述


首先js执行,会有一个函数执行栈(stack),一个任务队列(task queue),一个微任务队列(microtask queue),事件循环(event loop)。


  • 主线程:函数执行栈用来存放同步任务,按照后进先出的顺序执行;
  • 在任务队列中,存放的是宏任务。
  • 当函数执行栈为空时,会启动事件循环机制,将任务队列放到执行栈中执行。在此之前,每从任务队列中取一个任务时,如果微任务队列中存在任务,就先把微任务执行完成,在执行任务队列中的任务。
  • 依次循环,直到任务队列、微任务队列、函数执行栈均为空。


网络异常,图片无法展示
|


附:

同步任务:在主线程上执行的任务,只有前一个任务执行完成后才能执行下一个任务。

异步任务:不进入主线程执行,而是进入到任务队列(task queue)中执行。


Node.js中的事件循环


上段讲的是浏览器端的事件轮询,而node是多线程机制,由libuv库负责Node API的执行,将它分配给不同的线程,形成一个事件循环。

node中事件循环(event loop)大致分为六个部分。


  • timer定时器:执行setTimeout以及setInterval的回调。
  • I/O回调:处理网络、流、tcp错误等回调
  • idle空转和prepare阶段:node内部使用
  • poll轮询:执行poll中的I/O队列,检查定时器是否到时
  • check检查:存放setImmediate回调
  • close回调:关闭的回调


主要的是timer定时器、poll轮询、check 检查三大部分。在此我们只做了解。

事件循环过程:


  • 执行全局Script的同步代码。
  • 执行完同步代码调用栈清空后,执行微任务。先执行NextTick队列(NextTick Queue)中的所有任务,再执行其他微任务队列中的所有任务。
  • 开始执行宏任务,上面6个阶段。从第1个阶段开始,执行相应每一个阶段的宏任务队列中所有任务。(每个阶段的宏任务队列执行完毕后,开始执行微任务),然后在开始下一阶段的宏任务,依次构成事件循环。
  • timers Queue -> 执行微任务 -> I/O Queue -> 执行微任务 -> Check Queue 执行微任务 -> Close Callback Queue -> 执行微任务 ...


浏览器和Node端事件循环的差别


  • 两者的运行机制完全不同,实现机制也不同。
  • node.js可以理解成4个宏任务队列(timer、I/O、check、close)和2个微任务队列。但是执行宏任务时有6个阶段。
  • node.js在开始宏任务6个阶段时,每个阶段都将该宏任务队列中所有任务都取出来执行,每个阶段的宏任务执行完毕后,开始执行微任务。但是浏览器中的事件循环,是只取一个宏任务执行,然后看微任务队列是否存在,存在执行微任务,然后再取一个宏任务,构成循环。



JS异步任务


js的异步任务分为两种:宏任务、微任务。一个宏任务里面可以拥有多个微任务,在执行js代码块的时候才会去执行内部的微任务。


宏任务


macrotask,也叫tasks。一些异步任务的回调会依次进入宏任务队列,等待后续背调用。

宏任务包括:


  • setTimeout/setInterval
  • setImmediate(Node独有)
  • requestAnimationFrame(浏览器独有)
  • I/O
  • UI rendering(浏览器独有)


注意:

1、setTimeout延迟时间为0,与requestAnimationFrame比较:requestAnimationFrame优先级大于setTimeout

2、setTimeout延迟时间为0,与setImmediate比较:不确定

setTimeout(() => console.log('setTimeout'), 0)
setImmediate(()=>{
  console.log('setImmediate');
})


解释:


timer前的准备时间超过1ms,(loop到timer的时间大于1ms),则执行timer阶段(setTimeout)的回调函数。


timer前的准备时间小于1ms,则先执行check阶段(setTimeout)的回调函数,下次事件循环,再执行timer阶段的回调函数。


如果想要setImmediate先执行,可以使用fs文件包裹,确保在I/O回调阶段执行。这样时间循环,会先执行chack阶段,之后再执行timer阶段。


node版本中的setTimeout


setTimeout(() => {
  console.log(1)
})
setTimeout(() => {
  console.log(2)
  Promise.resolve().then(function () {
    console.log('promise')
  })
})
setTimeout(() => {
  console.log(3)
})


  • node11以后的版本与浏览器端运行结果一致:1 2 promise 3。
  • node11之前的版本,执行结果为:1 2 3 promise。它会先进入timer阶段,执行第一个setTimeout并打印。再执行第二个setTimeout并打印,并将Promise放入微任务队列中。然后执行第三个setTimeout并打印。事件循环在执行下一阶段时,先执行微任务队列,打印promise。


微任务


microtask,也叫jobs。除宏任务外的一些异步回调会依次进入微任务队列,等待后续被调用。 微任务包括:


  • process.nextTick(Node独有)
  • Promise.then()
  • Object.observe
  • MutationObserve


注意:

process.nextTick优先级高于Promise.then()。


两种异步任务的必要性


在异步任务队列中,遵循先进先出的原则。此时,在众多异步任务中,如果存在优先级较高的任务需要优先执行,那么只有一个异步任务队列是无法满足的,此时就需要引入微任务队列,将优先级较高的任务放到微任务队列中。如果微任务队列非空,则执行微任务队列,否则执行宏任务队列。


如果只有一种异步任务,那么优先级高的异步任务无法优先执行。


补充


async/await


async/await本质上还是基于Promise的一些封装

async函数在await之前的代码都是同步执行的,可以理解为await之前的代码属于new Promise时传入的代码,await之后的所有代码都是在Promise.then中的回调

目录
相关文章
|
4月前
|
前端开发 JavaScript UED
深入理解JavaScript中的事件循环机制
JavaScript中的事件循环机制是其异步编程的核心,深入理解该机制对于开发高效、流畅的前端应用至关重要。本文将介绍事件循环的工作原理、常见的事件循环模型,以及如何利用这些知识解决前端开发中的常见问题。
|
23天前
|
监控 JavaScript Linux
[译] 在生产环境运行 PM2 & Node.js
[译] 在生产环境运行 PM2 & Node.js
|
29天前
|
JavaScript 前端开发 算法
js 内存回收机制
【8月更文挑战第23天】js 内存回收机制
30 3
|
29天前
|
存储 JavaScript 前端开发
学习JavaScript 内存机制
【8月更文挑战第23天】学习JavaScript 内存机制
22 3
|
20天前
|
JavaScript 中间件 开发者
深入浅出Node.js中间件机制
【8月更文挑战第31天】本文将带你领略Node.js中间件的奥秘,通过直观的案例分析,揭示其背后的设计哲学。你将学会如何运用中间件构建强大而灵活的后端应用,以及在面对复杂业务逻辑时如何保持代码的清晰与高效。
|
20天前
|
设计模式 JavaScript 中间件
深入浅出Node.js中间件机制
【8月更文挑战第31天】在Node.js的世界里,中间件如同魔法般存在,它让复杂的请求处理变得井然有序。本文将带你领略中间件的奥秘,从原理到实战,一步步揭开它的神秘面纱。你将学会如何运用中间件来构建强大而灵活的后端应用,就像拼乐高一样有趣。
|
27天前
|
JavaScript Windows
【Azure 应用服务】用App Service部署运行 Vue.js 编写的项目,应该怎么部署运行呢?
【Azure 应用服务】用App Service部署运行 Vue.js 编写的项目,应该怎么部署运行呢?
|
27天前
|
JavaScript 前端开发 C++
【Azure Function】调试 VS Code Javascript Function本地不能运行,报错 Value cannot be null. (Parameter 'provider')问题
【Azure Function】调试 VS Code Javascript Function本地不能运行,报错 Value cannot be null. (Parameter 'provider')问题
|
3月前
|
设计模式 JavaScript 前端开发
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略
JavaScript的继承机制基于原型链,它定义了对象属性和方法的查找规则。每个对象都有一个原型,通过原型链,对象能访问到构造函数原型上的方法。例如`Animal.prototype`上的`speak`方法可被`Animal`实例访问。原型链的尽头是`Object.prototype`,其`[[Prototype]]`为`null`。继承方式包括原型链继承(通过`Object.create`)、构造函数继承(使用`call`或`apply`)和组合继承(结合两者)。ES6的`class`语法是语法糖,但底层仍基于原型。继承选择应根据需求,理解原型链原理对JavaScript面向对象编程至关重要
76 7
【JavaScript】深入浅出JavaScript继承机制:解密原型、原型链与面向对象实战攻略