正如另一篇文章所言,ecma262只定义了语言层面的一些规范,js实际运行过程中还需要特定的宿主环境(host environment)提供输入输出等功能。
本系列介绍三种常见的js宿主环境
- web browser(本文)
- node.js
- electron
本文以chrome为例,会对浏览器的架构以及相关运行过程做简要介绍,更多细节参考文中出现的链接和这篇web性能相关的文章。
全文参考inside look at modern web browser。
chrome浏览器架构
相关概念
在认识chrome具体架构之前我们先回顾几个概念
首先是两个比较重要的硬件
- cpu 中央处理单元,是计算机的运算核控制中心,参考这里
- gpu 即Graphics Processing Unit,可以理解为有很多核用于处理简单计算的cpu,一开始涉及为了处理图像,后来可以用来独立处理计算任务。
程序实际运行还涉及另外两个重要概念
多进程架构
一个浏览器包括很多种任务,可以只使用一个进程包含多个线程,也可以多个进程,不同的浏览器会有不同的实现(chrome浏览器为例,每个tab会分配一个渲染进程,其中的跨域iframe也会另外分配一个渲染进程,即Site Isolation),总体来说还是多进程会更有优势一些,包括
- 每个tab分别处于不同进程,避免了一个tab无响应等对其他tab的影响
- 增强安全性
本文介绍的架构(是参考文章发布时,即2018年9月,的设计)包括
- browser进程,浏览器的主进程,负责导航栏、书签、前进后退按钮,并且控制可见性以及特权功能,比如网络请求和文件访问。
- ui线程 页面的展示和交互
- network线程 网络请求
- storage线程 存储
- renderer进程 控制一个tab内展示的部分,js engine和browser engine工作在这个进程。其中js engine,是es262的实现,用来运行js;browser engine,又叫做 layout engine or rendering engine,负责js引擎外其他工作,比如解析html和css,以及布局
- worker线程
- main线程 处理worker以外的代码
- compositor线程
- raster线程 后面两个线程参与渲染
- plugin进程
- gpu进程 负责其他进程中gpu相关工作
导航过程
这部分主要发生在browser进程,并涉及部分和renderer进程的交互。
处理输入
当在输入框输入内容时首先判断是查询还是访问url。
开始导航
当点击回车,ui线程启动一个network线程发起请求,loading spinner开始,如果返回的是重定向,会重新发起另一个请求
处理响应
当收到响应后会根据content-type等判断类型,如果是html就会发给renderer process,否则发给下载器。 这个阶段也会进行安全检查,包括cors
这里使用的renderer process会在前面发起请求时同时查找或者启动,如果重定向到一个跨域站点会重新重复这个动作
提交导航
当数据和renderer process都准备好后,browser进程会发送一个ipc renderer process来提交这个导航。一旦renderer 进程确认,导航完成,开始文档加载阶段。
此时地址栏和会话历史更新,
渲染结束
当当前界面的所有frames执行完onload事件,renderer进程就会发送给browser进程,loading spinner停止
6.导航到另一个站点 大部分和前面一样,开始之前需要触发beforeunload事件
3 文档加载和渲染过程
这部分任务是将html页面中的各种资源转化为可交互的页面。
Parsing
renderer进程收到导航提交信息后开始接收html数据,解析dom。
Style calculation
解析css,为每个元素属性添加一个computed style。
Layout
计算每个元素的坐标,生成layout tree,其中包含伪元素等。
Paint
遍历layout tree创建paint records,记录绘制顺序,比如z-index的影响。
Compositing
main线程遍历layout tree生成layer tree,确定每个元素在哪一层,然后将layer tree和paint records提交到compositor线程,后者将每一层分成小块发给raster线程分别栅格化然后保存在gpu内存中。 当所需的小块栅格化完成,compositor线程就用来生成compositor frame,然后通过ipc提交给browser 进程,然后发送到gpu显示在屏幕上。 如果滚动事件发生,如果当前页面没有绑定事件监听器,compositor线程就会创建其他compositor frame发送给gpu渲染新的页面,如果绑定事件监听器,看下一节。
用户事件输入
接收事件输入
当用户交互事件发生时,browser进程将事件的坐标和事件类型发送给renderer process
处理事件
compositor线程根据信息判断event target,如果对应区域有事件绑定(这样的区域被称为Non-Fast Scrollable Region),就要将事件发送到main线程执行,否则只需要进行必要的合成。
平滑滚动
当滚动事件触发时,conpositor线程每次composite new frame之前需要先和main 线程通信(因为可能会取消默认事件),进而造成卡顿,在监听器上采用{passive: true}
可以直接合成新的帧,也不影响事件监听(这时候就不能取消默认行为)。
也可以直接取消事件绑定
#area {
touch-action: pan-x;
}