一步步来
首先我们要知道什么是线程,进程
引用官方说法:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
进程大概等于一个工程,线程大概等于一条流水线,一个进程可以利用多个线程去执行任务
浏览器
浏览器主要由4个进程组成
browser主进程
就是浏览器的主进程,一般只有一个,用于管理,协调,调配,前进后退一些大的功能,大概知道下就行GPU渲染进程
主要用于渲染一些3d动画,也是只有一个第三方插件进程
处理第三方插件的浏览器渲染进程
这个对于我们前端来说比较重要,每个tab都是拥有一个进程,每个tab直接互不干扰.
其中浏览器渲染进程又有5个线程,分别是,事件触发线程,js解析线程,网络请求线程,定时器线程,GUI线程
记忆方式:g主插渲(君主差炫,君主总是差一只很炫的宠物的意思...)
浏览器渲染进程
下面会一个个讲到渲染进程里面的5个线程;
- GUI线程
简单的说就是负责渲染页面,dom树css树render树的构成,重绘,回流都是利用这个线程.
注意:GUI线程的js线程会互斥,也就是js线程跑的时候,GUI线程就不会跑,反之亦然
其实这个也很好理解,如果js修改了页面dom结构,GUI又要不断实时更新,不太合理,所以是互斥的,等js执行完才会更新页面的渲染
网络请求线程
主要处理一些http异步请求,接受到请求之后会把回调函数交给事件队列线程,然后再给js线程去执行代码定时器线程
setTimeOut和setInterVal,这个线程专门读秒,因为js线程要执行代码,肯定没时间去读秒,所以额外有个定时器线程去处理,等到事件到了之后就会把回调函数推到事件队列线程那边js解析线程
js线程,解析js的脚本程序都是靠这个线程了,所以为什么说js是单线程的,因为只有一个线程去处理js,每个tab都拥有独立的js解析线程事件触发线程
这里面主要就是存放一些,异步的回调事件,因为js解析线程很忙,所以只能另外开辟一个线程去管理这些事件,当达成某个条件后,就会触发回调函数,推入事件触发线程,事件触发线程会根据js解析线程是否空间去推到js解析线程上解析代码
记忆方法:G网定j事(g网定急死,用2g网肯定急死人,太慢了)
任务(task)和微任务(microtask)
也有人称之为宏任务(macrotask)和微任务(microtask),这两个是什么东西呢?
宏任务:代码块,setTimeout,setInterval等,宏任务是放到事件触发队列里面的
微任务:Promise,process.nextTick等,是放到微任务队列里面的,由js线程掌管
微任务在es6里面被规范为jobs queue
总体的流程大概是这样的:
1.js解析线程先执行完栈内的代码.
2.在执行事件触发线程里面的宏任务之前,先会看看微任务队列里面是否有微任务,有就会先一步调用所有的微任务.
3.调用完微任务再触发一个宏任务.
4.执行完一个宏任务会再去找微任务.
总的来说,就是js在执行宏任务前先会把微任务执行完清空,执行完一个宏任务去清空一次微任务
下面是网上看到的一个想关的面试题,执行顺序是?为什么?
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function(a, b) {
console.log(2);
for(var i = 0; i < 10; i++) {
i == 9 && a();
}
console.log(3);
}).then(function() {
console.log(4)
});
console.log(5)
..........
................
.................
.................
..................
...................
.................
答案是 2 3 5 4 1 你答对了吗?为什么呢?
首先:
235我相信是没啥疑问的,就是js解析由上往下的执行顺序,
41在235后面,是因为都是异步的回调,所以会把回调的内容先挂起来
来看看setTimout里面的,因为虽然后面的参数是0(据说浏览器有限制最小是4毫秒).执行这段代码的时候,会先把setTimeout交给定时器线程去数,不管是0毫秒还是4毫秒也好,定时器线程数完之后就会把回调函数推到事件触发线程中.但是这个时候js解析线程还在解析下面的代码,要等js解析线程先把代码执行完,才会执行事件触发线程里面存着的回调!
最后再来看看promise里面的回调,输出的4,promise的异步回调有点不一样,它不放到事件触发线程里面,它会放到由js解析线程管理的微任务队列里面,所以js先解析完栈里面的代码,也就是输出了235之后,再执行事件触发线程里面的定时器给的回调之前,先会把微任务队列清空了,所以会先输出4,清空完再去执行事件触发线程里面的任务,也就是输出了1
所以这也是为什么setTimeOut和setInterVal有时会不准确,如果js解析线程还没解析完栈内的代码,就不能执行定时器的回调了,即使是定时器的时间到了,也没办法!