进程和线程
进程
cpu 分配资源的最小单位
线程
cpu 调度的最小单位
浏览器是多进程的
- 每打开一个tab 页,就相当于创建了一个独立的浏览器进程,根据浏览器的优化机制不通,多个tab 页可以合并为一个进程
浏览器包含哪些进程
浏览器打开一个网页相当于新开了一个进程,进程有自己的多线程
- Browser 进程(浏览器的主进程,只有一个)
- 页面显示,与用户交互,比如前进、后退
- 管理各个页面,创建和销毁其他进程
- 网络资源管理,比如下载
- 第三方插件进程
- 每个类型的插件对应一个进程,进党使用插件时才创建
- GPU 进程
- 最多一个,用于3D 绘制
- 浏览器渲染进程(浏览器内核)
- 默认每个tab 页一个进程,互不影响
- 页面渲染,JS脚本之行,事件处理等
浏览器多进程的优势
如果浏览器是但进程的,那某个tab 页崩溃了,会影响整个浏览器
浏览器内核(Render 进程)
浏览器的渲染进程是多线程的
- GUI 渲染线程
- 负责渲染页面,解析HTML,CSS,构建DOM 树,和RenderObject 树,布局和绘制等
- 当页面需要重绘(Repaint)或者回流(Reflow)时,该线程就会执行
- GUI 线程与JS 线程互斥,当JS 引擎执行时,GUI 线程会被挂起,GUI 更新会被保存在一个队列中,等JS 线程空闲时立即执行
- JS 线程
- 也称JS 内核,负责解析JS 脚本,运行代码
- 一直等待任务队列中的任务,一个tab 页永远只有一个js 线程
- 当JS 线程执行代码时,会将对应任务添加到事件线程中
- 事件触发线程
- 归属浏览器,而不是JS 线程,用来控制事件循环
- 当事件被出发时,该线程会把事件添加到待处理队列的队尾,等待JS 引擎处理
- 由于JS 是单线程,所以队列中事件都要排队等待JS 线程处理,当JS 引擎空闲时才会执行。
- 定时器触发线程
- 比如setInterval 和 setTimeout
- 浏览器的计时器并不是由JS 线程提供的,在计时完毕后,添加到事件队列中,待JS 引擎空闲后执行,所以JS 计时器不准
- setTimeout 中低于4ms 的时间间隔算为4ms
- 异步http 请求线程
- 在XMLHttpRequest 在连接后是通过浏览器新开的一个线程请求
- 检测到状态变更时,如果有回调函数,异步线程就产生状态变更事件,将这个回调放到事件队列中,再由JS 线程执行
Browser 进程和浏览器内核(render进程)的通信过程
打开任务管理器,再打开浏览器,可以在任务管理器中看到出现了两个进程,一个是主控进程,另一个时打开tab 页的渲染进程,过程如下
- Browser 进程收到用户请求,首先后去到页面内容(譬如通过网络下载资源),随后将该任务通过RendererHost 接口传递给Render 进程
- Render 进程收到消息后,交给渲染进程,开始渲染
- 渲染进程接收请求,加载并渲染网页
- JS 线程操作DOM
- 最后Render 进程将结果传递给Browser 进程
- Browser 进程接收到结果并将页面绘制出来
浏览器内核中线程之间的关系
GUI 渲染线程与JS 线程互斥
- 由于JS 线程可以操作DOM,如果在修改这些元素属性同时渲染页面,那么渲染线程前后获得的元素数据就可能不一致了
- 为了防止渲染出现不可预期的结果,浏览器设置GUI 渲染线程与JS 线程为互斥关系,当JS 线程执行时,GUI 线程会被挂起
- GUI 更新会被保存在一个队列中,等到JS 引擎线程空闲时立即执行
JS 阻塞页面加载
原因同上
Web Worker
由于JS 是单线程的,如果前一个任务没有结束,后面的任务只能排队,Web Worker 就是为了解决这个问题。
Web Worker的作用就是为JS 创造多线程环境,允许主线程创建Worker 线程,然后分配一些任务运行,在主线程运行的同时,Worker 线程在后台运行,互不干扰。等Worker 线程完成任务后,再把结果返回给主线程。这样的好处是一些很费时间的任务被Worker 线程承担,主线程就不会被阻塞,减少页面卡顿
Worker 线程一旦新建成功,就会始终运行,不会被主线程的活动打断,这样有利于随时响应主线程通信,但是Woker 比较耗费字眼,使用过后,应该立刻关闭
有以下几个使用注意点
- 同源限制
- 分配给Worker 线程运行的脚本文件,必须与主线程的脚本文件同源
- DOM 限制
- Worker 线程所在的全局对象,与主线程不一样,无法读取主线程所在网页的DOM 对象,也无法使用document、window、parent 这些对象,但是Worker 线程可以使用navigator 对象和loaction 对象
- 通信联系
- Worker 线程和主线程不在一个上下文环境,他们不能直接通信,必须通过消息完成
- 脚本限制
- Worker 线程不能执行alert 方法和confirm 方法,但是可以使用XMLHttpRequest 对象发出的Ajax 请求
- 文件限制
- Worker 线程无法读取本地文件,它所加载的脚本,必须来自网络
基本用法
直接点我去阮老师的博客,这里不赘述
Share Worker 与 Web Worker
- Web Worker 只属于某个页面,不会和其他页面的Render 进程共享
- 只属于render 进程下的一个线程
- 所以在Chrome 在Render 进程中创建一个新的线程来运行Worker 中的JS 程序
- Share Worker 是浏览器所有页面共享的,不能采用Web Worker 同样的方式实现
- 一个单独的进程
浏览器渲染流程
- 解析html 树,建立dom 树
- 解析css 构建css 树,与dom 树合并成render 树
- 布局render 树,负责各元素尺寸、位置的计算
- 绘制render 树,绘制页面像素信息
- 浏览器将各层的信息发送给GPU,GPU 会讲各层合成,显示
load 事件与 DOMContentLoaded 事件的先后
- 当load 事件触发时,页面所有的DOM,样式表,js 脚本,图片都已经加载完成了
- 当DOMContentLoaded 事件出发时,仅仅加载完了DOM
css 加载是否会阻塞dom 树渲染
css 是由单独的下载线程异步下载,所以不会阻塞dom 树解析,但是会阻塞render 树渲染(渲染时需要等css 加载完毕。
因为加载css 时,可能会修改dom 节点的样式,如果css 加载不阻塞render 渲染的话,当css 加载完之后render 树可能又需要重绘、回流,会造成额外性能损耗
Event Loop 和 JS 运行机制
- JS 氛围同步任务和异步任务
- 同步任务都在主线程上执行,形成一个==执行栈==
- 主线程之外,事件触发线程管理者一个==任务队列==,只要异步任务运行有了结果,就在==任务队列==中推入一个事件
- 当==执行栈==中所有同步任务执行完毕(此时JS 线程空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行
定时器
- 由定时器线程单独控制,避免JS 线程阻塞,影响计时准确性。
- 当使用setTimeout 或setInterval 时,就会调用定时器线程,计时完成后就会将需要执行的事件推入到事件队列中,需要等先进入队列中的任务执行完之后才会执行,所以定时器不准确
为什么setTimeout 而不是setInterval
- setTimeOut 时间到了就会执行定时器内的任务。
- setInterval 则是每次都精确的隔一段事件向任务队列中推入一个事件,但是事件执行时间不一定准确,可能这个事件还没执行完毕,下一个事件就来了,会导致定时器代码连续运行多次,而之间没有间隔。
- 浏览器最小化时,setInterval 回调函数放在队列中,等窗口再打开时,一次性全部执行