这是我参与「第四届青训营」笔记创作活动的的第4天,今天我想分享的是上了青训营老师讲的课后,根据老师发的资料自己总结归纳了浏览器的主要知识点。
架构
以下均以chrome为例子:
- 浏览器进程:控制浏览器这个应用的chrome(主框架)部分,包括地址栏、书签、前进/后退按钮等,同时也会处理浏览器不可见的高权限任务,如发送网络请求、访问文件。
- 渲染器进程:负责在标签页中显示网站及处理事件。
- 插件进程:控制网站用到的所有插件。
- GPU进程:在独立的进程中处理GPU任务。之所以放到独立的进程,是因为GPU要处理来自多个应用的请求,但要在同一个界面上绘制图形。
Chrome架构进化的目标是将整个浏览器程序的不同部分服务化,便于分割或合并。基本思路是在高配设备中,每个服务独立开进程,保证稳定;在低配设备中,多个服务合并为一个进程,节约资源。
进程都有自己私有的内存空间,因此每个进程可能都会保存某个公共基础设施(比如Chrome的JavaScript引擎V8)的多个副本。这会导致内存占用增多。为节省内存,Chrome会限制自己可以打开的进程数量。限制的条件取决于设备内存和CPU配置。达到限制条件后,Chrome会用一个进程处理同一个站点的多个标签页。
导航
导航涉及浏览器进程与线程间为显示网页而通信。一切从用户在浏览器中输入一个URL开始。输入URL之后,浏览器会通过互联网获取数据并显示网页。从请求网页到浏览器准备渲染网页的过程,叫做导航
标签页外面的一切都由浏览器进程处理。浏览器进程中有线程(UI线程)负责绘制浏览器的按钮和地址栏,有线程(网络线程)负责处理网络请求并从互联网接收数据,有线程(存储线程)负责访问文件和存储数据
渲染
渲染的步骤:解析HTML->计算样式->布局->绘制->合成
渲染是渲染器进程内部的工作,涉及Web性能的诸多方面(详细内容可以参考这里t.cn/Ai9c4nUu) 。标签页中的一切都由渲染器进程负责处理,其中主线程负责运行大多数客户端JavaScript代码,少量代码可能会由工作线程处理(如果用到了WebWorker或Service Worker)。合成器(compositor)线程和栅格化(raster)线程负责高效、平滑地渲染页面。
交互
用户的任何输入,如鼠标滚轮转动、触摸屏幕等等这些都是交互
一个事件处理程序就可以面向多个元素,这种高效的写法因此很流行。然而,从浏览器的角度来看,这样会导致整个页面被标记为“非快速滚动区”。这也就意味着,即便事件发生在那些不需要处理的元素上,合成器线程也要每次都跟主线程沟通,并等待它的回应。于是,合成器线程平滑滚动的优点就被抵销了。
为缓冲使用事件委托带来的副作用,可以在注册事件时传入passive: true。这个选项会提醒浏览器,你仍然希望主线程处理事件,但与此同时合成器线程也可以继续合成新的帧。
document.body.addEventListener('touchstart', evt => {
...
}, { passive: true })
为把对主线程过多的调用降至最少,Chrome会合并(coalesce)连续触发的事件(如wheel、mousewheel、mousemove、pointermove、touchmove),并将它们延迟到恰好在下一次requestAnimationFrame之前派发;对于其他离散触发的事件,像keydown、keyup、mouseup、mousedown、touchstart和touchend会立即派发。
总结
看完文章后,不仅对浏览器的结构有了更深的认识,而且还学会了几个代码知识:
1、给<script>标签添加defer、async属性(这样浏览器就会异步运行JavaScript代码,不会阻塞解析)的作用。
2、注册事件监听器时最好传入passive: true选项(提醒浏览器,你仍然希望主线程处理事件,但与此同时合成器线程也可以继续合成新的帧);
3、CSS的will-change属性(如果页面某些部分应该独立一层(如滑入的菜单)但却没有,那你可以在CSS中给它加上will-change属性来提醒浏览器)会让浏览器做出不同的决策。