js 运行机制(含异步机制、同步任务、异步任务、宏任务、微任务、Event Loop)

简介: js 运行机制(含异步机制、同步任务、异步任务、宏任务、微任务、Event Loop)

js 的同步任务和异步任务

js代码可以分为两种任务:

  • 同步任务(synchronous)—— 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
  • 异步任务(asynchronous)—— 不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

异步任务又分为宏任务与微任务:

  • 宏任务(macrotask )
事件/函数 浏览器环境 Node.js环境
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame
  • 微任务(microtask )
事件/函数 浏览器环境 Node.js环境
process.nextTick
MutationObserver
Promise.then catch finally

js在浏览器中的运行机制

从上到下依次解释执行所有代码

  • 遇到同步任务时,在主线程立刻执行该任务。
    此时主线程上有一个执行栈(execution context stack),所有同步代码会按顺序执行。
  • 遇到异步任务时,异步任务会进入到Event Table,当异步任务有结果后,将相对应的回调函数进行注册,放入事件队列(Event Queue);(异步的宏任务有结果后,会放入宏任务事件队列;异步的微任务有结果后,会放入微任务事件队列;)

(1)主线程从上到下依次执行所有同步任务

(2)主线程读取微任务事件队列,若存在微任务,则依次执行所有微任务

  • 微任务中同步任务会依次立刻执行
  • 微任务中微任务会依次添加到微任务事件队列末尾,待下一次读取微任务事件队列时执行
  • 微任务中宏任务会依次添加到宏任务事件队列末尾,待下一次读取宏任务事件队列时执行

(3)主线程读取宏任务事件队列,若存在宏任务,则依次执行所有宏任务

  • 宏任务中同步任务会依次立刻执行
  • 宏任务中微任务会依次添加到微任务事件队列末尾,待下一次读取微任务事件队列时执行
  • 宏任务中宏任务会依次添加到宏任务事件队列末尾,待下一次读取宏任务事件队列时执行

(4)依次重复第2步和第3步,直到清空微任务事件队列和宏任务事件队列

Event Loop(事件循环):主线程循环不断从"任务事件队列"中读取事件的运行机制称为Event Loop,这个过程是循环不断的,所以整个的这种运行机制

Event Loop 并不是在 ECMAScript 标准中定义的,而是在 HTML 标准中定义的

测试js在浏览器中运行机制的代码

console.log('0号同步任务1');
new Promise(function (resolve) {
    console.log('0号同步任务2');
    resolve();
}).then(function () {
    // 1号微任务
    setTimeout(function () {
        console.log('1号微任务中的宏任务1');
    })
    console.log('1号微任务中的同步任务1')
    new Promise(function (resolve) {
        console.log('1号微任务中的同步任务2');
        resolve();
    }).then(function () {
        // 1.1号微任务
        console.log('1.1号微任务中的同步任务1')
        setTimeout(function () {
            console.log('1.1号微任务中的宏任务1');
        })
        console.log('1.1号微任务中的同步任务2')
        setTimeout(function () {
            console.log('1.1号微任务中的宏任务2');
        })
    })
    console.log('1号微任务中的同步任务3')
    setTimeout(function () {
        console.log('1号微任务中的宏任务2');
    })
})
setTimeout(function () {
    console.log('0号宏任务中的同步任务1');
    new Promise(function (resolve) {
        console.log('0号宏任务中的同步任务2');
        resolve();
    }).then(function () {
        // 0号宏任务中的1号微任务
        setTimeout(function () {
            console.log('0号宏任务中的1号微任务中的宏任务1');
        })
        console.log('0号宏任务中的1号微任务中的同步任务1')
        new Promise(function (resolve) {
            console.log('0号宏任务中的1号微任务中的同步任务2');
            resolve();
        }).then(function () {
            //  0号宏任务中的1.1号微任务
            console.log('0号宏任务中的1.1号微任务中的同步任务1')
            setTimeout(function () {
                console.log('0号宏任务中的1.1号微任务中的宏任务1');
            })
        })
        console.log('0号宏任务中的1号微任务中的同步任务3')
    })
})
new Promise(function (resolve) {
    console.log('0号同步任务3');
    resolve();
}).then(function () {
    // 2号微任务
    setTimeout(function () {
        console.log('2号微任务中的宏任务1');
    })
    console.log('2号微任务中的同步任务1')
    new Promise(function (resolve) {
        console.log('2号微任务中的同步任务2');
        resolve();
    }).then(function () {
        // 2.1号微任务
        console.log('2.1号微任务中的同步任务1')
        setTimeout(function () {
            console.log('2.1号微任务中的宏任务1');
        })
    })
    console.log('2号微任务中的同步任务3')
})
console.log('0号同步任务4');

运行结果

0号同步任务1
0号同步任务2
0号同步任务3
0号同步任务4
1号微任务中的同步任务1
1号微任务中的同步任务2
1号微任务中的同步任务3
2号微任务中的同步任务1
2号微任务中的同步任务2
2号微任务中的同步任务3
1.1号微任务中的同步任务1
1.1号微任务中的同步任务2
2.1号微任务中的同步任务1
0号宏任务中的同步任务1
0号宏任务中的同步任务2
0号宏任务中的1号微任务中的同步任务1
0号宏任务中的1号微任务中的同步任务2
0号宏任务中的1号微任务中的同步任务3
0号宏任务中的1.1号微任务中的同步任务1
1号微任务中的宏任务1
1号微任务中的宏任务2
2号微任务中的宏任务1
1.1号微任务中的宏任务1
1.1号微任务中的宏任务2
2.1号微任务中的宏任务1
0号宏任务中的1号微任务中的宏任务1
0号宏任务中的1.1号微任务中的宏任务1


js在node.js中的运行机制

(1)V8引擎解析JavaScript脚本。

(2)解析后的代码,调用Node API。

(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。

(4)V8引擎再将结果返回给用户。

Node.js还提供了另外两个与"任务队列"有关的方法:process.nextTick和setImmediate。

  • process.nextTick方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。也就是说,它指定的任务总是发生在所有异步任务之前。【多个process.nextTick语句总是在当前"执行栈"一次执行完】
  • setImmediate方法则是在当前"任务队列"的尾部添加事件,也就是说,它指定的任务总是在下一次Event Loop时执行。【多个setImmediate可能则需要多次loop才能执行完。】

由于process.nextTick指定的回调函数是在本次"事件循环"触发,而setImmediate指定的是在下次"事件循环"触发,所以很显然,前者总是比后者发生得早,而且执行效率也高(因为不用检查"任务队列")。

目录
相关文章
|
7月前
|
Web App开发 JavaScript 前端开发
如何在JavaScript中确定异步操作之间的依赖关系?
如何在JavaScript中确定异步操作之间的依赖关系?
181 58
|
7月前
|
前端开发 JavaScript
有没有方法可以保证在JavaScript中多个异步操作的执行顺序?
有没有方法可以保证在JavaScript中多个异步操作的执行顺序?
299 58
|
11月前
|
存储 JavaScript 前端开发
深入理解JavaScript中的事件循环(Event Loop):机制与实现
【10月更文挑战第12天】深入理解JavaScript中的事件循环(Event Loop):机制与实现
405 3
|
6月前
|
JavaScript 前端开发 API
JavaScript中通过array.map()实现数据转换、创建派生数组、异步数据流处理、复杂API请求、DOM操作、搜索和过滤等,array.map()的使用详解(附实际应用代码)
array.map()可以用来数据转换、创建派生数组、应用函数、链式调用、异步数据流处理、复杂API请求梳理、提供DOM操作、用来搜索和过滤等,比for好用太多了,主要是写法简单,并且非常直观,并且能提升代码的可读性,也就提升了Long Term代码的可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
7月前
|
JavaScript 前端开发
Node.js 中实现多任务下载的并发控制策略
Node.js 中实现多任务下载的并发控制策略
173 15
|
6月前
|
消息中间件 JavaScript 前端开发
最细最有条理解析:事件循环(消息循环)是什么?为什么JS需要异步
度一教育的袁进老师谈到他的理解:单线程是异步产生的原因,事件循环是异步的实现方式。 本质是因为渲染进程因为计算机图形学的限制,只能是单线程。所以需要“异步”这个技术思想来解决页面阻塞的问题,而“事件循环”是实现“异步”这个技术思想的最主要的技术手段。 但事件循环并不是全部的技术手段,比如Promise,虽然受事件循环管理,但是如果没有事件循环,单一Promise依然能实现异步不是吗? 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您
|
10月前
|
JSON 前端开发 JavaScript
在 JavaScript 中,如何使用 Promise 处理异步操作?
通过以上方式,可以使用Promise来有效地处理各种异步操作,使异步代码更加清晰、易读和易于维护,避免了回调地狱的问题,提高了代码的质量和可维护性。
|
11月前
|
前端开发 JavaScript
深入理解JavaScript中的事件循环(Event Loop):从原理到实践
【10月更文挑战第12天】 深入理解JavaScript中的事件循环(Event Loop):从原理到实践
338 1
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
241 2
|
10月前
|
JavaScript 前端开发
JavaScript中的原型 保姆级文章一文搞懂
本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
161 1
JavaScript中的原型 保姆级文章一文搞懂