...

【JavaScript】浏览器的事件循环 EventLoop

浏览器的事件循环

1 前言

首先说一下,JS 运行环境不只是有 浏览器 还有 Node。这里说的也就是浏览器的事件循环,Node 这里不做赘述。

可以说一下就是代码在浏览器的顺序是如何实现的?

首先 JS 是单线程的!浏览器都是多进程的,1 个网页可能就一个进程了。1 个进程有多个线程。可能有网络线程,有渲染线程,JS 引擎也是一个线程。

比如 JS 里面会有 setTimeout 操作,难道 setTimeout 结束之前,其他都被阻塞了吗?NO,不是的,而是 JS 交给别人来做了。JS 明明是单线程,怎么还能交给别人做?这就是浏览器的事件循环的调度了。浏览器会有另一个线程帮你执行。当 setTimeout 时间到的时候,会把里面的任务放入队列里。

JS 之所以可以实现异步执行,依靠的就是事件循环。

2 线程 & 进程

这里就不写线程和进程的区别,反正进程(process)就是一个很大的概念,线程(thread)就是很小的,1 个进程可能由多个线程组成。就这样记忆。

那么浏览器有多少线程呢?搞清事件循环宏任务微任务 感觉这个写的还可以,引用一下

/**  
 * 1、GUI 渲染线程 (可以理解为html css渲染的线程)  
 * 2、JS 引擎线程 (主要说的就是这里的任务队列,专门用来解析JS的)  
 * 3、定时器触发线程 (setInterval/setTimeout这样)  
 * 4、浏览器事件线程 (click等等)  
 * 5、http 请求线程 (XMLHttpRequest)  
 */  

3 任务分类

那这么多任务,分为什么呢?

同步任务 → 从上到下,依次执行

console.log(1);  
console.log(2);  
console.log(3);  

异步任务 → 自己单独行动

console.log('1');  
setTimeout(() => {  
  console.log(2);  
}, 1000);  
console.log(3);  

宏任务 macrotask queue

ajax、setTimeout、setInterval、DOM 监听、UI Rendering,IO

微任务 microtask queue

Promise 的 then 回调、 Mutation Observer API、queueMicrotask()等

这里注意了,写的是 Promise 的 then 回调是微任务,不是说 Promise 就是微任务,setTimeout 也同理,setTimeout 本身也是同步代码,里面的函数才是宏任务的。setTimeout(()=>{})

new Promise((resolve, reject) => {  
  // 这里不是微任务 这里属于正常的同步代码  
  resolve(1);  
}).then((res) => {  
  // 🔥 这里才是  
  console.log(res); // 🔥 这里才是  
});  

4 执行顺序 ⭐️

JS 的代码流程是什么?因为是单线程,难道一个个都堵塞吗?其实不是,因为有事件循环。

4-1 正常执行

栈这里面记录了一个简单的代码执行,代码默认在main.js

image-20220403220431866

那么浏览器的时间循环加上异步呢?

异步的一些函数调用,比如setTimeout()

  • setTimeout()的本质不是和 JS 在一条道上的,而是一个 webapi,这个会被放入事件队列
  • 事件队列中的函数,会放在调用栈,在合适的时机执行
  • 下面这个图还蛮形象的

出自于这篇文章JavaScript Visualized: Promises & Async/Await

写的超好!

img

4-2 综合

下面通过一个面试题说一下执行过程

先说结论

主线程任务 > 微任务(Promise,queueMicrotask) > 宏任务 (setTimeout)等等

image-20220403231935375

下面又是一个面试题,增加了 await 和 async

这个必须要先了解一下生成器和迭代器,一起食用更佳。因为 await 和 async 本质就是语法糖而已。

new Promise(function (resolve) {  
  console.log('promise1');  
  resolve(  );  
}).then(function () {  
  console.log('then1');  
});  

// await这一句 相当于 就是立即执行这一块  
function (resolve) {  
  console.log('promise1');  
  resolve();  
// await后面的那一句 相当于then  

// 于是就感觉是这样的  
async funtion async1() {  
  await async2;  
  console.log('then1');  
}  
async funtion async2() {  
  console.log('promise1');  
  resolve();  
}  

下面来了

image-20220403234123397

上面有一个当初有一个疑问就是setTimeout()不是宏任务吗?为什么还是第二步?

其实这是一个认知错误,setTimeout()本身是同步的任务,里面的回调函数才是异步的宏任务!

setTimeout(function () {  
  console.log('setTimeout');  
}, 0);  
// 也就是说 下面这个才是回调函数的宏任务  
function () {  
  console.log('setTimeout');  
}  

5. 关于阻塞&非阻塞&同步&异步

其实这个吧,我以前经常搞不清,随着我学习的越来越多,我差不多知道是什么意思的。

首先 NodeJS 的组合会用 libuv 用的是事件队列,和 Java 那种有线程锁不一样,你所有看起来异步的东西最后都会放进去这个 libuv,然后帮你搞,本质是不会发生脏数据那种的。

我自己的感觉吧

阻塞 非阻塞 → 指的就是调用程序的人 是对后面产生影响不,自己不干后面就不能干。

同步 异步 → 指的是执行自己是以什么形式执行的,嘛。差不多吧。反正 JS 几乎都是回调异步操作的。

其他语言是怎么实现这种同步异步操作的呢,和 JS 的事件循环不同,貌似是通过上锁 🔐 的概念。

Q&A

多个 script 标签属于什么事件循环?

刚开始浏览器是没有微任务,只有宏任务的。

宏任务和微任务执行顺序?

优先执行微任务,微任务的队列清空才会执行宏任务。

共有评论(0)

登陆即可评论哦