JavaScript事件循环

2021-07-25  本文已影响0人  凝固热

为什么js是单线程

JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

任务队列

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

在CPU过于忙碌的情况下,比如IO设备很慢,这时候不得不等IO结束,才能继续下一个任务。

预算,所有任务分为了两种,一种是同步任务,另一种是异步任务

同步任务指的是在主线程上排队执行任务,前一个任务执行完才能进行下一个任务;
异步任务则不一样,不进到主线程,而且进入任务队列的任务,只有任务队列通知主线程,异步任务才可以执行,该任务才会进入到主线程执行。

异步执行的运行机制如下:

事件循环

事件和回调函数

任务队列是一个事件的队列(也可以理解成消息的队列),IO设备完成一项任务,就在任务队列中添加一个事件,表示相关的异步任务可以进入执行栈了。主线程读取任务队列,就是读取里面有哪些事件。

任务队列中的事件,除了IO设备的事件以外,还包括一些用户产生的事件(比如鼠标点击、页面滚动等等)。只要指定过回调函数,这些事件发生时就会进入任务队列,等待主线程读取。

所谓回调函数,就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数

事件循环

主线程从任务队列中读取事件,这个过程是循环不断的,这种运行机制成为事件循环

Nodejs也是单线程的事件循环,但机制不同于浏览器环境

宏任务和微任务

每个线程都有自己的事件循环,所有都能独立运行,然而同源窗口共享一个事件循环以同步通信。事件循环一直运行,来执行进入任务队列中的宏任务。

宏任务

浏览器为了能够使得JS内部task与DOM任务能够有序的执行,会在一个task执行结束后,在下一个 task 执行开始前,对页面进行重新渲染 (task->渲染->task->...)
鼠标点击会触发一个事件回调,需要执行一个宏任务,然后解析HTMl。

微任务

微任务通常来说就是需要在当前 task 执行结束后立即执行的任务,比如对一系列动作做出反馈,或或者是需要异步的执行任务而又不需要分配一个新的 task,这样便可以减小一点性能的开销。只要执行栈中没有其他的js代码正在执行且每个宏任务执行完,微任务队列会立即执行。如果在微任务执行期间微任务队列加入了新的微任务,会将新的微任务加入队列尾部,之后也会被执行。微任务包括了mutation observe的回调还有接下来的例子promise的回调。

解析如下例子:

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

new Promise((resolve) => {
    console.log('2');
    resolve()
}).then(() => {
    console.log('3');
}).then(()=>{
    console.log('4')
})
宏任务与微任务
上一篇 下一篇

猜你喜欢

热点阅读