1、JS 线程:
进程:
一个进程就是一个程序,比如我们用浏览器打开1个网页,就是开启了1个进程,打开3个网页就是开启了3个进程。
线程:
一个进程的运行,需要很多个线程的相互配合,比如我们打开QQ这个进程,可能同时有接收消息线程、传输文件线程、检测安全线程等等等,所以一个网页能够正常运行并和用户实现交互,也需要多个进程相互配合。
浏览器有很多线程:
-
GUI 渲染线程
-
JS 引擎线程
-
定时器触发线程 (setTimeout)
-
浏览器事件线程 (onclick)
-
http 异步线程
-
EventLoop轮询处理线程
...
我们都知道JS是单线程的,即JS的代码只能在一个线程上运行,也就说,JS同时只能执行一个js任务,原因就是 JS的主要用途是与用户互动和操作DOM。设想一段JS代码,分发到两个并行互不相关的线程上运行,一个线程在DOM上添加内容,另一个线程在删除DOM,那么会发生什么?以哪个为准?所以为了避免复杂性,JS从一开始就是单线程的。
JS 引擎图:
2、事件循环:
事件循环英文名叫 Event Loop,其中事件循环可以拆分为 “事件” + “循环”。
事件包括很多,像我们常见的点击事件(click)、鼠标事件(Mouseover)等交互事件;事件冒泡、事件委托等;addEventListener、removeEventListener()等。当然,有了事件,肯定要有事件处理器,当事件发生时,在事件处理器中做一些事件操作。
但是当这些事件发生时,浏览器是怎么知道发生了这些事件?
用伪代码来表示,如果没有事件循环,那我们的线程只是静态的,当代码执行完一次之后,代码就失效了,那对于 I/O 事件是没有办法捕获的,但是如果我们有了事件循环,将进程的渲染变为激活状态,然后我们再事件监听器中做出响应。
//如果没有事件循环,函数执行后该段代码就失效了
function mainThread() {
console.log("Hello World!");
}
mainThread();
//---------------------
//有事件循环时
function clickTrigger() {
return "我点击按钮了"
}
function mainThread(){
while(true){
if(clickTrigger()) {
console.log(“通知click事件监听器”)
}
clickTrigger = null;
}
}
mainThread();
//在事件处理器中对事件进行处理
button.addEventListener('click', ()=>{
console.log("多亏了事件循环,我(浏览器)才能知道用户做了什么操作");
})
3、消息队列:
消息队列也叫任务队列,是一个静态的队列存储结构,存储异步成功后的后调函数字符串,先成功的回调函数排在队列前面。需要注意的是,异步函数成功后,才把回调函数放进队列中,而不是一开始就把所有的异步函数直接放进队列里面。如我们设置一个定时器,3秒后执行一个函数,那这个函数一定是在3秒后才放入队列中。
代码:
1 var a = 2;
2 setTimeout(fun A)
3 ajax(fun B)
4 console.log()
5 dom.onclick(func C)
在这段代码中,主线程运行到第二行 setTimeout(fun A) 时,会把这行代码交给定时器触发线程去执行;运行到第三行 ajax(fun B) 时,会把这行代码交给 http 异步线程去执行;运行到第五行 dom.onclick(func C) 时,会把这行代码交给浏览器事件循环去执行。然后这些异步代码的回调函数会被各自的执行线程保存着,在将来的某个时候,把这些回调函数交给 EventLoop 轮询处理线程去执行。
4、宏任务与微任务:
宏任务:
- setTimeout,setInterval,setImmediate,requestAnimationFrame
- UI交互事件
- 网络请求等等
微任务:
- Promise.then (Promise执行本身时是属于同步代码,只有.then才是微任务)
- process.nextTick(Node.js 环境)
时间循环,消息队列与宏任务、微任务之间的联系:
- 宏任务加入消息队列;
- 每个宏任务队列里面有微任务队列,执行宏任务的过程中遇到微任务,就把微任务加入到微任务队列中;
- 宏任务微任务的队列都为空时才会执行下一个宏任务;
- 事件循环捕获队列出队的宏任务和微任务执行。
事件循环会不断地处理消息队列出队的任务,而宏任务指的就是入队到消息队列中的任务,每个宏任务都有一个微任务队列,宏任务在执行过程中,如果此时产生微任务,那么会将产生的微任务入队到当前的微任务队列中,在当前宏任务的主要任务完成后,会依次出队并执行微任务队列中的任务,直到当前微任务队列为空才会进行下一个宏任务。
执行顺序:
执行同步代码 ==> 检查微任务并执行 ==> 执行宏任务1 ==> 检查微任务并执行 ==> 执行宏任务2 ==> 检查微任务并执行 ==> 执行宏任务3 ......
总的结论就是,执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务中进行下一轮循环。
这类知识掘金上有好多文章可供学习,我这里也是在学习中做的简要总结。