5.浏览器中的页面

本篇是这个专栏的第五章:《No5.浏览器中的页面》。本章分为八节。

21 | Chrome开发者工具:利用网络面板做性能分析

本节首先对开发者工具的各个模块进行了一个简单介绍,然后重点讲解的是NetWork面板。 网络面板包括:控制器、过滤器、抓图信息、时间线、详细列表和下载信息概要六个区域构成。

1.控制器

  • “开始/暂停”抓包。

  • 全局搜索。

  • Disable cache:禁止从Cache中加载资源。

  • Online按钮:模拟2G/3G 网络,模拟弱网环境。

    2.过滤器

    过滤功能。

3.抓图信息

抓图信息区域,可以用来分析用户等待页面加载时间内所看到的内容,分析用户实际的体验情况。

4.时间线

时间线,主要用来展示 HTTP、HTTPS、WebSocket 加载的状态和时间的一个关系,用于直观感受页面的加载过程。

5.详细列表

详细记录了每个资源从发起请求到完成请求这中间所有过程的状态,以及最终请求完成的数据信息. Queuing:当浏览器发起一个请求的时候,会有很多原因导致该请求不能被立即执行,而是需要排队等待。 Stalled: 在发起连接之前,还有一些原因可能导致连接过程被推迟,这个推迟就表现在面板中的 Stalled 上。 Proxy Negotiation:若使用代理服务器,会增加一个此阶段。 Waiting (TTFB):通常也称为“第一字节时间”。 TTFB 是反映服务端响应速度的重要指标,对服务器来说,TTFB 时间越短,就说明服务器响应越快。 Content Download :这意味着从第一字节时间到接收到全部响应数据所用的时间。

6.下载信息概要

重点关注 DOMContentLoaded和Load两个事件。

  • DOMContentLoaded:这个事件发生后,说明页面已经构建好DOM了,即DOM需要的HTML、JavaScript、CSS等文件已下载完成了。

  • Load:说明浏览器已经加载了所有的资源(图像、样式等)。

22 | DOM树:JavaScript是如何影响DOM树构建的?

什么是DOM

DOM 是表述 HTML 的内部数据结构,它会将 Web 页面和 JavaScript 脚本连接起来,并过滤一些不安全的内容。

DOM树如何生成

在渲染引擎内部,有一个叫 HTML 解析器(HTMLParser)的模块,它的职责就是负责将 HTML 字节流转换为 DOM 结构。 HTML解析器过程是:网络进程加载了多少数据,HTML解析器便解析多少数据。

JavaScript是如何影响DOM生成的

  • 在两段 div 中间插入了一段 JavaScript 脚本:当HTML解析器解析到script标签的时候会暂停DOM解析,去执行这段JS脚本。

  • 在页面中引入 JavaScript文件:整个执行流程还是一样的,执行到 JavaScript 标签时,暂停整个 DOM 的解析,执行 JavaScript 代码,不过这里执行 JavaScript 时,需要先下载这段 JavaScript 代码。这里需要重点关注下载环境,因为 JavaScript 文件的下载过程会阻塞 DOM 解析(Chrome浏览器做的一个主要优化是预解析操作)。

    另外也有一些相关的策略:比如使用 CDN 来加速 JavaScript 文件的加载,压缩 JavaScript 文件的体积。如果 JavaScript 文件中没有操作 DOM 相关代码,就可以将该 JavaScript 脚本设置为异步加载,通过 asyncdefer 来标记代码.

  • async:使用 async 标志的脚本文件一旦加载完成,会立即执行.

  • defer:使用了 defer 标记的脚本文件,需要在 DOMContentLoaded 事件之前执行.

通过上面的分析,我们知道了 JavaScript 会阻塞 DOM 生成,而样式文件又会阻塞 JavaScript 的执行,所以在实际的工程中需要重点关注 JavaScript 文件和样式表文件,使用不当会影响到页面性能的。

23 | 渲染流水线:CSS如何影响首次加载时的白屏问题

CSS资源是页面中非常重要的一环,本节首先站在渲染流水线的视角来介绍CSS是如何工作的、然后通过CSS工作流程来分分析性能瓶颈、最后讨论如何减少首次加载时的白屏问题。

渲染流水线视角下的CSS

首先是发起页面请求,网络进程接收到返回的HTML数据,将其发送给渲染进程,渲染进程解析HTML数据并构建DOM。 需要特别注意下,请求 HTML 数据和构建 DOM 中间有一段空闲时间,这个空闲时间有可能成为页面渲染的瓶颈。

前面提到一嘴:Chrome浏览器做的一个主要优化是预解析操作。 因此,Chrome开启这个预解析进程后,在遇到JavaScript或CSS文件后,会提前下载这些文件。 这里也有一个空闲时间需要注意一下,就是在 DOM 构建结束之后、css 文件还未下载完成的这段时间内,渲染流水线无事可做,因为下一步是合成布局树,而合成布局树需要 CSSOM 和 DOM,所以这里需要等待 CSS 加载结束并解析成 CSSOM。

CSSOM的两个作用:

  • 提供给 JavaScript 操作样式表的能力.

  • 为布局树的合成提供基础的样式信息。

影响页面展示的因素以及优化策略

从发起 URL 请求开始,到首次显示页面的内容,在视觉上经历的三个阶段: 1. 等请求发出去之后,到提交数据阶段,这时页面展示出来的还是之前页面的内容。 2. 提交数据之后渲染进程会创建一个空白页面,我们通常把这段时间称为解析白屏,并等待 CSS 文件和 JavaScript 文件的加载完成,生成 CSSOM 和 DOM,然后合成布局树,最后还要经过一系列的步骤准备首次渲染. 3. 等首次渲染完成之后,就开始进入完整页面的生成阶段了,然后页面会一点点被绘制出来。

这里重点关注第二个阶段: 该阶段的主要任务包括了:解析 HTML、下载 CSS、下载 JavaScript、生成 CSSOM、执行 JavaScript、生成布局树、绘制页面一系列操作。 对应策略:

  • 通过内联 JavaScript、内联 CSS 来移除这两种类型的文件下载,这样获取到 HTML 文件之后就可以直接开始渲染流程了。

  • 但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过 webpack 等工具移除一些不必要的注释,并压缩 JavaScript 文件。

  • 还可以将一些不需要在解析 HTML 阶段使用的 JavaScript 标记上 sync 或者 defer。

  • 对于大的 CSS 文件,可以通过媒体查询属性,将其拆分为多个不同用途的 CSS 文件,这样只有在特定的场景下才会加载特定的 CSS 文件。

24 | 分层和合成机制:为什么CSS动画比JavaScript高效

在第五节的时候,我们知道DOM构建成功后还要经历布局、分层、绘制、合成、显示等阶段后才能显示出漂亮的页面。 这一节主要讲解的是渲染引擎的分层和合成机制,作者说分层和合成机制代表了浏览器最为先进的合成技术,请注意是最为先进的.

显示器是怎么显示图像的

每个显示器的固定刷新频率通常是60HZ,即每秒更新60张图片,更新的图片都来自显卡中一个叫前缓冲区的地方,,显示器所做的任务很简单,就是每秒固定读取 60 次前缓冲区中的图像,并将读取的图像显示到显示器上。 显卡的作用:显卡的职责就是合成新的图像,并将图像保存到后缓冲区中,一旦显卡把合成的图像写到后缓冲区,系统就会让后缓冲区和前缓冲区互换,这样就能保证显示器能读取到最新显卡合成的图像。

帧 VS 帧率

渲染流水线生成的每一张图片称为一帧,渲染流水线每秒更新了多少帧称为帧率。

如何生成一帧图像

生成一帧图像有三种方式:重排、重绘、合成。 这三种方式的渲染路径不同,通常渲染路径越长,生成图像花费的时间越久。 这里聚焦点在合成上 ,为了提升每帧的渲染效率,Chrome 引入了分层和合成的机制,Chrome的合成技术用三个词来概括:分层、分块、合成。

分层和合成

你可以把一张网页想象成是由很多个图片叠加在一起的,每个图片就对应一个图层,将素材分解为多个图层的操作就称为分层。最后将这些图层合并到一起的操作就称为合成。 在Chrome渲染流水线中,分层体现在生成布局树之后,渲染引擎根据布局树的特点将其转化为层树,层树是渲染流水线后续流程的基础结构。 需要重点关注的是,合成操作是在合成线程上完成的,这也就意味着在执行合成操作时,是不会影响到主线程执行的。这就是为什么经常主线程卡住了,但是 CSS 动画依然能执行的原因。

分块

如果说分层是从宏观上提升了渲染效率,那么分块则是从微观层面提升了渲染效率。 在首次合成图块的时候使用一个低分辨率的图片。

如何利用分层技术优化代码

在写 Web 应用的时候,你可能经常需要对某个元素做几何形状变换、透明度变换或者一些缩放操作,如果使用 JavaScript 来写这些效果,会牵涉到整个渲染流水线,所以 JavaScript 的绘制效率会非常低下. 这时你可以使用 will-change 来告诉渲染引擎你会对该元素做一些特效变换,CSS 代码如下:

.box {
will-change: transform, opacity;
}

这段代码就是提前告诉渲染引擎 box 元素将要做几何变换和透明度变换操作,这时候渲染引擎会将该元素单独实现一帧,等这些变换发生时,渲染引擎会通过合成线程直接去处理变换,这些变换并没有涉及到主线程,这样就大大提升了渲染的效率。这也是 CSS 动画比 JavaScript 动画高效的原因.

25 | 页面性能:如何系统地优化页面?

本节所谈论的页面优化,其实就是让页面更快的显示和响应。 通常一个页面有三个阶段:加载阶段、交互阶段和关闭阶段。

  • 加载阶段,是指从发出请求到渲染出完整页面的过程,影响到这个阶段的主要因素有网络和 JavaScript 脚本。

  • 交互阶段,主要是从页面加载完成到用户交互的整合过程,影响到这个阶段的主要因素是 JavaScript 脚本。

  • 关闭阶段,主要是用户发出关闭指令后页面所做的一些清理操作。

    这里我们需要重点关注加载阶段和交互阶段,因为影响到我们体验的因素主要都在这两个阶段.

加载阶段

并非所有的资源都会阻塞页面的首次绘制,比如图片、音频、视频等文件就不会阻塞页面的首次渲染。而JavaScript、首次请求的HTML资源文件、CSS文件是会阻塞首次渲染的。把这些能阻塞页面渲染的称为关键资源。基于关键资源,细化出三个影响页面首次渲染的核心因素:

  • 第一个是关键资源个数。

  • 第二个是关键资源大小。

  • 第三个是请求关键资源需要多少个RTT(Round Trip Time).[通常1个HTTP的数据包在14KB左右,所以0.1M的页面需要拆分成8个包来传输,也就是说需要8个RTT]。

然后针对核心因素,考虑优化方案:总的优化原则就是减少关键资源个数、降低关键资源大小、降低关键资源的RTT次数。

交互阶段

交互阶段的优化,一个大的原则就是让单个帧的生成速度变快。 1. 减少JavaScript脚本执行时间。 2. 避免强制同步布局。【所谓强制同步布局,是指JavaScript强制将计算样式和布局操作提前到当前的任务中。】 3. 避免布局抖动。 4. 合理利用CSS合成动画。 5. 避免频繁的垃圾回收。

26 | 虚拟DOM:虚拟DOM和实际的DOM有何不同?

本节先聊一些DOM的缺陷,然后在此基础上介绍虚拟DOM如何解决这些缺陷,最后站在双缓存和MVC的视角来聊聊虚拟DOM。

DOM的缺陷

通过前面对DOM的学习,我们知道对于一些复杂的页面或者目前使用非常多的单页面应用来说,其DOM结构复杂,每次操作需要去不断修改DOM树,每次操作渲染引擎都需要进行重绘、重排或者合成操作,执行一次重排或者重绘操作是非常耗时的,这样就带来了性能问题。 所以就需要一直方式来减少JavaScript对DOM的操作,这时候虚拟DOM就上场了。

什么是虚拟DOM

虚拟DOM要解决的事情:

  • 将页面改变的内容应用到虚拟DOM上,而不是直接应用在DOM上。

  • 变化被应用到虚拟DOM上时,虚拟DOM并不急着去渲染页面,而仅仅是调整虚拟DOM的内部状态,这样操作虚拟DOM的代价就变得非常轻了。

  • 在虚拟DOM收集到足够的改变时,再把这些变化一次性应用到真实的DOM上。

接下来从双缓存和MVC模型这两个视角来聊聊虚拟DOM:

    1. 双缓存

      双换粗是一种经典的思路,应用哎很多场合,能解决页面无效刷新和闪屏的问题,虚拟DOM就是双缓存思想的一种实现。

      使用双缓存,可以先将计算的中间结果存放到另一个缓冲区中,等全部的计算结束,该缓冲区已经存储了完整的图形,这样使得整个图像的输出非常稳定。

  • MVC模式

    基于MVC的设计思想广泛地渗透到各种场合,且基于MVC又衍生出了很多其他模式(如MVP、MVVM),不过万变不离其宗,它们的基础框架都是基于MVC而来。站在MVC视角来理解虚拟DOM能让你看到更为“广阔的世界”.

27 | 渐进式网页应用(PWA):它究竟解决了Web应用的哪些问题?

PWA,全称是Progressive Web App,渐进式网页应用。

渐进式:

  • 站在Web应用开发者来说,PWA提供了一个渐进式的过度方案,让普通站点逐步过度到Web应用。采取渐进式可以降低站点改造的代价,使得站点逐步支持各项新技术,而不是一步到位。

  • 站在技术角度来说,PWA技术也是一个渐进式的演化过程,在技术层面会一点点演进,比如逐渐提供更好的设备特性支持,不断优化更加流畅的动画效果,不断让页面的加载速度变得更快,不断实现本地应用的特性。

    可以这么理解:PWA是一套理念,渐进式增强Web的优势,并通过技术手段渐进式缩短和本地应用或者小程序的距离。

Web应用 VS 本地应用

相较于本地应用,Web应用缺陷:

  • 首先,Web应用缺少离线使用能力,在离线或者弱网环境下基本上是无法使用的。

  • 其次,Web应用还缺少了消息推送的能力。

  • 最后,Web缺少一级入口。

针对以上缺陷,PWA提出了两种解决方案:通过引入Service Worker来试着解决离线存储和消息推送的问题,通过引入manifest.json来解决一级入口的问题。

Service Worker

在2014年的时候,标准委员会就提出来Service Worker的概念,主要思想是在页面和网络之间增加一个拦截器,主要功能就是用来缓存资源和拦截请求。

设计思路: 为避免JavaScript过多占用页面主线程时长的情况,浏览器实现了Web Worker的功能。Web Worker的目的是让JavaScript能够运行在页面主线程之外,且只能执行一些与DOM无关的JS脚本。在Chrome中,Web Worker其实就是在渲染进程中开启一个新线程,它的生命周期和页面关联。 "让其运行在主线程之外"就是Service Worker来自Web Worker的一个核心思想。但需要在Web Worker的基础上加上储存功能。且Service Worker还需要会为多个页面提供服务,所以还不能把Service Worker和单个页面绑定起来。 消息推送也是基于Service Worker来实现的。 最后,若要使站点支持Service Worker,首先必要的一步就是要将站点升级到HTTPS。

28 | WebComponent:像搭积木一样构建Web应用

首先,本节介绍了组件化开发是程序员的刚需,所谓组件化就是功能模块要实现高内聚、低耦合的特性。 不过由于 DOM 和 CSSOM 都是全局的,所以它们是影响了前端组件化的主要元素。 基于这个原因,就出现 WebComponent,它包含自定义元素、影子 DOM 和 HTML 模板三种技术,使得开发者可以隔离 CSS 和 DOM。 在此基础上,还重点介绍了影子 DOM 到底是怎么实现的。 关于 WebComponent 的未来如何,这里我们不好预测和评判,但是有一点可以肯定,WebComponent 也会采用渐进式迭代的方式向前推进,未来依然有很多坑需要去填。