前端发展历史|从输入url到看到页面经历了些什么(二)——浏览器渲染

浏览器架构
浏览器一般由以下几部分构成:

  • 用户界面:除了主窗口渲染界面之外的其他显示部分
  • 浏览器引擎:主控用户界面和渲染引擎,为它们之间传送指令
  • 渲染引擎:负责显示请求的内容(解析html和css等)
  • 网络模块:处理网络请求,同时还提供获得的文档的缓存,为所有的平台提供底层实现
  • js解释器:解释和执行js代码(著名的v8引擎)
  • 用户界面后端:绘制基本的窗口小部件,比如组合框和窗口,在底层使用的是操作系统的用户界面方法,并且公开通用的接口
  • 数据存储:管理用户数据,比如书签,偏好设置等
上面的组成部分是之前看博客的时候看到的,最近看了一下浏览器原理方面的文章,接下来从进程与线程的角度对浏览器架构进行进一步分析。参考文章如下:
浏览器内部揭秘(1-4)
有大大们翻译了中文版本:
现代浏览器内部揭秘(一)
现代浏览器内部揭秘(二)
现代浏览器内部揭秘(三)
现代浏览器内部揭秘(四)
浏览器一直都在致力于如何提供更好的用户体验,明白浏览器如何处理开发者写的代码,能够反过来为用户提供更多友好体验。
在此简单介绍一下,首先是进程和线程的概念:进程可以被描述为是一个应用的执行程序,线程存在于进程并执行程序任意部分。如果两个进程需要进行对话,可以通过进程间通信(IPC)来进行。
通常会有疑问,浏览器是单进程还是多进程?
A:浏览器可能是一个拥有很多线程的进程,也可能是一些通过IPC通信的不同线程的进程,Chrome浏览器属于多渲染进程架构。Chrome进程主要包括浏览器进程,渲染进程,插件进程和GPU进程四个部分:
  1. 浏览器进程:控制应用中的“Chrome”部分,包括地址栏,书签,回退和前进按钮等,还处理一些不可见的操作例如网络请求与文件访问等。
  2. 渲染进程:控制标签页内的网站展示,简单来说就是每个标签都有自己的渲染进程,这样就算其中一个标签失去响应,也不会影响其他标签的正常使用
  3. 插件进程:控制站点使用的任意插件
  4. GPU进程:处理独立于其他进程的GPU任务。因为GPU会被分成不同进程,处理来自不同应用的请求并绘制在相同表面
  5. 网络进程:主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里,最近独立出来成为一个单独的进程
多进程的好处:
  • 安全性和沙箱化:由于操作系统提供了限制进程权限的方法,浏览器就可以用沙箱保护某些特定功能的进程
  • 沙箱看成是操作系统给进程上了一把锁,沙箱里面的程序可以运行,但是不能在你的硬盘上写入任何数据,也不能在敏感位置读取任何数据
多进程的缺点:
  • 进程有自己的私有内存空间,所以一些公共部分只能被拷贝不能像线程之间一样共享,所以就会消耗更多内存
从浏览器内部处理逻辑的角度再来看看从输入url到看到页面浏览器是怎么工作的;
  1. 处理输入:UI线程会询问输入的是url地址还是一个查询条件(chrome地址栏也可以作为搜索栏操作),此时UI线程需要解析和决定把请求发送到搜索引擎或者要请求的网站中去。
  2. 开始导航:UI线程通知网络进程调取去获取站点内容,网络进程开始工作,比如建立连接,下载资源等
  3. 读取响应:数据返回时网络进程需要判断响应报文,如果响应是个HTML文件,下一步就会将数据传给渲染进程,如果是一个压缩文件或者其他文件,就需要将数据传递给下载管理器,这个时候也会进行安全检查和CORB(cross origin read blocking)检查,以保证敏感的跨域数据不被传给渲染进程
  4. 查找渲染进程:一旦检查执行完毕而且网络线程确信浏览器能够导航到请求的站点,网络线程会告诉UI线程所有的数据准备完成,UI线程会寻找渲染进程,开始渲染web页面,每个标签对应一个渲染进程。但如果从一个页面打开了另一个新页面,而新页面和当前页面属于同一站点的话,那么新页面会复用父页面的渲染进程
  5. 提交导航:数据和渲染进程都准备就绪时,浏览器进程会发送一个IPC到渲染进程去提交导航。它也会传递数据流,所以渲染进程可以保持接收HTML数据,一旦浏览器进程收到渲染进程已经提交的确认信息,导航完毕并且文档加载解析开始
  6. 额外的步骤:初始加载完毕:一旦导航被提交,渲染进程开始加载资源和渲染页面,一旦渲染进程渲染完毕,会发送一个IPC返回给浏览器进程,此时UI线程停止标签页上的加载动画,但是客户端js可以在此时仍然加载额外资源并重新渲染视图
了解浏览器组成之后,接下来就到了浏览器渲染过程了。
1.概述
前端发展历史|从输入url到看到页面经历了些什么(二)——浏览器渲染
文章图片

如图所示是webkit的渲染过程,
  1. 渲染引擎解析HTML文档以及CSS文档,生成对应的DOM和CSSOM
  2. 两棵树将合成为一棵渲染树,树中有很多的组成部分,这些组成部分被称为节点
  3. 渲染树构建完毕后,为每个节点分配一个应该出现在屏幕上的确切坐标,这个阶段称为布局
  4. 找到每个节点的位置后,渲染引擎会遍历渲染树,将每个节点的细节绘制出来
  5. 最后将画面显示出来
1.1 解析阶段 【前端发展历史|从输入url到看到页面经历了些什么(二)——浏览器渲染】渲染进程的核心是将HTML,CSS和Javascript转换为用户可以与之交互的网页,渲染进程内部包括主线程,工作线程,合成线程和光栅线程。
在实际应用中,解析阶段主要是主线程在工作。渲染引擎会先解析HTML头部代码,下载样式表,下载的同时HTML会继续解析,等到解析完成之后,构造生成DOM树,开始解析下载好的CSS,构造CSSOM。
这个时候主线程解析CSS并确定每个DOM节点计算后的样式(没下载完不能开始构建)。样式计算的目的是为了计算DOM节点中的每个元素的具体样式,大体分为三个步骤:
  1. 把CSS转换为浏览器能够理解的结构(渲染引擎接收到CSS文本时,会执行转换操作,将CSS文本转换为styleSheets)
  2. 转换样式表中的属性值,使其标准化(将不容易被渲染引擎理解的属性标准化,2em->32px这种)
  3. 计算出DOM树中每个节点的具体样式
    等两棵树都解析完了(两棵树是可以并行解析的),就开始进行渲染树的构造。为了加快速度,预加载扫描器(preload scanner)会在主线程处理外部资源请求时同时运行,当HTML文档中有前端发展历史|从输入url到看到页面经历了些什么(二)——浏览器渲染
    文章图片

    图片地图的缺点:
    1.定位图片地图上的区域坐标时,如果采取手工方式则很难完成且容易出错,而且除了矩形外几乎无法定义其他形状
    2.通过DHTML创建的图片地图则在IE中无法工作
    CSS Sprites
    将多幅图片合并为一幅单独的图片,使用css的background-position属性,可以将HTML元素放置到背景图片中期望的位置上
    CSS Sprites的优点:
    1.通过合并图片减少HTTP请求,并且比图片地图更灵活
    2.降低了下载量,合并后的图片会比分离的图片的总和要小,因为降低了图片自身的开销(颜色表,格式信息等)
    合并脚本和样式表
    内联图片
    使用data:URL模式可以在Web页面中包含图片但无需任何额外的HTTP请求
    缺陷:1.ie7之前不受ie支持,2.可能会存在数据大小上的限制
    ** iconfont**
    采用字体的方式来做图标,将图标作为一个透明的矢量图,这样可以通过font-size和color进行图标的改变,文件的体积也会变小
    规则2:使用内容发布网络(content delivery network) 内容发布网络(CDN):是一组分布在多个不同地理位置的web服务器,用于更加有效地向用户发布内容,在优化性能时,向特定用户发布内容的服务器的选择基于对网络可用度的测量
    优点:缩短响应时间,还可备份,扩展存储能力,进行缓存
    缺点:响应时间可能会受到其他网站的影响
    CDN用于发布静态内容
    规则3:添加Expires头 浏览器(和代理)使用缓存来减少HTTP请求的数量,并减少HTTP响应的大小,使web页面加载得更快
    web服务器使用Expires头来告诉web客户端它可以使用一个组件的当前副本,直到指定的时间为止
    另一种缓存的选择,HTTP1.1的Cache-Control头可以克服Expires头的限制,因为Expires头使用一个特定的时间,要求服务器和客户端的时钟严格同步,而且过期日期需要经常检查,Cache-Control头可以使用max-age指令指定组件被缓存多久,以秒为单位定义了一个更新窗,两者同时出现的话Expires会被覆盖
    Cache-Control: max-age=31536000

    不支持Cache-Control的浏览器还是要用Expires头
    跨浏览器改善缓存的最佳解决方案就是使用由ExpiresDefault设置的Expries头,
    用户为了获取最新版本的组件,最有效的解决方案就是修改其所有链接
    规则4:压缩组件 通过减少HTTP响应的大小来减少响应时间,最简单的方式是利用gzip编码来压缩HTTP响应包,gzip是GNU项目开发的一种免费的格式,并被标准化为RFC1952
    方式:
    Accept-Encoding: gzip, default

    压缩的内容:HTML文档,脚本以及样式表,图片和PDF不应该被压缩,因为本身就被压缩过,再压缩反而耗费CPU,还可能增加文件大小
    压缩的成本:1.服务器端会花费额外的CPU周期来完成压缩,2.客户端要对压缩文件进行解压缩
    当浏览器通过代理发送请求时,当有来源于既有支持gzip又有不支持gzip的同个请求时,代理缓存就可能会混乱,解决方法:在服务器的Vary响应头中包含Accept-Encoding,即:
    Vary:Accept-Encoding

    当并不清楚浏览器是否支持缓存时,使用浏览器白名单:只为已经证实过支持压缩的浏览器提供压缩内容,在有代理的前提下,可以将User-Agent加到Vary头中,即:
    Vary:Accept-Encoding,User-Agent

    根据使用场景选择适合的方式
    1. 网站用户少,且浏览器环境差别不大,这时边缘情况可以不考虑,可以通过压缩组件改善用户体验
    2. 如果更加注重的是带宽开销,也可以通过压缩组件的方式降低开销,提高代理处理的请求数量
    3. 如果有大量用户,且本身能应付大的带宽开销,压缩内容时使用Cache-Control:Private,这禁用代理缓存但避免了边缘情形缺陷
    规则5:将样式表放在顶部 将样式放在文档底部会导致浏览器中阻止用户内容逐步呈现,为避免样式变化时重绘页面,浏览器会阻止内容的逐步呈现,这样在等待位于底部的样式表时,内容不能被呈现,就会造成白屏
    使用@import进行样式加载会导致组件下载时的无序性,所以尽可能使用
    如果样式表不是呈现页面必须的,可以想办法在文档加载完毕之后动态加载进去
    规则6:将脚本放在底部 对响应时间影响最大的事页面中组件的数量,当缓存为空时,每个组件都会产生一个HTTP请求,HTTP1.1规范建议浏览器从每个主机名并行地下载两个组件
    脚本是会阻塞下载的,具体的分析可以看看上文的浏览器渲染原理中的解析阶段
    规则7:避免CSS表达式 当页面发生改变时,CSS表达式会重新求值,对CSS表达式的频繁求值也会导致CSS表达式的低下性能
    尽可能使用一次性表达式或者利用事件处理器来操作CSS属性
    规则8:使用外部的js和CSS 规则9:减少DNS查找 规则10:精简javascript 可以通过混淆的方式进行代码的精简(比如Uglify)
    规则11:避免重定向 重定向在实际应用中都不会有缓存,除非有附加的Expires或者Cache-control等头
    规则12:删除重复脚本 规则13:配置ETag ETag(实体标签):Web服务器和浏览器用于确认缓存组件有效性的一种机制,用于检测浏览器缓存中的组件与原始服务器上的组件是否匹配
    规则14:使用Ajax可缓存 Ajax的目的是为了突破web本质的开始-停止交互方式,它在UI和服务器之间插入了一层,Ajax层位于客户端,与服务器进行交互以获取请求的信息,并与表现层交互,仅更新必要的组件。它将Web体验从“浏览页面”转化成了“与应用程序进行交互”

      推荐阅读