带你深入理解js事件循环机制

news2024/11/18 23:30:08

同步任务和异步任务(微任务和宏任务)

JavaScript是一门单线程语言

分为同步任务和异步任务

同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。

异步任务指的是,不进入主线程、而进入"任务队列"的任务;只有等主线程任务全部执行完毕,"任务队列"的任务才会进入主线程执行。

异步任务分为宏任务和微任务

new promise()、console.log()属于同步任务

宏任务(macrotask)微任务(microtask)
谁发起的宿主(Node、浏览器)JS引擎
具体事件1. script (可以理解为外层同步代码) 2. setTimeout/setInterval 3. UI rendering/UI事件 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js)1. Promise 2. MutaionObserver 3. Object.observe(已废弃;Proxy 对象替代) 4. process.nextTick(Node.js)
谁先运行后运行先运行
会触发新一轮Tick吗不会

执行过程: 同步任务 —> 微任务 —> 宏任务

1.先执行所有同步任务,碰到异步任务放到任务队列中
2.同步任务执行完毕,开始执行当前所有的异步任务
3.先执行任务队列里面所有的微任务
4.然后执行一个宏任务
5.然后再执行所有的微任务
6.再执行一个宏任务,再执行所有的微任务·······依次类推到执行结束。

3-6的这个循环称为事件循环Event Loop

事件循环是JavaScript实现异步的一种方法,也是JavaScript的执行机制

async/await (重点)

(个人注解:async/await 底层依然是 Promise,所以是微任务,只是 await 比较特殊)

async

当我们在函数前使用async的时候,使得该函数返回的是一个Promise对象

async function test() {return 1 // async的函数会在这里帮我们隐士使用Promise.resolve(1)
}
// 等价于下面的代码
function test() { return new Promise(function(resolve, reject) { resolve(1) })
}
// 可见async只是一个语法糖,只是帮助我们返回一个Promise而已 

await

await表示等待,是右侧「表达式」的结果,这个表达式的计算结果可以是 Promise 对象的值或者一个函数的值(换句话说,就是没有特殊限定)。并且只能在带有async的内部使用

使用await时,会从右往左执行,当遇到await时, ★★★★★会阻塞函数内部处于它后面的代码,去执行该函数外部的同步代码,当外部同步代码执行完毕,再回到该函数内部执行剩余的代码★★★★★, 并且当await执行完毕之后,会先处理微任务队列的代码

示例

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

// 先执行1 输出1
// 执行到2,把setTimeout放入异步的任务队列中(宏任务)
// 执行到3,把process.nextTick放入异步任务队列中(微任务)
// 执行到4,上面提到promise里面是同步任务,所以输出7,再将then放入异步任务队列中(微任务)
// 执行到5,同2
// 上面的同步任务全部完成,开始进行异步任务
// 先执行微任务,发现里面有两个微任务,分别是3,4压入的,所以输出6 8
// 再执行一个宏任务,也就是第一个setTimeout
// 先输出2,把process.nextTick放入微任务中,再如上promise先输出4,再将then放入微任务中
// 再执行所以微任务输出输出3 5
// 同样的,再执行一个宏任务setTImeout2,输出9 11 在执行微任务输出10 12
// 所以最好的顺序为:1 7 6 8 2 4 3 5 9 11 10 12 
async function async1() {console.log( 'async1 start' )await async2()console.log( 'async1 end' )
}

async function async2() {console.log( 'async2' )
}
console.log( 'script start' )
setTimeout( function () {console.log( 'setTimeout' )
}, 0 )
async1();
new Promise( function ( resolve ) {console.log( 'promise1' )resolve();
} ).then( function () {console.log( 'promise2' )
} )
console.log( 'script end' )

// 首先执行同步代码,console.log( 'script start' )
// 遇到setTimeout,会被推入宏任务队列
// 执行async1(), 它也是同步的,只是返回值是Promise,在内部首先执行console.log( 'async1 start' )
// 然后执行async2(), 然后会打印console.log( 'async2' )
// 从右到左会执行, 当遇到await的时候,阻塞后面的代码,去外部执行同步代码
// 进入new Promise,打印console.log( 'promise1' )
// 将.then放入事件循环的微任务队列
// 继续执行,打印console.log( 'script end' )
// 外部同步代码执行完毕,接着回到async1()内部, 继续执行 await async2() 后面的代码,执行 console.log( 'async1 end' ) ,所以打印出 async1 end 。(个人理解:async/await本质上也是Promise,也是属于微任务的,所以当遇到await的时候,await后面的代码被阻塞了,应该也是被放到微任务队列了,当同步代码执行完毕之后,然后去执行微任务队列的代码,执行微任务队列的代码的时候,也是按照被压入微任务队列的顺序执行的)
// 执行微任务队列的代码, 打印 console.log( 'promise2' )
// 进入第二次事件循环,执行宏任务队列, 打印console.log( 'setTimeout' )
/**
 * 执行结果为:
 * script start
 * async1 start
 * async2
 * promise1
 * script end
 * async1 end
 * promise2
 * setTimeout
 */ 
console.log(1);
async function fn(){console.log(2)new Promise((resolve)=>{resolve();}).then(()=>{console.log("XXX")})await console.log(3)console.log(4)
}
fn();
new Promise((resolve)=>{console.log(6)resolve();
}).then(()=>{console.log(7)
})
console.log(8)

// 执行结果为:1 2 3 6 8 XXX 4 7
/*
前面的 1 2 3 6 8 不再解析,重点是后面的 XXX 4 7,由此可见 await console.log(3) 之后的代码 console.log(4) 是被放入到微任务队列了,
代码 console.log("XXX") 也是被压入微任务队列了,console.log("XXX")是在 console.log(4) 之前,
所以当同步任务执行完毕之后,执行微任务队列代码的时候,优先打印出来的是 XXX ,然后才是 4 。
*/ 
console.log(1);
async function fn(){console.log(2)await console.log(3)await console.log(4)await console.log("await之后的:",11)await console.log("await之后的:",22)await console.log("await之后的:",33)await console.log("await之后的:",44)
}
setTimeout(()=>{console.log(5)
},0)
fn();
new Promise((resolve)=>{console.log(6)resolve();
}).then(()=>{console.log(7)
})
console.log(8)

/**
 * 执行结果为:
 * 1
 * 2
 * 3
 * 6
 * 8
 * 4
 * 7
 * await之后的: 11
 * await之后的: 22
 * await之后的: 33
 * await之后的: 44
 * 5
 */
/*
由此可见,代码执行的时候,只要碰见 await ,都会执行完当前的 await 之后,
把 await 后面的代码放到微任务队列里面。但是定时器里面的 5 是最后打印出来的,
可见当不断碰见 await ,把 await 之后的代码不断的放到微任务队列里面的时候,
代码执行顺序是会把微任务队列执行完毕,才会去执行宏任务队列里面的代码。
*/ 
Promise.resolve().then(() => {console.log(0);return Promise.resolve(4) // 顺延2位如果是return 4 则打印 0、1、4、2、3、5、6、7
}).then(res => console.log(res))
 
Promise.resolve().then(() => {console.log(1);
}).then(() => {console.log(2);
}).then(() => {console.log(3);
}).then(() => {console.log(5);
}).then(() => {console.log(6);
}).then(() => {console.log(7);
})
/*
此题主要注意的是原生的Promise的then方法中,如果返回的是一个普通值,则返回的值会被立即调用并赋值给resolve函数,
如果返回的是一个thenable,则then方法将会被放入到微队列中执行,
如果返回的是一个Promise.resolve,则会再加一次微任务队列。
即微任务后移,Promise.resolve本身是执行then方法,而then方法本身是在微任务队列中执行,
同时return Promise.resolve时是将resolve调用的返回值 作为上级then中resolve的参数传递,
调用外层then方法时本身是在微队列里面,所以函数的执行顺序是要在微队列中下移两次。

*/ 

根据w3c的最新解释

  • 每个任务都有一个任务类型 , 同一个类型的任务必须在一个队列也就是一共有多个队列 , 不同类型的任务可以分属不同的队列,在一个次事件循环中,浏览器可以根据实际情况从不同的队列中区出任务执行
  • 浏览器必须准备好一个微队列 , 微队列中的任务优先所有其他任务执行他里面的东西 所有都要给我等 连绘制任务 都要等 就是最高优先级了

随着浏览器的复杂度急剧提升 W3C不再使用宏队列的说法

在目前chrome的实现中 至少包含了下面的队列

  • 延时队列 : 用于存放计时器到达后的回调任务 , 优先级中
  • 交互列队 : 用于存放用户操作后产生的事件处理任务 , 优先级高
  • 微队列 : 用户存放需要最快执行的任务 优先级最高

添加任务到微队列的主要方式主要是使用 Promise、MutationObserver

例如:
// 立即把一个函数添加到微队列
Promise.resolve().then(函数) 

任务有优先级吗?

  • 任务没有优先级,在消息队列中先进先出
  • 消息队列是有优先级的
// 立刻把一个函数添加到微队列 最高执行
promise.resolve().then(函数)

setTimeOut(()=>{ // 第三步执行延时队列中的任务console.log(1);
},0)

promise.resolve().then(()=>{ // 第二步执行微队列中的任务console.log(2);
})

console.log(3); // 第一步先执行全局js

// 3 2 1 

面试题

1、如何理解 JS 的异步?

JS是一门单线程的语言,这是因为它运行在浏览器的渲染主线程中,而渲染主线程只有一个。

而渲染主线程承担着诸多的工作,渲染页面、执行 JS 都在其中运行。

如果使用同步的方式,就极有可能导致主线程产生阻塞,从而导致消息队列中的很多其他任务无法得到执行。这样一来,一方面会导致繁忙的主线程白白的消耗时间,另一方面导致页面无法及时更新,给用户造成卡死现象。

所以浏览器采用异步的方式来避免。具体做法是当某些任务发生时,比如计时器、网络、事件监听,主线程将任务交给其他线程去处理,自身立即结束任务的执行,转而执行后续代码。当其他线程完成时,将事先传递的回调函数包装成任务,加入到消息队列的末尾排队,等待主线程调度执行。

在这种异步模式下,浏览器永不阻塞,从而最大限度的保证了单线程的流畅运行。

2、 阐述一下js的事件循环

事件循环又叫做消息循环,是浏览器渲染主线程的工作方式。

在 Chrome 的源码中,它开启一个不会结束的 for 循环,每次循环从消息队列中取出第一个任务执行,而其他线程只需要在合适的时候将任务加入到队列末尾即可。

过去把消息队列简单分为宏队列和微队列,这种说法目前已无法满足复杂的浏览器环境,取而代之的是一种更加灵活多变的处理方式。

根据 W3C 官方的解释,每个任务有不同的类型,同类型的任务必须在同一个队列,不同的任务可以属于不同的队列。不同任务队列有不同的优先级,在一次事件循环中,由浏览器自行决定取哪一个队列的任务。但浏览器必须有一个微队列,微队列的任务一定具有最高的优先级,必须优先调度执行。

3、JS 中的计时器能做到精确计时吗?为什么?

不行,因为:

1.计算机硬件没有原子钟,无法做到精确计时
2.操作系统的计时函数本身就有少量偏差,由于 JS 的计时器最终调用的是操作系统的函数,也就携带了这些偏差
3.按照 W3C 的标准,浏览器实现计时器时,如果嵌套层级超过 5 层,则会带有 4 毫秒的最少时间,这样在计时时间少于 4 毫秒时又带来了偏差
4.受事件循环的影响,计时器的回调函数只能在主线程空闲时运行,因此又带来了偏差

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/409376.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pro1:图片的隐藏和显示

什么是JavaScript?实现目标实现代码实现效果实现方法💛作者主页:静Yu 🧡简介:CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家,前端知识交流社区创建者 💛社区地址:前端知识交…

axios 中如何取消请求_从不会使用到精通原理_番茄出品

start 最近频繁遇到一个问题,axios 是如何取消请求的?这篇文章将从新手小白的视角出发,从 axios 取消逻辑的基础使用,到原理分析,带你彻底了解并掌握 axios 中取消请求的“秘密”。编写时间:2023/02/24-23…

520到了,教你做个JavaWeb表白墙小项目

目录 1.配置 Maven 项目 1.1 创建 Maven 项目 1.2 引入相关依赖 1.3 项目总结构 2. 约定前后端交互接口 3.服务端代码 3.1 创建Message类 3.2 创建工具类 3.3 添加信息类(AddMessage) 3.4 查询信息类(MessageList) 4. …

vue3中的配置代理

如图,配置代理就是在客户端与服务器中充当中介的角色 目录 关于配置代理,首先我们的要知道为什么要配置代理,配置代理做了什么事情 一、跨域是什么 二、如何解决 三、配置代理案例 1. 下面是我自己在后端尝试解决跨域的例子 2.然后在vue中…

【Vue项目实战】Vue3中动态修改样式和级联样式优先顺序详解

Vue实战笔记目录前言一、ref修改样式1、通过 Vue 的 ref 组件获取 DOM 元素节点注:这边比较一下,和 DOM操作 document.getElementById(id) 这个方法有什么区别2、通过 ref 修改 DOM 元素节点样式二、通过v-bind对Html元素的样式(style)进行动态绑定三、v…

端午假期整理了仿天猫H5 APP项目vue.js+express+mongo

效果 源码 源码太多,放github上了点击 遇到的问题 连接mongodb数据库多个集合(model文件) mongodb与mysql数据库连接不同,sql在定义查询语句时可以连接不同的表mongodb需要在开始定义好连接要用到的表 module.exports {dbPr…

echarts-wordcloud 血泪总结使用说明 (配置项及其不足点优化)

基本使用方法 echarts-wordcloud是基于echarts的一个词云库,是我常用的一个组件,业务上用的多一点,但是这个库在echarts的官网文档里面没有说明,git上的说明也很少,有些配置需要自己摸索,下面都是我的血泪…

css动画效果

动画1&#xff08;沿着椭圆转动&#xff09; 其实是个2D转动&#xff0c;只不过咱们设置椭圆&#xff0c;然后加上scale缩放&#xff08;近大远小&#xff09;&#xff0c;看上去就是3D效果 代码&#xff1a; <!DOCTYPE html> <html lang"en"><head…

使用Node.js手撸一个建静态Web服务器,内部CV指南

文章里有全部代码&#xff0c;也可以积分下载操作步骤如上图文章结束 话说这个键盘真漂亮~~ 文章目录使用Node.js手撸一个建静态Web服务器一、动静态服务器的概念1.1 静态Web服务器概念1.2 静态Web服务器的优点1.3 快速搭建的途径二、 手撸指南2.1 框架搭建2.2 CtrlC/V2.3 启动…

Vue使用ElementUI对table的指定列进行合算

前言 最近有一个想法&#xff0c;就是记录自己花销的时候&#xff0c;table中有一项内容是花销的金额。然后想在table的底部有一项内容是该金额的总计。 然后我就顺着elementui的table组件寻找相关的demo&#xff0c;还真发现了一个这样的demo。 对于这个demo&#xff0c;官方…

css实现轮播图

轮播图&#xff1a;就是多张图片按照一定的时间和顺序依次从某个窗口来向用户展示图片 轮播图的实现代码&#xff1a; 1&#xff09;创建一个容器来进行轮播图的展示 这里的容器就是最外部的盒子 注意最外部盒子设置宽高时要与我们进行展示的图片的宽高保持一致&#xff0c…

npm i 安装不上依赖怎么办

起因 在github上下载的项目需要使用npm i来安装依赖。但是安装过程需要很久&#xff0c;然后安装完了又发现安装过程报错了。导致项目跑不起来。原因是某些依赖没有成功的安装上去 解决办法&#xff08;依次试&#xff0c;每次试新的命令最好把上次安装node_modules删掉&#…

threejs三维地图大屏项目分享

这是最近公司的一个项目。客户的需求是基于总公司和子公司的数据&#xff0c;开发一个数据展示大屏。 大屏两边都是一些图表展示数据&#xff0c;中间部分是一个三维中国地图&#xff0c;点击中国地图的某个省份&#xff0c;可以下钻到省份地图的展示。 地图上&#xff0c;会做…

(Select)解决:Element-ui 中 Select 选择器下拉框样式及输入框样式的修改问题(背景色透明与悬停背景色变化与下拉框边距变化等操作)

Ⅰ、Element-ui 提供的组件与想要目标情况的对比&#xff1a; 1、Element-ui 提供组件情况&#xff1a; 其一、Element-ui 自提供的代码情况为(示例的代码)&#xff1a; // Element-ui 自提供的代码&#xff1a; <template><el-select v-model"value" pla…

JS对象详解

JS对象详解 js的对象是什么&#xff1f;js的对象类型有哪些&#xff1f;具体实例是什么&#xff1f; 一、ECMA-262对JS对象的定义&#xff1a; 属性的无序集合&#xff0c;每个属性存放一个原始值、对象或函数&#xff1b; 对象是无特定顺序的值的数组&#xff1b; 对象是一…

用 JSP 连接 MySQL 登入注册项目实践(JSP + HTML + CSS + MySQL)

目录 一、写在前面 二、效果图 三、实现思路 四、实现代码 1、login总界面 2、registercheck总代码 3、logoutcheck总代码 4、amendcheck总代码 相关文章 jsp实现简单登入注册界面功能用jsp实现简单登入注册界面功能&#xff08;css美化&#xff09;&#xff08;软件i…

【前端点击穿透】pointer-events属性详解

什么是pointer-events&#xff1f; pointer-events 属性是一个指针属性&#xff0c;是用于控制在什么条件下特定的图形元素可以成为指针事件的目标。 This CSS property,when set to “none” allows elements to not receive hover/click events,instead the event will occur…

使用Three.js实现web端显示点云

需要了解html、js、websocket的基本使用&#xff0c;建议浏览three.js文档中场景、渲染器、光源、相机以点模型、Buffergeometry的相关知识 第一步&#xff0c;创建html文件 <!DOCTYPE html> <html><head><meta charset"utf-8"><title&…

vue3+vite在main.ts或者main.js文件中引入/App.vue报错(/App.vue不是模块)

vscode报错&#xff1a;./APP.vue不是模块场景复现情况一、vue3.0js情况二、vue3.0ts方案一&#xff1a;根目录新建env.d.ts方案二&#xff1a;根目录新建tsconfig.jsonvue3报错提示 找不到模块“/App.vue”或其相应的类型声明 场景复现 在使用 vue3➕vite➕ts 或者 js 搭建前…

微信小程序--》从零实现小程序项目案例

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;微信小程序 &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&…