本文主要是介绍Event Loop,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Event Loop
一、线程与进程
- JS 是单线程执行的,指的是一个进程里只有一个主线程
1.1.概念
- 进程是 CPU资源分配的最小单位;线程是 CPU调度的最小单位
- 进程好比工厂,有单独的专属自己的工厂资源。
- 线程好比工人,多个工人在一个工厂中协作工作,工厂与工人是 1:n的关系。也就是说一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
- 多个工厂之间独立存在
1.2.多进程与多线程
- 多进程:在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。
- 多进程带来的好处,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。
- 多线程:程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
- 以Chrome浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程,一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。
1.3.单线程
- 一只有当上一个任务完成之后才会继续完成下一个任务。==>同步操作的概念。
- 带来的问题:如果前一个任务耗时很长,后一个任务就得一直等着。这就会导致I/O操作(耗时但cpu闲置)时造成性能浪费的问题。
- 解决:异步操作,可以一起执行多个任务。主线程完全可以不管IO操作,暂时挂起处于等待中的任务,先运行排在后面的任务。等到I/O操作返回了结果,再回过头,把挂起的任务继续执行下去。
- I/O:将数据读入内存或者内存输出的过程
二、浏览器内核
2.1.GUI渲染线程
- 主要负责页面的渲染,解析HTML、CSS,构建DOM树,布局和绘制等。
- 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
- 该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起,当任务队列空闲时,主线程才会去执行GUI渲染。
2.2.JS引擎线程
- 主要负责处理 JavaScript脚本,执行代码。
- 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS引擎线程的执行。
- 当然,该线程与 GUI渲染线程互斥,当 JS引擎线程执行 JavaScript脚本时间过长,将导致页面渲染的阻塞。
var x = true;
while(x){//死循环,会阻塞进程
console.log(x);
};
console.log("don't carry out"); //不会执行
2.3.定时器触发线程
- 负责执行异步定时器一类的函数的线程,如: setTimeout,setInterval。
- 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS引擎线程执行。
2.4.事件触发线程
- 比如 setTimeout定时器计数结束, ajax等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS引擎线程的执行。
5.异步http请求线程
- 负责执行异步请求一类的函数的线程,如: Promise,axios,ajax等。
- 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待JS引擎线程执行。
三、浏览器中的Event Loop
3.1.执行栈和任务队列
执行栈
- 一个专门用来存放执行代码的栈内存结构,执行的主线程。
任务队列
- 当遇到一个异步事件后,并不会一直等待异步事件返回结果,而是会将这个事件挂在与执行栈不同的队列中,我们称之为任务队列。
- 需要注意的是异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
3.2.同步任务和异步任务
- JS是单线程的,可以将所有任务分成两种,一种是同步任务,另一种是异步任务。
同步任务
- 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。
异步任务
- 不进入主线程,而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
3.3.macro-task和micro-task
- 异步任务有两种:macro-task(宏任务)和micro-task(微任务)
- 宏任务主要包含:setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等。
- 微任务主要包含:Promise、MutaionObserverhtml(html5新特性)
3.4.Js异步执行的运行机制
- 所有同步任务都在主线程上执行,形成一个执行栈。
- 主线程之外,还存在一个任务队列。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
- 一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
3.5.Event Loop 过程解析
-
一开始执行栈空,macro 队列里有且只有一个 script 脚本(整体代码)。
-
全局上下文(script 标签)被推入执行栈,同步代码执行。在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。
-
同步代码执行完了,script 脚本会被移出 macro 队列,调用栈Stack会清空。
-
执行所有的微任务。逐个执行队列中的任务并把它出队,直到队列被清空。
-
上述过程循环往复,直到两个队列都清空
-
注:宏任务是一个一个的。微任务是一队一队的。只有微任务队列清空才执行宏任务
经典例子:
console.log(0);
setTimeout(function() {//t1-宏任务
console.log(1)
}, 0);
Promise.resolve().then(() => {//t2-微任务
console.log(2)
});
console.log(3)
//打印顺序是 0 3 2 1
- script 任务先运行。首先输出 0,遇到Promise实例then微任务,setTimeout宏任务。此时 macro-task有 t1,micro-task有t2,继续执行输出3。
- 执行所有的微任务t2,输出 2
- 执行宏任务任务t3,输出1
- 队列空,执行完毕
复杂一点的例子:
new Promise(resolve => {
resolve(1);
Promise.resolve().then(() => { // t2-微任务
console.log(2)
});
setTimeout(function(){//t3-宏任务
console.log(3);
Promise.resolve().then(()=>{//t5-微任务
console.log('t5')
})
},0)
console.log(4)
}).then(t => {//t1-微任务
console.log(t)
});
setTimeout(function(){//t4-宏任务
console.log(5);
},100)
console.log(6);
/*
script执行:t1-微队列,t2-微任务,t3-宏任务,输出4,t4-宏任务,输出6
==>队列:微-> t2-微队列,t1-微任务
宏-> t3-宏任务,t4-宏任务
执行所有的微任务t2、t1:输出2,输出1
(注:先执行t2、再执行t1,resolve是用来表示promise的状态,只有promise调用then的时候,then里面的函数才会被推入微任务中。)
==>队列:微->
宏-> t3-宏任务,t4-宏任务
执行t3-宏任务:输出3,t5-微任务
==>队列:微-> t5-微任务
宏-> t4-宏任务
//执行所有的微任务t5:输出t5
==>队列:微->
宏-> t4-宏任务
执行t4-宏任务:输出5
==>队列:微->
宏->
最后输出结果是:4,6,2,1,3,t5,5
*/
参考自:
Js事件循环(Event Loop)机制
这篇关于Event Loop的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!