线程与进程
概念:
- 进程是CPU资源分配的最小单位
- 线程是CPU调度得最小单位
浏览器内核
浏览器默认有三个进程:
一. 浏览器主进程(Browser进程)
作用:
- 负责浏览器界面显示,用户交互(前进、后退,关闭等)
- 负责各个页面的管理,如创建和销毁其它进程
- 将Render进程中得到的内存中的bitmap,绘制到用户界面上
- 网络资源的管理,如下载
分类:
- GUI渲染线程
.主要负责页面的渲染,解析HTML,CSS,构建DOM树,布局和绘制等。
.当界面需要重绘或者由于某种操作引发回流时,将执行该线程
.该线程与JS引擎线程互斥,当执行JS引擎线程时,GUI渲染会被挂起,当任务队列空闲时,JS引擎才会去执行GUI渲染。
- JS引擎线程
.该线程当然是主要负责处理 JavaScript脚本,执行代码。
.也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS引擎线程的执行。
.当然,该线程与 GUI渲染线程互斥,当 JS引擎线程执行 JavaScript脚本时间过长,将导致页面渲染的阻塞。
- 事件触发线程
.主要负责将准备好的事件交给JS引擎线程执行
.比如setTimeout定时器计数结束,ajax等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待JS引擎线程的执行。
- 定时器触发线程
.顾名思义,负责执行异步定时器一类的函数的线程,如:setTimeout,setInterval。
.主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待JS殷勤线程执行。
- HTTP请求线程
.顾名思义,负责执行异步请求一类的函数的线程,如:promise,axios,ajax等。
.主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待JS引擎线程执行。
二. GPU进程
用于3d绘制等,如为dom元素添加css3样式-webkit-transform:translateZ(0),将使浏览器的渲染从cup转向gpu,开启gpu加速。
三. 浏览器渲染进程(Render进程)也被称为浏览器内核。
浏览器渲染进程(浏览器内核),主要负责页面的渲染、JS执行以及事件的循环。
Browser进程,Render进程,GPU进程三者是如何协作的呢?
Browser进程收到用户的请求,首先由UI线程处理,而且将相应的任务转给IO线程,他随机将该任务传递给Render进程;
Render进程的IO线程经过简单解释后交给渲染线程,渲染线程接收请求,加载网页并渲染网页,这其中可能需要Browser进程获取资源和需要GPU进程来帮助渲染,最后Render进程将结果由IO线程传递给Browser进程;
Browser进程接收到结果并将结果绘制出来;
同步任务和异步任务
-
同步任务 即可以立即执行的任务,例如 console.log() 打印一条日志、声明一个变量或者执行一次加法操作等。
-
异步任务 相反不会立即执行的事件任务。异步任务包括宏任务和微任务(后面会进行解释~)。
每个宏任务都包含了一个微任务队列:
常见的异步操作:
- Ajax
- DOM的事件操作
- setTimeout
- Promise的then方法
- Node的读取文件
事件循环完整的执行过程
- 代码开始执行,JavaScript 引擎对所有的代码进行区分。
- 同步代码被压入栈中,异步代码根据不同来源加入到宏任务队列尾部,或者微任务队列的尾部。
- 等待栈中的代码被执行完毕,此时通知任务队列,执行位于队列首部的宏任务。
- 宏任务执行完毕,开始执行其关联的微任务。
- 关联的微任务执行完毕,继续执行下一个宏任务,直到任务队列中所有宏任务被执行完毕。
- 执行下一个任务队列。
//写出下面代码的运行结果:
console.log("1");
setTimeout(function() {
console.log("2")
}, 0)
setTimeout(function() {
console.log("3")
}, 200)
console.log("4");
宏任务和微任务
异步任务:宏任务和微任务,宏任务队列可以有多个,微任务队列只有一个
宏任务和微任务的执行方式在浏览器和 Node 中有差异。
在微任务中 process.nextTick 优先级高于Promise当异步任务进入栈执行时,是宏任务还是微任务
- 由于执行代码都是全局任务script,而全局任务属于宏任务,所以当栈为空,同步任务任务执行完毕时,会先执行微任务队列里的任务
- 微任务队列里的任务全部执行完毕后,会读取宏任务队列中拍最前的任务
- 执行宏任务的过程中,遇到微任务,依次加入微任务队列
- 栈空后,再次读取微任务队列里的任务,依次类推
案例分析:
setTimeout(function() {
console.log(1)
Promise.resolve().then(() => {
console.log(2)
})
}, 0)
setTimeout(function() {
console.log(3)
}, 200)
new Promise(function(resolve, reject) {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
console.log(6);
- 我们遇到的第一个宏任务setTimeout,放到我们异步队列里面,我们暂时记为setTimeout1,直接加入宏任务队列
- 再次遇到了一个宏任务setTimeout,放到我们异步队列里面,我们暂时记为setTimeout2,直接加入宏任务队列
- 接下来是一个promise对象,在实例化对象中console.log(4)属于同步任务所以,直接打印4,但是then()属于微任务,并且加入微任务队列中
- 最后遇到打印语句,直接打印6
- 栈空后,先执行微任务队列中的then(),输出5,此时微任务队列为空
7. 先执行console.log(1),此时我们打印出来1,后面有一个then(),加入到微任务。 8. 然后执行微任务队列中的then()方法,输出2,此时微任务队列为空; 9. 继续读取宏任务队列的最靠前的任务 setTimeout2。
10. 直接执行打印语句,打印日志 3。执行完毕。
先通过一个小案例来说明一下async函数:
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
async1();
console.log('script end')
await 要等的东西分为两种情况:
- 是promise对象
- 不是promise对象
- 如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。
- 如果不是 promise , await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完,再回到async内部,把这个非promise的东西,作为 await表达式的结果。
小案例的实现过程分析:
- 首先代码是自上而下执行,所以先调用的async1(),那么就先执行async1函数,先打印async1 start;
- await 后面是等待的async2函数的返回值,从右向左的。先打印async2,后打印的 script end;
- 最后是打印 async1 end
所以小伙伴们大家来看一下:
//大家开动脑筋看一道比较经典的面试题:
async function async1() {
console.log('async1 start') //2
await async2()
console.log('async1 end') // 6
}
async function async2() {
console.log('async2') //3
}
console.log('script start') // 1
setTimeout(function() {
console.log('setTimeout') //8
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1') //4
resolve();
}).then(function() {
console.log('promise2') //7
})
参考:
8 张图帮你一步步看清 async/await 和 promise 的执行顺序
事件循环机制的那些事
从 薛定谔的猫 聊到 Event loop
微任务、宏任务与Event-Loop