简言
最近在做前端知识的复习和整理,有了一些自己新的体会。更多在于记录,通过反复的温习,写笔记消除自己以前学习知识点的误区
进程/线程
进程
cpu分配资源的最小单位,进程和进程之间相互独立,进程下管理着多个线程
线程
cpu调度资源的最小单位,具体的工作由线程来执行,多个线程共享cpu分配给进程的资源
浏览器新开tab是进程还是线程?
浏览器新开tab是进程,进程之间相互独立,不影响cpu分配给各自的资源。如果新开tab是线程的话,那么假如一个线程占用资源时间长,可能导致内存溢出,造成整个浏览器的崩溃
浏览器渲染原理
浏览器可以通过开启多个tab,构建多个页面,那么每个页面都构成实际都是一个独立的进程,进程下又控制着多个线程来完成页面的搭建工作
构建页面进程下大致可以归类为5类线程
-
GUI线程
解析HTML文档,构建CSS样式,并由构建的DOM tree和样式计算后生成的CSSOM tree生成render tree,再根据布局,分层,分块,栅格化,合成等流程将页面渲染到浏览器页面上;平时我们所说的减少回流重绘也是在这个过程中出现的;
回流:布局阶段-改变了节点的位置大小等信息,例如position,width,height,font-size等
重绘:改变样式信息,例如color,background
-
JS线程(JS引擎)
用于解析script,以及执行js代码,并处理来自于后续任务队列(task queue)中的待处理逻辑,不过JS线程执行会
阻塞GUI线程,因为这是为了防止渲染错乱
-
定时器线程(setTimeout,setInterval)
用于接收来自于JS线程的异步定时器任务,但是它
只做定时器的计时,不做异步任务,当计时完成之后,将结果交给事件处理线程 -
异步网络线程
用于接收来自JS线程的异步请求任务,执行异步请求操作,当结果返回之后将回调任务交给事件处理线程
-
事件处理线程
将接收到的来自异步操作线程的任务按照接收顺序,以及添加到 task queue 中
下面是上述的图片过程展示
同步异步/Event Loop
看上面的图,是不是很像Event Loop事件循环的示意图?
接下来就讲一下js中的同步异步,以及Event Loop(事件循环)
同步/异步
js引擎是单线程,意味任务只能一个一个执行
但js引擎在执行js代码过程中实现了同步和异步,这就令js有了处理复杂逻辑时发生阻塞的解决办法
同步执行:代码从上到下依次执行,并且遇到复杂或高优先级任务时会阻塞后续代码执行(复杂循环,渲染任务等)
异步执行:代码从上到下依次执行,遇到复杂任务时,将复杂任务挂起(js引擎交由由异步线程执行),js引擎并继续执行后续同步代码代码;当异步任务执行结束之后,将回调结果交给js线程继续处理
Event Loop
而要支持js引擎不断去管理并处理异步线程回调结果,那么就需要 执行栈(Call Stack) 和 任务队列(task queue) 的通力配合,因此形成了事件循环(Event Loop)
但我更愿意叫 事件的持续处理 - 强调事件的连贯而不是循环
Event Loop 分为两部分,执行栈(Call Stack)和任务队列(task queue),如果细分的话按下图所示
这张图中分为了5部分
- Memory Heap 堆内存
- js引擎
- Call Stack 执行栈
- 异步线程
- task queue 任务队列
Memory Heap(堆内存)
代码开始执行时会使用进程分配的内存资源存储如Object,Function,Array等结构化数据,并提供堆中的地址给执行栈中的逻辑使用
js 引擎
js引擎(js线程)用于创建执行代码的调用栈并添加js代码执行,当调用栈为空时检查任务队列是否有待处理任务,如果有,则依次从任务队列的队头取出任务添加到调用栈执行
Call Stack(调用栈)
调用栈的js代码同步执行,过程中如果遇到异步任务,则将异步任务添加到对应的异步线程中,继续执行剩余代码,直到将所有同步代码执行完毕
异步线程
异步线程接收到来自调用栈的异步任务后,进行异步处理,等待结果返回后,通过回调的方式将任务添加到任务队列的队尾
task queue
任务队列遵循先入先出原则,类似排队结账,后来者排队尾,先到者先结账。
任务队列存储的一定是异步任务的回调结果(异步I/O,Http,定时器等)
那既然Event Loop这么全面了,那为什么还要有宏任务和微任务呢?
这就要说到任务的紧急性了
看下面这个例子
setTimeout(()=>{
console.log(1)
setTimeout(()=>{
console.log(2)
})
})
setTimeout(()=> {
console.log(3)
})
// 结果为 1 3 2
如果console.log(2)是一个紧急任务,必须要在console.log(3)之前执行怎么办?
这就是微任务出现的原因
为了解决处在任务队列中任务优先级,时效性的问题
setTimeout(()=>{
console.log(1)
new Promise((resolve)=>{
resolve()
}).then(()=>{
console.log(2)
})
})
setTimeout(()=> {
console.log(3)
})
// 结果为 1 2 3
那么有微任务就一定有宏任务
宏任务:每个任务都可以看成是宏任务
-
可以形成宏任务的操作有:
定时器,异步I/O,数据库读取操作等
-
可以形成微任务的操作有:
Promise,defineProperty,Proxy等
当执行栈中的同步代码执行完成之后,js引擎会从task queue队头中取出第一个任务(宏任务)到执行栈中,宏任务中的代码同步执行,当遇到微任务操作时,会将微任务添加到微任务队列中,当同步代码执行完成之后,js引擎检查微任务队列中是否有未执行的任务,如果有则依次取出执行,当微任务队列被清空后,js引擎会清空调用栈,从任务队列中取出下一个宏任务