前言:其实网上关于js是单线程的文章很多,但是浏览器的线程,进程很多都没有提到,所以自己也进行了一个总结,将碎片化的知识进行梳理,形成一个知识体系,有问题的地方欢迎大家一起探讨。
首先,简单了解一下进程和线程
1.进程,线程
进程 - 系统资源分配,有自己独立的地址空间,一个进程有多个线程,分别执行不同任务。进程之间不能共享资源,而线程共享所在进程的地址空间和其它资源。同时线程还有自己的栈和栈指针,程序计数器等寄存器。
线程 - cpu调度单位,它包含在进程中,是进程运行的最小单位,线程依赖进程存在,线程的切换开销相对较小
2.浏览器的进程与线程
首先,浏览器是多进程的,一般情况下打开一个tab页,就会新开一个进程,当然,这不是绝对的,有时候浏览器会自动合并多个进程(如打开多个空白页),下面这张图来印证一下浏览器的多进程的
浏览器是多进程会有很多的好处: 不会因为一个页面的崩溃导致整个浏览器崩溃,不会因为某个插件的崩溃整个浏览器崩溃等等
浏览器的进程有哪些呢?
Browser进程 - 浏览器的主进程,负责协调主控,
- 负责浏览器界面显示,与用户交互,如前进,后退等操作
- 负责各个页面的管理,创建和销毁等其他进程
- 将Render进程得到的内存中的Bitmaap,绘制到用户界面上
- 网络资源的管理,下载等等
GPU进程 - 最多一个,用于3D绘制等
渲染进程 - 默认一个Tab页面一个进程,互相不会影响,主要是用于页面的渲染,脚本的执行和事件的处理等等(在这个里面有需要线程需要进行细)
第三方插件进程 - 每种类型的插件对应一个进程,只有在使用该插件的时候才会进行创建
网络进程 - 加载页面网络资源
在这些进程中,其实在网上百度最常出现的就是渲染进程
渲染进程(浏览器内核)
1.有哪些线程?
GUI及渲染线程 - 负责渲染浏览器界面,包括html,css,构建DOM树,Render树,布局和绘制等等
- 当界面需要进行重绘或者回流都会执行
js引擎线程 - js内核,负责处理执行JavaScript脚本
- 等待任务队列的任务的到来,然后进行处理,浏览器无论什么时候都只有一个js引擎在运行js程序
事件触发线程 - 当js引擎执行到点击事件,异步触发等等,都会将对应的任务添加到事件线程中,当事件符合触发条件时,会将事件添加到处理队列的队尾,等待js引擎空闲后去执行
定时触发线程 - setTimeout,setInterval所在线程,也会是在触发时候,事件添加到队尾,等待js引擎空闲去处理
异步http请求线程 - 在XMLHttpRequest连接后启动一个线程,线程如果是检测到请求的状态变更,有回调函数,则会将回到函数添加到队尾没等待js引擎空闲进行处理
2.线程之间存在的关系?
(1) GUI渲染线程和js引擎线程是互斥的
原因很简单,假设你正在渲染一个元素让颜色变为红色,这个时候js又在操作DOM变为蓝色,同时都在操作同一个元素,那元素是什么颜色,会导致渲染出现不可预期的结果
(2)js会阻塞页面的加载(第一条就互斥的就说明了这个问题)
尽量避免js执行时间过程,会造成页面的渲染不连贯,导致页面渲染加载阻塞
3.浏览器的渲染进程具体过程
渲染进程开始之前,前期有些准备
- 浏览器输入url,浏览器主进程开始接管,开启一个下载线程
- DNS解析网址为ip地址
-浏览器开始发送一个http请求,三次握手,建立TCP/IP连接
- 服务器响应内容给客户端
- 内容通过RenderHost接口转交给Render进程,
- 浏览器渲染流程开始
有一幅图,将渲染过程清晰的展现了出来
- 解析html建立DOM树
- 解析css构建css层叠样式
- 将DOM树和css层叠样式构造为render树
- 绘制render树
- 绘制完成后,浏览器将各层信息发送给GPU,GPU会将各层合成显示在屏幕
在绘制过程中,会出现回流和重绘,css的加载不会影响DOM树的构建,但是会阻塞DOM树的渲染
说说js引擎的事件循环机制
首先要了解js引擎是单线程的
js分为同步任务和异步任务,同步任务都在主线程上执行,形成一个执行栈
主线程之外,事件触发线程管理这一个任务队列,一旦异步任务有了运行结果,就会在任务队列之中放置一个事件
一旦执行栈中的同步任务执行完毕,js引擎空闲了,就会去读取任务队列,将异步任务添加到执行栈中开始执行
对于同步和异步任务
对于宏任务和微任务
宏任务 - 整体script,setTimeout,setInterval ...
微任务 - 原生Promise中的then,process.nextTick,MutaionObserver
其实文字说明一直都是一个理解,看两个例子就明白了
案例1:
console.log("同步任务1");
function asyn(mac) {
console.log("同步任务2");
if (mac) {
console.log(mac);
}
return new Promise((resolve, reject) => {
console.log("Promise中的同步任务");
resolve("Promise中回调的异步微任务")
})
}
setTimeout(() => {
console.log("异步任务中的宏任务");
setTimeout(() => {
console.log("定时器中的定时器(宏任务)");
}, 0)
asyn("定时器传递任务").then(res => {
console.log('定时器中的:', res);
})
}, 0)
asyn().then(res => {
console.log(res);
})
console.log("同步任务3")
将代码从下往下进行执行,先进行运行同步代码在运行异步代码,先运行宏任务在运行微任务,如下图
执行顺序如下,上图在执行同步的时候,少写了一个Promise的同步任务,但是分析过程是一样的
案例2:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function () {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function () {
console.log('promise1')
})
.then(function () {
console.log('promise2')
})
console.log('script end')
案例二就自己去做了,案例2没有前面的绕,但是记住先执行同步在执行异步,先执行宏任务,在执行微任务
浏览器进程之间的通信
浏览器有这么多的进程,但是进程之间是相互关联的,当打开一个浏览器,就可以看到任务管理器中出现了两个进程,一个是主进程,一个就是打开的tab页的渲染进程
对于主进程(Brower主进程)它做了什么,收到用户的请求,获取到页面内容,随后将该任务通过RenderHost的接口传递给Render进程
Render进程通过接口收到信息,简单解析后交给渲染线程GUI开始渲染(js线程操作DOM可能会造成回流重绘)
Render进程将结果传递回主进程,主进程收到结果并进行绘制
码字不易,友情赞助一个赞吧!欢迎一起讨论