【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
那么浏览器的时间循环加上异步呢?
异步的一些函数调用,比如setTimeout()
setTimeout()
的本质不是和 JS 在一条道上的,而是一个 webapi,这个会被放入事件队列- 事件队列中的函数,会放在调用栈,在合适的时机执行
- 下面这个图还蛮形象的
出自于这篇文章JavaScript Visualized: Promises & Async/Await
写的超好!
4-2 综合
下面通过一个面试题说一下执行过程
先说结论
主线程任务 > 微任务(Promise,queueMicrotask) > 宏任务 (setTimeout)等等
下面又是一个面试题,增加了 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(); }
下面来了
上面有一个当初有一个疑问就是setTimeout()
不是宏任务吗?为什么还是第二步?
其实这是一个认知错误,setTimeout()
本身是同步的任务,里面的回调函数才是异步的宏任务!
setTimeout(function () { console.log('setTimeout'); }, 0); // 也就是说 下面这个才是回调函数的宏任务 function () { console.log('setTimeout'); }
5. 关于阻塞&非阻塞&同步&异步
其实这个吧,我以前经常搞不清,随着我学习的越来越多,我差不多知道是什么意思的。
首先 NodeJS 的组合会用 libuv 用的是事件队列,和 Java 那种有线程锁不一样,你所有看起来异步的东西最后都会放进去这个 libuv,然后帮你搞,本质是不会发生脏数据那种的。
我自己的感觉吧
阻塞 非阻塞 → 指的就是调用程序的人 是对后面产生影响不,自己不干后面就不能干。
同步 异步 → 指的是执行自己是以什么形式执行的,嘛。差不多吧。反正 JS 几乎都是回调异步操作的。
其他语言是怎么实现这种同步异步操作的呢,和 JS 的事件循环不同,貌似是通过上锁 🔐 的概念。
Q&A
多个 script 标签属于什么事件循环?
刚开始浏览器是没有微任务,只有宏任务的。
宏任务和微任务执行顺序?
优先执行微任务,微任务的队列清空才会执行宏任务。
共有评论(0)