第1节 理解Web性能

1.1 模拟网络连接

  • 由于是在本地计算机上运行客户网站的,所以向本地主机发出请求不会出现延迟。但没有延迟就很难衡量任何性能提升,因为这种场景下不会存在网络瓶颈。

  • 首先打开Chrome,启用开发者工具。在Windows下需要按F12,在Mac下则要按Command +Alt + I。此时开发者工具应该会出现在Chrome窗口中。另一种办法是选择View ➤ Developer➤Developer Tools(视图→开发者→开发者工具)菜单。出现Tools菜单时,点击窗口顶部的Network选项卡,如图所示。

    throttle.png

    注意!用完要记得关闭,不然会影响浏览其他网站

1.2 资源瀑布图

  • 在Network选项卡中,首先要确保选中Disable cache复选框。首次访问一个网站时,不会有任何资源缓存——这个场景才是你想要重现的,否则网站的资源将从缓存中提供。尽管有缓存时网站加载速度更快,但最好假设普通用户未曾缓存过你的网站资源。对于这样的未知站点,此类情况发生的概率更大。

  • 在Network选项卡中,要确保左上角的Record按钮处于开启状态(见图1-7)。按钮为红色代表已启用

    record.png

1.3 ⭐浏览器与服务器通信

  • 当浏览器请求网页时,使用超文本传输协议(Http)协议和服务器通信,浏览器发出一个HTTP请求,Web服务器回一个HTTP响应,响应包含状态码和请求的内容
  • 发出请求后,会受到一个200 OK 的响应码,这个响应码确认了请求的资源确实存在,且响应中还包含了index.html的内容,随后,Web浏览器会下载并解析index.html的内容
  • 上述所有步骤都会产生所谓的延迟:包括等待请求到达Web服务器所花费的时间、Web服务器汇集和发送响应内容所花费的时间,以及Web浏览器下载响应内容的时间。提高性能的主要目的之一就是减少延迟,即减少响应到达客户端所需要的时间。
  • 在HTTP/1服务器和浏览器的通信中,可能会出现一种被称为队头阻塞(head-of-line blocking)的现象。之所以会发生这种情况,是因为浏览器限制了单一时间点内发出的请求数(通常是6个)。当一个或者多个请求正在处理,而其余请求已完成时,对内容的新请求将会被阻塞,直到原先剩余的请求完成为止。这种行为会增加页面的加载时间。HTTP/2是HTTP的一个新版本,它在很大程度上解决了队头阻塞问题,并在浏览器中得到了广泛支持

1.4 基本的Web优化技术

1.4.1 优化网站资源(css、js、html)
  • 减少请求数。但请注意,这种方法最适合用于HTTP/1工作流。这个客户网站的请求已经比较轻量了,减少请求数并没有多大作用

  • 这些优化工作包括:首先要缩小网站资源,包括CSS、JavaScript和HTML本身;然后要在不损害其视觉完整性的前提下,优化网站上的图像;最后要在服务器上压缩文本资源。

1.4.2 缩小资源
  • 缩小(minification)是从基于文本的资源中去除所有空白和非必要字符的过程,因而不会影响资源的工作方式。下图是CSS应用缩小的基本思想。

    minify.png

  • 本节首先要缩小网站的CSS,然后是JavaScript,最后是HTML

    • 先下载包

      1
      npm install -g minifier html-minify
    • 缩小CSS

      1
      minify -o styles.min.css styles.css

      然后在index.html中更新这个文件的引用,方法是将标签的引用从styles.css更改为styles.min.css

      1
      <link rel="stylesheet" type="text/css" href="css/styles.min.css">
    • 依照上面的方法 缩小Javascript、html文件,之后修改引入即可。

    • 缩小Html

      • 缩小网站的HTML之前,需要将网站根文件夹中的index.html复制到一个名为index.src.html的单独的源文件中,以便保留原始文件并进行更改。复制完成后,可以使用htmlminify将其缩小

        1
        htmlminify -o index.html index.src.html

1.5 使用服务器压缩

  • 服务器压缩的工作方式是用户从服务器请求网页。用户的请求附带一个Accept-Encoding头部信息,向服务器告知浏览器可以使用的压缩格式

  • 步骤

    • 安装compression模块

      1
      npm install compression

      配置Node HTTP服务器以使用compression

      1
      2
      3
      4
      5
      6
      7
      8
      9
      var express = require("express");
      var compression = require("compression");

      var app = express();

      // 运行静态服务器
      app.use(compression()) //脚本将compression模块挂载到Web服务器
      app.use(express.static(__dirname));
      app.listen(8080);
    • 比较应用服务器压缩前后客户网站上文本资源

      compression.png

    1.6 压缩图像

  • 你试过压缩JPEG或MP3文件吗?这不仅不会缩小体积,而且最终的压缩文件可能会更大。这是因为这些类型的文件在编码时已经被压缩了。其压缩方式和Web上的内容没什么不同。要注意避免压缩那些已经在编码时使用压缩的文件类型,例如JPEG、PNG和GIF图像,以及WOFF和WOFF2字体文件。

  • 要压缩这些图像,请将它们上传到TinyPNG网站,该网站将自动进行优化。完成后,下载所有文件,并将其复制到网站的img文件夹。出现提示时,为所有冲突选择Overwrite(覆盖)选项。然后重新加载页面,在Chrome的开发者工具中再次检查瀑布图,以查看这些较小的图像所产生的差异。效果如下:

    tinypng.png

第2节 使用评估工具

2.1 使用Google PageSpeed Insights

  • Google PageSpeed Insights (一次只能分析一个URL)

  • 使用Google Analytics进行批量报告

    • 如果你的网站上已经有Google Analytics,那么你要做的就是登录并跟进。如果你尚未在网站上安装它,请使用Google账户登录http://www.google.com/analytics,并按照说明进行操作。这个过程只需花费很少的时间,并且需要将一小段JavaScript代码粘贴到站点的HTML中。之后需要等一到两天,让Google Analytics收集数据。

    • 请注意,向网站添加Google Analytics会带来法律问题。安装跟踪代码时,你要接受法律协议的条款。如果你是网站的唯一拥有者,那么可以自行决定,否则一定要得到网站拥有者的同意。如果你是一家大公司的开发人员,这一点很重要,因为法律审查在大公司是常见的流程。

    • 报告指标

      ❏ Page——页面URL。

      ❏ PageViews——报告周期内页面的浏览次数。报告周期通常是前一个月,但可以更改为自定义的时间段。

      ❏ Avg. Page Load Time——页面加载的平均用时,单位是秒。

      ❏ PageSpeed Suggestions——PageSpeed Insights为提升相关页面URL的性能,给出的建议数量。单击这个值将跳转到一个新窗口,其中包含该URL的PageSpeed Insights报告。

      ❏ PageSpeed Score——PageSpeed Insights报告给出的分数。该分数的范围为1~100,分数低表示有改进的空间,分数高表示性能特征好。

2.2 检查网络请求

2.2.1 查看计时时间(首字节时间)
  • 首字节时间(Time to First Byte,TTFB),即从用户请求网页到响应的第一个字节到达之间的时间。

  • 鼠标悬浮在瀑布图(可到1.2了解)中的waterfall即可查看。

    ttfb

2.2.2 查看HTTP请求和响应头
  • 典型的请求/响应图,它显示了伴随请求和响应的HTTP头部(尽管该示例比实际中简单得多)

    http.png

  • 在Network选项卡下,单击资源名称,右侧的单独窗格中将显示其请求和响应头部。

    request-headers.png

  • Web服务器的Content-Encoding响应头可以用于确定资源是否已被压缩,以及所使用的压缩算法(本例中为gzip)

    content-encoding.png

2.3 渲染性能检查工具

2.3.1 ⭐理解浏览器如何渲染网页
  • 用户访问网站时,浏览器将解析HTML和CSS,并将其渲染到屏幕。图2-15显示了这个过程的基本情况。
    • 解析HTML创建DOM(Document Object Model)
      • 从Web服务器下载HTML时,浏览器会对其进行解析以构建DOM,这是HTML文档结构的层次表示
    • 解析CSS创建CSSOM(CSS Object Model)
      • DOM建立完成后,浏览器解析CSS并创建CSSOM。CSSOM与DOM类似,只不过它是用来表示将CSS规则应用于文档的方式。
    • 布局元素
      • DOM和CSSOM树组合创建渲染树,然后渲染树执行布局过程,在此过程应用CSS规则,并在页面上布局元素以创建UI
    • 绘制页面
      • 文档完成布局过程后,页面外观将应用CSS和页面中的媒体内容。绘制过程结束时,输出转换为像素并在屏幕上显示。
2.3.2 使用Google Chrome的Performance面板
  • Chrome的Performance面板可以记录页面的加载、脚本执行、渲染和绘制活动

    performance.png

  • 事件摘要显示会话中上述每个类别花费的CPU时间量。可以在工具窗格底部的Summary选项卡看到摘要

    summary.png

  • 火焰图用来表示计算机程序中发生的事件。在Chrome的Performance面板中,它将这些数据排列在一个调用栈中。对火焰图来说,调用栈是记录的页面活动的分层表示。

    fire.png

  • 当你在火焰图中找到一个想要深入的调用栈时,可以通过点击它的层与之交互。点击层时,Performance面板底部的摘要视图将会更新所选事件的特定信息

    fire-detail.png

2.3.3 jank可能是问题事件元凶
  • 最小化浏览器加载和渲染页面的时间。要做到这一点,必须击败唯一的敌人:jank。jank是指交互和动画效果卡顿,或未能顺利渲染。如果使用的编程技术欠佳,那么即使是从网络快速加载的页面,也会受到jank的影响。
  • 导致jank的原因时单一的帧中占用了太多的CPU
  • 当在活动概述或火焰图上看到红色时,它就是低帧速率的一个指征,这是jank的前兆

2.4 JavaScript代码基准测试

  • 使用consoletimetimeEnd,在代码块前后使用

    1
    2
    3
    4
    console.time("Flag")
    //...中间测试代码
    console.timeEnd("Flag")
    //然后在控制台查看即可

2.5 模拟设备和互联网连接(TODO)

第3节 优化CSS

3.1 原则

  • 减少冗余代码

  • 合并原则

  • 避免类名过于具体

    1
    header div.phoneNumber h3.numberHeader

    简化为

    1
    .numberHeader

3.2 ⭐查找CSS冗余

  • csscss是一个命令行工具,可以在CSS中查找冗余。这是重构CSS的一个好起点。要安装csscss,需要使用Ruby的gem安装程序。如果你安装了SASS,即可使用gem

  • 你可以在一个CSS文件上运行它。尝试在客户网站的styles.css文件上运行:

    1
    csscss styles.css -v --no-match-shorthand

    这条命令使用两个参数检查styles.css是否有多余的规则。-v参数告诉程序要详细打印出匹配的规则。–no match-shorthand参数使程序不会将任何简写规则(如border-bottom)扩展为更明确的规则(如border-bottom-style样式)。如果要扩展这些规则,请删除这个开关。程序输出将显示跨元素间的所有冗余样式。以下代码是这些规则的一个示例。控制台输出如下:

    csscss.png

  • 根据控制台输出执行如下操作

    • 合并选择器和规则

      csscss1.png

    • 清除单个选择器中的匹配规则——返回代码前面,并将#okayButton、#schedule和.submitAppointment a多余的规则移除。

    • 重新运行csscss,检查输出,并重复前面的步骤——清理完旧选择器中的冗余规则后,重新运行csscss,验证优化后的规则是否已从代码中删除。

3.3 em与rem

  • em是根据文档的默认字体大小(通常为16px)计算的相对单位。公式:px / 默认文字大小 = em。此时,平板计算机的临界点600px,除以默认的文档字体大小16px,得到的值就是37.5em。1000px的桌面临界点使用相同的公式转换,得到的值就是62.5em。
  • em是特定于上下文的单位
  • 如果其父级元素的字体大小为12px,则em值是通过将原始px值除以12来计算的。rem单位类似于em,只是它的上下文总是依据文档根节点的默认字体大小,而不是其父元素的字体大小。
  • rem单位类似于em,只是它的上下文总是依据文档根节点的默认字体大小,而不是其父元素的字体大小

3.4 viewport

  • 为了确保设备正确显示新的响应式CSS,还应该在元素中添加以下标签:

    1
    <meta name="viewport" content="width=device-width,initial-scale=1">

    这个标签告诉浏览器两件事:设备应该以与设备屏幕相同的宽度渲染页面,并且页面的初始比例应该是100%

3.5 调整CSS性能

3.5.1 原生CSS中避免使用@import
  • 你可能见过@import指令在CSS中的使用。应该避免这种做法,因为@import指令与标签不同,在下载整个样式表之前,不会处理样式表中的@import指令。这种行为会导致网页的总加载时间延迟。

  • @import串行请求

    import-css-serial.png

  • 在LESS/SASS中,@import有不同的功能。在这些语言中,@import由编译器读取并用于打包LESS/SASS文件。这样你就可以在开发期间模块化样式,并在编译为CSS时进行打包。

  • <link>标签加载是并行加载请求

3.5.2 防止无样式内容闪烁
  • 出现这种原因是由于css在文档太晚加载,应该尽早在<head>标签中添加css引用,这样还可以提高页面渲染性能
3.5.3 使用flex布局替代传统布局,性能更优

3.6 使用CSS过渡

3.6.1 trasition
  • 使用

    1
    transition: transition-property transition-duration transition-timing-function transition-delay;

    这个简写属性代表以下内容。

    • transition-property——需要设置动画的CSS属性。值可以是任何有效的属性,如color、border-radius等。某些属性无法设置动画,例如display属性。

    • transition-duration——完成过渡所需的时间。可以用秒或毫秒表示(例如,2.5s或250ms)。

    • transition-timing-function——过渡期使用的缓动效果。可以使用预设(如linear或ease)表示,也可以使用steps函数分段,或者通过cubic-bezier函数提供更细微的缓动行为。忽略此选项将使用默认的ease预设设置过渡动画。

    • transition-delay——过渡开始前的延迟时间(以秒或毫秒为单位)。如果不需要延迟,则忽略此项。还可以在元素上过渡多个属性。如果还希望过渡.box元素的宽度和高度,则可以向transition属性添加更多内容:

  • 还可以在元素上过渡多个属性。如果还希望过渡.box元素的宽度和高度,则可以向transition属性添加更多内容:

    1
    .box{    width:64px;    height:64px;    transition:width 2s ease-out, height 2s ease-out;}
3.6.2 will-change优化过渡
  • will-change接受任何有效的CSS属性,或者以逗号分隔的属性列表,用来设置动画

    1
    will-change:property,...
  • 需要注意的是需要给属性足够的时间工作

    1
    2
    3
    4
    5
    //error
    #sideHeader a:hover{
    background-color:#ff0000;
    will-change:background-color;
    }

    这样做的问题是,浏览器没有时间来应用必要的优化。此时要想更好地使用该属性,则应当将其应用于父元素的:hover状态,以便浏览器可以预测将要发生的情况:

    1
    2
    3
    4
    //good
    #sideHeader:hover a{
    will-change:background-color;
    }

    这为浏览器提供了足够的时间来为元素的更改做准备,因为当用户的鼠标进入#siteHeader元素并悬停在链接上时,其中的所有a元素都将在#siteHeader元素悬停事件时准备好。

  • 关于这个属性,需要记住的关键一点是,你可以预测元素的潜在更改,而不是假设它们会发生。