前端性能优化

课程介绍

任何小型企业,博主或成长型公司在网上出现时面临的最大挑战之一就是“页面加载时间”问题。除非您能确保尽可能快速无缝地加载每个页面,您才可以拥有世界上最好最引人注目的网站,否则您仍然无法赢得关注者的尊重和忠诚度。

根据当今网络上的所有主要浏览器公司的说法,如果每个网站都想要排名靠前,表现良好并确保客户满意,那么每个网站都需要快。毕竟,如果您的网页加载速度很慢,那么您会立即增加客户开始在其他地方搜索信息,而不是坚持下去的风险。

这也是为什么很多公司去找专门的技术人员来做SEO(Search Engine Optimization),即搜索引擎优化。 因为,页面的加载会影响到爬虫的爬取,页面加载速度是一项非常重要的指标。况且,页面优化直接影响到了用户体验。所以学习前端性能优化势在必行。

经常有面试官会问:从用户输入浏览器输入url到页面最后呈现 有哪些过程?有什么办法能优化页面性能?

答案大致如下:

  1. 用户输入URL地址
  2. 浏览器解析URL解析出主机名
  3. 浏览器将主机名转换成服务器ip地址(浏览器先查找本地DNS缓存列表 没有的话 再向浏览器默认的DNS服务器发送查询请求 同时缓存)
  4. 浏览器将端口号从URL中解析出来
  5. 浏览器建立一条与目标Web服务器的TCP连接(三次握手)
  6. 浏览器向服务器发送一条HTTP请求报文
  7. 服务器向浏览器返回一条HTTP响应报文
  8. 关闭连接 浏览器解析文档
  9. 如果文档中有资源 重复6 7 8 动作 直至资源全部加载完毕

image-20190619100048897

再深一点层次:会问到渲染机制,重排、重绘

重排:

  1. 定义:DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据各种样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称之为reflow。

  2. 触发Reflow:

    最常问:如何减少Reflow?或者避免Reflow?

重绘:

  1. 定义:当各种盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint。

    页面要呈现的内容,通通都绘制到页面上。

  2. 触发Repaint

    最常问:如何避免最小程序的Repaint?

既然页面的性能优化这么重要,你知道有哪些原因是影响页面性能的?比如...

...

有哪些办法可以加速我们的页面性能?比如...

...

本课程的主要内容:

学习本门课你能收获什么?

课程准备:

页面性能优化

前端性能优化可以分为两大部分:浏览器部分、代码部分。

浏览器部分又可以分为:

代码部分又可以分为:

首先来看看浏览器部分:前端的页面主要在浏览器上运行着,那么我们追根溯源,从浏览器的原理开始,研究如何进行页面性能优化。

浏览器

目前使用的主流浏览器有五个:Internet Explorer、Firefox、Safari、Chrome 浏览器和 Opera。本文中以开放源代码浏览器为例,即 Firefox、Chrome 浏览器和 Safari(部分开源)。根据 StatCounter 浏览器统计数据,目前(2019年 6 月)Firefox、Safari 和 Chrome 浏览器的总市场占有率 62.7%。由此可见,如今开放源代码浏览器在浏览器市场中占据了非常坚实的部分。

image-20190618165435861

浏览器的主要作用

浏览器的主要功能就是向服务器发出请求,在浏览器窗口中展示您选择的网络资源。这些网络资源包括以下内容:

也可以分为HTML文档(HTML/CSS/JS)、PDF、图片、视频和其他类型。

浏览器解释并显示 HTML 文件的方式是在 HTML 和 CSS 规范中指定的。这些规范由网络标准化组织 W3C(万维网联盟)进行维护。
多年以来,各浏览器都没有完全遵从这些规范,同时还在开发自己独有的扩展程序,这给网络开发人员带来了严重的兼容性问题。如今,大多数的浏览器都是或多或少地遵从规范。

浏览器的用户界面有很多彼此相同的元素,其中包括:

奇怪的是,浏览器的用户界面并没有任何正式的规范,这是多年来的最佳实践自然发展以及彼此之间相互模仿的结果。HTML5 也没有定义浏览器必须具有的用户界面元素,但列出了一些通用的元素,例如地址栏、状态栏和工具栏等。

有一些比较有趣的事情:

Safari是乔布斯在2003年的Macworld大会发布的,9年过去了,08年才发展起来的Chrome用了不到Safari一半的时间远远把Safari甩在了后面,这让Safari情何以堪。

两大浏览器都是基于苹果的布局引擎Webkit,按道理Safari是占优势的,因为两者都分别是自己操作系统的默认浏览器,许多人就顺便使用了,懒得去下载别的浏览器。

许多人可能会想,这还不简单,Chrome运行于Windows操作系统,而Safari大部分时候都用于苹果自己的系统,Windows .VS. OS X,哪个使用人群广?很显然是前者,所以Chrome当然比Safari发真快,可是,实际上Safari要比Chrome更早登陆Windows。

在2007年六月,Safari for Windows Beta版就已经发布,正式版于2008年三月发布,而Chrome直到2008年的九月才发布,Chrome只用了一年的时间就超过了Safari。

大量Benchmark测试表明Chrome不管在网页打开还是JavaScript表现方面,速度都最快。

但是,当Safari发布Windows版的时候,测试结果也是一样的,不管在Mac还是PC上测试,那时候都是最快的,如果仅仅是因为速度,那么Safari在2007年六月的表现就应该和chrome在2008年九月的时候一样。

难道Chrome胜在插件?也许,不过Safari在2010年年中的时候也有了许多插件,当然,Chrome的插件质量更好更丰富,如果这也足够让Safari落后于Chrome,那么开发商们可能早就将游戏植入Chrome,而且,Firefox也先于Chrome和Safari植入插件,现在不也败给了Chrome吗?

主要原因:

  1. Safari用起来不舒服,google推广给力
  2. 独特的用户体验(UI、交互、云、Google全家桶)

那么Safari就一文不值了?

但是,Mac上的用户使用safari更加的省电、省资源,稳定性会更好。插件不需要爬梯子!!!更低的学习成本。

IE(Internet Explorer)的黑历史:

IE 浏览器的开发应该是在 1994 年中开始的,那是我还在读大学。微软为了对抗当时的主流浏览器 NetScape,准备开发自己的浏览器并在 Windows 中默认捆绑销售。但是,留给微软的时间并不充裕,他们没时间从零开始,于是和 Spyglass 合作开发,于是,计算机历史上最著名也最臭名昭著的浏览器 IE 诞生了。因为强大的功能和捆绑策略,IE 迅速击败了 NetScape,成为浏览器市场的绝对霸主。

在 2002 年,IE 已经拥有了95%的市场份额,几乎打败了所有的竞争对手。大为僵,又不思进取,于是颠覆者前仆后继。Firefox 开始迅速崛起,Chrome 后来居上,反观 IE,出现了各种安全漏洞和兼容性问题。2006年,IE 被评为“史上第八糟科技产品”。在相当长的时间里,IE 浏览器是前端程序员员的噩梦

带给我们的思考:

浏览器的组成结构

img

  1. 用户界面(User Interface) - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。

  2. 浏览器引擎(Browser engine) - 在用户界面和渲染引擎之间传送指令。

  3. 渲染引擎(Rendering engine) - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。

    也可以叫呈现引擎(Rendering Engine)或者布局引擎(Layout Engine)

    默认情况下,渲染引擎可显示 HTML 和 XML 文档与图片。通过插件(或浏览器扩展程序),还可以显示其他类型的内容;例如,使用 PDF 查看器插件就能显示 PDF 文档。但是在本章中,我们将集中介绍其主要用途:显示使用 CSS 格式化的 HTML 内容和图片。

浏览器(Firefox、Chrome 浏览器和 Safari)是基于两种渲染引擎构建的。Firefox 使用的是 Gecko,这是 Mozilla 公司“自制”的渲染引擎。而 Safari 和 Chrome 浏览器使用的都是 WebKit。

浏览器 渲染引擎(开发语言) 脚本引擎(开发语言)
Chrome Blink (c++) V8 (c++)
Opera Blink (c++) V8 (c++)
Safari Webkit (c++) JavaScript Core (nitro)
FireFox Gecko (c++) SpiderMonkey (c/c++)
Edge EdgeHTML (c++) Chakra JavaScript Engine (c++)
IE Trident (c++) Chakra JScript Engine (c++)

Firefox在2017年启用了新的web引擎Quantum,Quantum 以 Gecko 引擎为基础,同时利用了 Rust 的良好并发性和 Servo 的高性能组件,为 Firefox 带来了更多的并行化和 GPU 运算,让 Firefox 更快更可靠。

2015 年 3 月,微软将放弃自家Edge:转而开发Chromium内核浏览器。

它们的开发时间轴:

image-20190618172835156

WebKit 是一种开放源代码渲染引擎,起初用于 Linux 平台,随后由 Apple 公司进行修改,从而支持苹果机和 Windows。有关详情,请参阅 webkit.org

  1. 网络(Networking) - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。

  2. 用户界面后端(UI Backend) - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。

  3. JavaScript 解释器(JavaScript Interpreter)。用于解析和执行 JavaScript 代码,如 V8 引擎。

    JS引擎线程负责解析Javascript脚本,运行代码。

    JS引擎一直等待任务队列中任务的到来,然后加以处理,一个Tab页(renderer进程)中只有一个JS线程在运行

  4. 数据存储(Data Persistence)。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。

浏览器是多进程的

进程线程简单的理解:进程里面可以有多个线程,进程就是QQ,线程就是会话。

浏览器里面的进程:

  1. Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有
  2. 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建

  3. GPU进程:最多一个,用于3D绘制等

  4. 渲染进程(浏览器内核)(Renderer进程,内部是多线程的)

渲染进程是多线程的:

  1. GUI渲染线程

  2. js引擎线程

  3. 事件触发线程

  4. 定时触发器线程

  5. 异步http请求线程

    了解 一下,执行线:

    image-20190619165952240

渲染线程与JS引擎线程互斥

由于JavaScript是可操纵DOM的,如果在修改这些元素属性同时渲染界面(即JS线程和UI线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了。

因此为了防止渲染出现不可预期的结果,浏览器设置GUI渲染线程与JS引擎为互斥的关系,当JS引擎执行时GUI线程会被挂起, GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。

举一个简单的例子:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <script>
      // 页面上的hello world不会变成红色
      document.getElementById("app").style.color = "red";
    </script>
  </head>
  <body>
    <div id="app">
      hello world!
    </div>
  </body>
</html>

JS阻塞页面加载

从上述的互斥关系,可以推导出,JS如果执行时间过长就会阻塞页面。

譬如,假设JS引擎正在进行巨量的计算,此时就算GUI有更新,也会被保存到队列中,等待JS引擎空闲后执行。 然后,由于巨量计算,所以JS引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比。

所以,要尽量避免JS执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉。

总结一下:

浏览器的渲染机制

对于渲染,我们首先需要了解一个概念:设备刷新率。

设备刷新率是设备屏幕渲染的频率,通俗一点就是,把屏幕当作墙,设备刷新率就是多久重新粉刷一次墙面。基本我们平常接触的设备,如手机、电脑,它们的默认刷新频率都是60FPS,也就是屏幕在1s内渲染60次,约16.7ms渲染一次屏幕。

这就意味着,我们的浏览器最佳的渲染性能就是所有的操作在一帧16.7ms内完成,能否做到一帧内完成直接决定着渲染性,影响用户交互。

渲染引擎一开始会从网络层获取请求文档的内容,内容的大小一般限制在 8000 个块以内。

然后进行如下所示的基本流程

img

渲染引擎将开始解析 HTML 文档,并将各标记逐个转化成“内容树”上的 DOM 节点。同时也会解析外部 CSS 文件以及样式元素中的样式数据。HTML 中这些带有视觉指令的样式信息将用于创建另一个树结构:呈现树

呈现树包含多个带有视觉属性(如颜色和尺寸)的矩形。这些矩形的排列顺序就是它们将在屏幕上显示的顺序。

呈现树构建完毕之后,进入“布局”处理阶段,也就是为每个节点分配一个应出现在屏幕上的确切坐标。下一个阶段是绘制 - 渲染引擎会遍历呈现树,由用户界面后端层将每个节点绘制出来。

需要着重指出的是,这是一个渐进的过程。为达到更好的用户体验,渲染引擎会力求尽快将内容显示在屏幕上。它不必等到整个 HTML 文档解析完毕之后,就会开始构建呈现树和设置布局。在不断接收和处理来自网络的其余内容的同时,渲染引擎会将部分内容解析并显示出来。

那么我们知道了,CSSOM 树和 DOM 树合并成渲染树,然后用于计算每个可见元素的布局,并输出给绘制流程,将像素渲染到屏幕上。优化上述每一个步骤对实现最佳渲染性能至关重要。

具体的流程:

DOM CSSOM RenderTree

请注意 visibility: hiddendisplay: none 是不一样的。前者隐藏元素,但元素仍占据着布局空间(即将其渲染成一个空框),而后者 (display: none) 将元素从渲染树中完全移除,元素既不可见,也不是布局的组成部分。

我们以webkit的渲染作为示例:

img

我们可以使用调试工具来看一个真实的例子:

image-20190618182337662

script标签的处理

JS可以操作DOM来修改DOM结构,可以操作CSSOM来修改节点样式,这就导致了浏览器在解析HTML时,一旦碰到script,就会立即停止HTML的解析(而CSS不会),执行JS,再返还控制权。

事实上,JS执行前不仅仅是停止了HTML的解析,它还必须等待CSS的解析完成。当浏览器碰到script元素时,发现该元素前面的CSS还未解析完,就会等待CSS解析完成,再去执行JS。

JS阻塞了HTML的解析,也阻塞了其后的CSS解析,整个解析进程必须等待JS的执行完成才能够继续,这就是所谓的JS阻塞页面。一个script标签,推迟了DOM的生成、CSSOM的生成以及之后的所有渲染过程,从性能角度上讲,将script放在页面底部,也就合情合理了。

简单来说:渲染线程与JS引擎线程是互斥的,当JS引擎执行时渲染线程会被挂起(相当于被冻结了),渲染更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

总结一下:

浏览器的渲染流程分为:

重排reflow与重绘repaint

重排reflow

reflow指的是重新计算页面布局。

某个节点reflow时会重新计算节点的尺寸和位置,而且还有可能触发其子节点、祖先节点和页面上的其他节点reflow。在这之后再触发一次repaint。

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流,每个页面至少需要一次回流,就是在页面第一次加载的时候。

导致reflow的操作

触发页面重布局的一些css属性

重绘repaint

repiant或者redraw遍历所有的节点检测各节点的可见性、颜色、轮廓等可见的样式属性,然后根据检测的结果更新页面的响应部分。

当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘

只触发重绘不触发重排的一些CSS属性:

我们来看一个例子:

var bstyle = document.body.style; // cache

bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; //  再一次的 reflow 和 repaint

bstyle.color = "blue"; // repaint
bstyle.backgroundColor = "#fad"; // repaint

bstyle.fontSize = "2em"; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

当然,我们的浏览器是聪明的,它不会像上面那样,你每改一次样式,它就reflow或repaint一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些情况浏览器是不会这么做的,比如:resize窗口,改变了页面默认的字体,等。对于这些操作,浏览器会马上进行reflow。

但是有些时候,我们的脚本会阻止浏览器这么干,比如:如果我们请求下面的一些DOM值:

减少重绘与重排:

重绘和回流在实际开发中是很难避免的,我们能做的就是尽量减少这种行为的发生。

PS: display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。

总结一下:

重排:元素的尺寸变了、位置变了

重绘:元素的颜色、背景、边框、轮廓变了,但是,元素的几何尺寸没有变。

Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。

页面加载缓慢的原因

browser-rendering

backend-generation

wpo-front-backend

浏览器部分

代码部分

优化原则

尽量减少HTTP请求

在浏览器(客户端)和服务器发生通信时,就已经消耗了大量的时间,尤其是在网络情况比较糟糕的时候,这个问题尤其的突出。

一个正常HTTP请求的流程简述:如在浏览器中输入"www.xxxxxx.com"并按下回车,浏览器再与这个URL指向的服务器建立连接,然后浏览器才能向服务器发送请求信息,服务器在接受到请求的信息后再返回相应的信息,浏览器接收到来自服务器的应答信息后,对这些数据解释执行。

而当我们请求的网页文件中有很多图片、CSS、JS甚至音乐等信息时,将会频繁的与服务器建立连接,与释放连接,这必定会造成资源的浪费,且每个HTTP请求都会对服务器和浏览器产生性能负担。

网速相同的条件下,下载一个100KB的图片比下载两个50KB的图片要耗费的网络资源更多。所以,请减少HTTP请求。

具体的方法:

  1. 组合文件,优化图片,使用sprites设计风格: 将背景图片合并成一个文件,通过background-imagebackground-position 控制显示;

    确保您的图像不大于它们所需的图像,它们采用正确的文件格式(PNG通常更适用于少于16种颜色的图形,而JPEG通常更适合照片)并且它们是针对Web压缩的。

    使用CSS sprites在网站上经常使用的图像创建模板,如按钮和图标。CSS sprites将您的图像组合成一个大图像,一次加载所有(这意味着更少的HTTP请求),然后只显示您想要显示的部分。这意味着您通过不让用户等待加载多个图像来节省加载时间。

    有一些在线工具:

  2. 肉联图片,使用data:URL方案将图像数据嵌入实际页面中。这可以增加HTML文档的大小。将内嵌图像组合到(缓存的)样式表中是一种减少HTTP请求并避免增加页面大小的方法。

  3. 简化页面的设计

使用内容传送网络CDN

内容分发网络(CDN),也称为内容传送网络,是用于分发传送内容的负载的服务器网络。从本质上讲,您网站的副本存储在多个地理位置不同的数据中心,以便用户可以更快,更可靠地访问您的网站。

对于初创公司和私人网站来说,CDN服务的成本可能过高,但随着您的目标受众变得越来越大并变得更加全球化,CDN对于实现快速响应时间是必要的。

请记住,最终用户响应时间的80-90%用于下载页面中的所有组件:图像,样式表,脚本,Flash等。这是Performance Golden Rule。而不是从重新设计应用程序架构的艰巨任务开始,最好首先分散您的静态内容。这不仅可以大大缩短响应时间,而且由于内容交付网络,它更容易实现。

内容传送网络(CDN)是分布在多个位置的Web服务器的集合,以更有效地向用户传送内容。选择用于向特定用户传送内容的服务器通常基于网络接近度的度量。例如,选择具有最少网络跳数的服务器或具有最快响应时间的服务器。

CDN服务商有很多,这里就不再多说:专门做CDN服务器的蓝讯、网宿、帝联、快网,还有阿里云、腾讯云、华为云等。国外如Akamai TechnologiesEdgeCastlevel3

除了去购买一些CDN服务商的服务以外,对于大多数开发者,可以使用公共CDN网络上的资源,如以下的方式去使用CDN加速:

<!-- google -->
<script
  type="text/javascript"
  src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"
></script>

<!-- cdnjs -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

<!-- qiniu cloud -->
<script src="https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js"></script>

<!-- other cdn -->
<script
  crossorigin="anonymous"
  integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
  src="https://lib.baomitu.com/jquery/3.4.1/jquery.min.js"
></script>

避免空src或者是href值

空的src和href都会导致多余的HTTP请求,虽然不影响加载时间,但是会对服务器产生不必要的流量和压力。浏览器仍然会向服务器发起一个 HTTP 请求:

空的src的image严重的以至于影响整个网站的用户体验,空 src 产生请求的后果不容小憩:

有两种形式:

  1. HTML形式

    <img src="">
    <a href=""></a>
    
  2. JavaScript形式

    var img = new Image();
    img.src = "";
    

解决办法:

  1. 删除空的srchref标签
  2. a标签的href属性,连接到实际的页面:
<a href="#"></a>
<a href="#nogo"></a>
<a href="##"></a>
<a href="###"></a>
<a href="void(0);"></a>
<a href="void(0)"></a>
<a href=";"></a>
<a href=""></a>
  1. 禁止跳转,添加cursor:pointer样式
<style>
    a{cursor: pointer}
</style>
<a>点击一</a>
<a onclick="doSomething()">点击二</a>
  1. a 标签创建一个带有描述信息的href 属性,并监控click事件调用preventDefault()函数。
<a href="#Something_De scriptive" id="my_id">Trigger</a>
<script>
    $("#my_id").click(function(e){
        e.preventDefault(); //取消单击事件的默认动作以阻止链接的跳转。
        //  其他的代码
})
</script>

优点:

gzip的组件

所有现代浏览器都支持 gzip 压缩并会为所有 HTTP 请求自动协商此类压缩。启用 gzip 压缩可大幅缩减所传输的响应的大小(最多可缩减 90%),从而显著缩短下载相应资源所需的时间、减少客户端的流量消耗并加快网页的首次呈现速度。

从HTTP / 1.1开始,Web客户端表示支持使用HTTP请求中的Accept-Encoding标头进行压缩。

Accept-Encoding:gzip,deflate

压缩包括XML和JSON在内的任何文本响应都是值得的。不应对图像和PDF文件进行gzip压缩,因为它们已经过压缩。试图对它们进行gzip不仅会浪费CPU,还可能会增加文件大小。

比如,在nginx中开启gzip压缩:

# 开启gzip
gzip on;
# 启用gzip压缩的最小文件,小于设置值的文件将不会压缩
gzip_min_length 1k;
# gzip 压缩级别,1-10,数字越大压缩的越好,也越占用CPU时间,后面会有详细说明
gzip_comp_level 2;
# 进行压缩的文件类型。javascript有多种形式。其中的值可以在 mime.types 文件中找到。
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;
# 是否在http header中添加Vary: Accept-Encoding,建议开启
gzip_vary on;
# 禁用IE 6 gzip
gzip_disable "MSIE [1-6]\.";

其他web容器启动gzip的方法:

CSS放在顶部,JS放在底部

把Javascript脚本在底部,删除阻止渲染的JavaScript
在HTML文件<body>中指定外部样式表和内联样式块可能对浏览器的渲染性能产生不利影响。

  1. 浏览器阻塞渲染网页直到所有外部的样式表都已被下载。
  2. (用<style>标记指定的)内联样式块可能会导致reflows和页面跳动。
    因此,把外部样式表和内联样式块放在页面的<head>中是很重要的。通过确保样式表首先被下载和解析,可以让浏览器逐步渲染页面。

具体做法:

如果可以让CSS资源只在特定条件下使用,这样这些资源就可以在首次加载时先不进行构建CSSOM树,只有在符合特定条件时,才会让浏览器进行阻塞渲染然后构建CSSOM树。

CSS的媒体查询正是用来实现这个功能的,它由媒体类型以及零个或多个检查特定媒体特征状况的表达式组成。

<!-- 没有使用媒体查询,这个css资源会阻塞渲染  -->
<link href="style.css"    rel="stylesheet">
<!-- all是默认类型,它和不设置媒体查询的效果是一样的 -->
<link href="style.css"    rel="stylesheet" media="all">
<!-- 动态媒体查询, 将在网页加载时计算。
根据网页加载时设备的方向,portrait.css 可能阻塞渲染,也可能不阻塞渲染。-->
<link href="portrait.css" rel="stylesheet" media="orientation:portrait">
<!-- 只在打印网页时应用,因此网页首次在浏览器中加载时,它不会阻塞渲染。 -->
<link href="print.css"    rel="stylesheet" media="print">

使用媒体查询可以让CSS资源不在首次加载中阻塞渲染,但不管是哪种CSS资源它们的下载请求都不会被忽略,浏览器仍然会先下载CSS文件。

把Javascript脚本在底部,删除阻止渲染的JavaScript

浏览器必须通过在呈现页面之前解析HTML来构建DOM树。如果您的浏览器在此过程中遇到脚本,则必须先停止并执行它,然后才能继续。

具体做法:

将脚本定义或引用放置到<body>底部。
<script defer="defer"> defer 属性规定是否对脚本执行进行延迟, 脚本将在页面完成解析时执行。

请输入图片描述

蓝色线代表网络读取,红色线代表执行时间,这俩都是针对脚本的;绿色线代表 HTML 解析。

此图告诉我们以下几个要点:

  1. deferasync 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
  2. 它俩的差别在于脚本下载完之后何时执行,显然 defer*是最接近我们对于应用脚本加载和执行的要求的
  3. 关于 defer,此图未尽之处在于它是按照加载顺序执行脚本的,这一点要善加利用
  4. async 则是一个乱序执行的主,反正对它来说脚本的加载和执行是紧紧挨着的,所以不管你声明的顺序如何,只要它加载完了就会立刻执行
  5. 仔细想想,async对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的,最典型的例子:Google Analytics

减少DNS查找

用户访问网站的过程如下:

  1. 在地址栏输入网站地址,如www.example.com;
  2. 本地DNS得到这个请求,查询本地DNS缓存,如果有这条记录,则直接返回对应的IP;否则,请求网络上的DNS服务器,得到相应的IP,返回给客户机,并缓存这条记录;

  3. 浏览器向得到的IP发起建立连接请求,得到响应后建立连接,请求数据;

  4. Server端计算所需数据,并返回给client端;

  5. client端,即浏览器,解析数据并显示在浏览器窗口中,至此,请求完成。

在 一次请求中,DNS解析可以占到请求时间的三分之一左右(这点有待验证),所以如果可以缩短DNS解析时间,就可以加快页面的打开速度。

缩短DNS解析的 方法可以通过延长DNS缓存的时间选用更快的DNS Server减少域名总数(例如原来有5个img server,分别为img1.xxx.com至img5.xxx.com,则现在可以减少到3个)等等,但是减少域名个数又会降低资源并行下载的数量, 因为同一域名最多可以并行下载两个资源,所以这里需要一个折衷方案,作者的建议就是将资源分布在大于等于2但小于等于4个域名上(这个也有待验证,例如针 对多大的系统选用多少个域名是最合理的等)。

使用DNS预解析

这里会有一些兼容性问题,可以参见:http://caniuse.com/#feat=link-rel-dns-prefetch

sdfdsf

在网页体验中我们常会遇到这种情况,即在调用百度联盟、谷歌联盟以及当前网页所在域名外的域名文件时会遇到请求延时非常严重的情况。那么有没有方法去解决这种请求严重延时的现象呢?

一般来说这种延时的原因不会是对方网站带宽或者负载的原因,那么到底是什么导致了这种情况呢。湛蓝试着进行推测,假设是DNS的问题,因为DNS解析速度很可能是造成资源延时的最大原因。在页面header中添加了以下代码(用以DNS预解析):

<meta http-equiv="x-dns-prefetch-control" content="on" />

<link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />
<link rel="dns-prefetch" href="http://nsclick.baidu.com" />
<link rel="dns-prefetch" href="http://hm.baidu.com" />
<link rel="dns-prefetch" href="http://eiv.baidu.com" />

dns-prefetch需慎用,多页面重复DNS预解析会增加重复DNS查询次数。

使用场景:

  1. 新用户访问,后端可以通过 Cookie 判断是否为首次进入站点,对于这类用户,DNS Prefetch 可以比较明显地提升访问速度
  2. 登录页,提前在页面上进行下一跳页用到资源的 DNS Prefetch
  3. 页面中的静态资源在不同的domain下,如CSS、JS、图片等文件
  4. 电商网站的商品页大量载入不同domain下的商品图,如淘宝
  5. 手机网页
  6. 大型网站
  7. js或服务端重定向

Chrome中的一些指令:

压缩资源

避免3xx/4xx

避免重定向

3xx是重定向相关的HTTP响应代码

重定向的意思是,用户的原始请求(例如请求A)被重定向到其他的请求(例如请求B)。

每次页面重定向到另一个页面时,您的访问者都会面临等待HTTP请求 - 响应周期完成的额外时间。例如,如果移动重定向模式如下所示:

example.com - > www.example.com - > m.example.com - > m.example.com/home,这两个额外重定向中的每一个都会使您的页面成为可能加载速度慢。

HTTP 重定向通过 301/302 状态码实现。

HTTP/1.1 301 Moved Permanently  
Location: http://example.com/newuri  
Content-Type: text/html

301 Moved Permanently,这个状态码标识用户所请求的资源被移动到了另外的位置,客户端接收到此响应后,需要发起另外一个请求去下载所需的资源。

302 Found,这个状态码标识用户所请求的资源被找到了,但不在原始位置,服务器会回复其他的一个位置,客户端收到此响应后,也需要发起另外一个请求去下载所需的资源。

客户端收到服务器的重定向响应后,会根据响应头中 Location 的地址再次发送请求。重定向会影响用户体验,尤其是多次重定向时,用户在一段时间内看不到任何内容,只看到浏览器进度条一直在刷新。

有时重定向无法避免,在糟糕也比抛出 404 好。虽然通过 HTML meta refresh 和 JavaScript 也能实现,但首选 HTTP 3xx 跳转,以保证浏览器「后退」功能正常工作(也利于 SEO)。

常见的优化办法:

避免404浏览器找不到资源的情况发生

发出HTTP请求并获得无用的响应(即404 Not Found)是完全没必要的。特别糟糕的是当外部JavaScript的链接错误并且结果是404时。首先,此下载将阻止并行下载。接下来,浏览器可能会尝试解析404响应主体,就像它是JavaScript代码一样,这样就带来的性能的浪费。

404的影响:

有时候,404错误发生了,用户可能根本没有感觉到。例如

看得到的影响:

常见的优化办法:

404 意味着Not Found,意思是说未找到资源。既然如此,那么至少会有两种原因导致404错误:

AJAX优化

使Ajax可缓存

AJAX=Asynchronous JavaScript And XML,AJAX不是新的编程语言,而是一种使用现有标准的新方法。
AJAX是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。

由于AJAX其实也是需要发起请求,然后服务器执行,并将结果(通常是JSON格式的)发送给浏览器进行最后的呈现或者处理,所以对于网站设计优化的角度而言,我们同样需要考虑对这些请求,是否可以尽可能的利用到缓存的功能来提高性能。

对于AJAX而言,有一些特殊性,并不是所有的AJAX请求都是可以缓存的。

  1. POST的请求,是不可以在客户端缓存的,每次请求都需要发送给服务器进行处理,每次都会返回状态码200。(这里可以优化的是,服务器端对数据进行缓存,以便提高处理速度)
  2. GET的请求,是可以(并且默认)在客户端进行缓存的,除非指定了不同的地址,否则同一地址的AJAX请求,不会重复再服务器执行,而是返回304。

有的时候,我们可能希望GET请求不被缓存,有几种做法来达到这样的目的。

  1. 每次调用的时候,请求不同的地址(可以在原始地址后面添加一个随机的号码)。
  2. 如果你所使用的是jquery的话,则可以考虑禁用AJAX的缓存。
$.ajaxSetup({ cache: false });

axios中

var config = {    headers: {'Content-Type': 'application/json','Cache-Control' : 'no-cache'}};

axios.get('/post',config)

使用GET的Ajax请求

在使用XMLHttpRequest(目前的AJAX都是基于它实现的)的时候,浏览器中的POST实现为两步走的过程,首先发送头部信息,然后再发送数据。但如果是使用GET的话,就只有一个TCP的包发送出去(除非有大量的Cookie),这样无疑可以提高性能。

但是get有容量限制,大于2K(对get的限制IE是2K,firefox、chrome是4K)的内容只能用post。

归类一下:

利用缓存

浏览器会缓存大量信息(样式表,图像,JavaScript文件等),以便当访问者返回您的站点时,浏览器不必重新加载整个页面。然后设置“expires”标题,以表示希望缓存该信息的时间。在许多情况下,除非您的网站设计经常更改,否则一年是合理的时间段。

利用浏览器缓存 ,为链接或者资源,添加Expires或Cache-Control头

格式:

Expires = "Expires" ":" HTTP-date
# e.g.
Expires: Thu, 01 Dec 1994 16:00:00 GMT 
#(必须是GMT格式)

通过HTTP的META设置expires和cache-control

<meta http-equiv="Cache-Control" content="max-age=7200" />
<meta http-equiv="Expires" content="Mon, 01 Aug 2019 00:00:00 GMT" />

上述设置仅为举例,实际使用其一即可。这样写的话仅对该网页有效,对网页中的图片或其他请求无效,并不会做任何cache
这样客户端的请求就多了,尽管只是检查Last-modified状态的东西,但是请求一多对浏览速度必定有影响。

Cache-Control 的参数包括:

把css与js使用外联的方式进行使用

建议将css和js以外联方式引用以充分利用cache,只有一个例外就是针对首页,首页可以采用内联来减少http请求数,加快页面显示速度,文中说首 页内联的原因是首页被人们访问的次数不会太多,而且样式一般比较特殊,不像其它页面一样有相同的页面模板(因为就有相同的样式部分),所以首页的样式可以 内联。并且应该在首页加载完成之后,再在后台动态加载后续页面的css和js,以提高后续页面的访问速度。

如果站点上的用户每个会话有多个页面查看,并且您的许多页面重复使用相同的脚本和样式表,则缓存的外部文件可能会带来更大的潜在好处。

对于现在的单页面的应用,更适合于动态加载的方案

缓存favicon.ico,并设置最好小于1k

favicon.ico是一个保留在服务器根目录中的图片,为了减轻拥有favicon.ico的带来的性能问题,请确保:

配置的ETag

ETag,全程为:Entity Tag,意思是实体标签,它属于HTTP协议的一部分,也就是所有的Web服务器都应该支持这个特性。它的作用是用一个特殊的字符串来标识某个资源的“版本”,客户端(浏览器)请求的时候,比较ETag如果一致,则表示该资源并没有被修改过,客户端(浏览器)可以使用自己缓存的版本,避免重复下载。
它比last-modified date更具有弹性,例如某个文件在1秒内修改了10次,Etag可以综合Inode(文件的索引节点(inode)数),MTime(修改时间)和Size来精准的进行判断,避开UNIX记录MTime只能精确到秒的问题。 服务器集群使用,可取后两个参数。使用ETags减少Web应用带宽和负载。

响应标 优势 和特点 劣势 和可能的问题
Expires - HTTP 1.0就有,简单易用。 - 服务器通过这个Header告诉浏览器,某资源直到某个时间才会过期,所以在没有过期之前,浏览器就直接使用本地的缓存了。 - 因为这是时间是由服务器发送的(UTC),但如果服务器时间和客户端事件存在不一致,可能会有些问题。 - 可能存在版本的问题,因为如果在到期之前修改过了,客户端是不会知道的。 - Cache-Control中的max-age可以实现类似的效果,但更加好,因为max-age是一个以秒为单位的时间数,而不是具体的时间,所以不存在上面提到的第一个问题。
Cache-Contro - 服务器通过一个Header(Last-Modified)告诉浏览器,某资源最后修改的时间。 - 浏览器在请求的时候,包含一个Header(If-Modified-Since),然后服务器可以进行比较,如果在该时间后没有修改过,则返回304。 - 它比Expires多很多选项设置 - Last-Modified 也是一个时间,但该时间只能精确到秒,如果在同一个秒中有多次修改(这个在现在的环境下应该确实是可能的),则可能会发生问题。
ETag - 可以更加精确地判断资源是否被修改,因为它不是一个时间值,而是对时间经过处理的一个长整型数值(当然具体算法我们目前还不得而知)。 - 浏览器发起新请求时需要包含 If-None-Match。 - 如果部署在服务器场环境中,配置不当的话,可能每个服务器会对相同的资源生成不一样的ETag,这样就增加了重复下载的可能性。

我们来看一个真实的例子:

image-20190619145105413

其他的一些缓存使用:

总结一下:

缩短服务器响应时间

服务器响应时间受到访问流量,每个页面使用的资源,服务器使用的软件以及硬件本身的影响。要改善服务器响应时间,请查找性能瓶颈,如慢速数据库查询,慢速路由或缺少足够的内存并修复它们。最佳服务器响应时间低于200毫秒。

很多潜在因素都可能会延缓服务器响应,例如缓慢的应用逻辑、缓慢的数据库查询、缓慢的路由、框架、库、资源 CPU 不足或内存不足。您需要充分考虑所有这些因素,才能改善服务器的响应用时。 若想找出服务器响应用时过长的原因,首先要进行衡量。然后,准备好相关数据,并参阅有关如何解决该问题的相应指导。当解决问题后,您必须继续衡量服务器响应用时,并设法应对任何会在将来出现的性能瓶颈问题。

  1. 收集并检查现有性能和数据。若无可用内容,请使用自动化的网络应用监测解决方案(市面上有托管的开源版本,适用于大多数平台)进行评估,或添加自定义的方法。
  2. 找出并修复首要的性能瓶颈问题。如果z您使用的是热门网页框架或内容管理平台,请参阅与性能优化最佳做法相关的文档。
  3. 监测并提醒任何会在将来出现的性能衰退问题!

通常来说,服务器的响应时间由数据库的问题居多,还有错误的程序设计(比如:烂用for循环去查数据库)。

优化工具

Google Page Speed

Google Page Speed以帮助开发人员和网站所有者确保他们的网页尽可能顺畅,快速地运行。

Google Page Speed特点:

对于某些人来说,Google Page Speed将是显而易见的选择,因为它可以与其他对维护网站性能至关重要的Google工具一起使用,例如Google Analytics。

JsPerf & Benchmark.js

经常看到项目中有一个benchmark的文件夹,那么这个benchmark是干嘛用的?

JsPerf在线:https://jsperf.com/

JsPerf Github地址:https://github.com/jsperf/jsperf.com

Benchmark.js网址:https://github.com/bestiejs/benchmark.js

BenchMark.js可以本地安装,而JsPerf.js是线上使用的。

HTML中引入:

<script src="lodash.js"></script>
<script src="platform.js"></script>
<script src="benchmark.js"></script>

或者使用npm:

$ {sudo -H} npm i -g npm
$ npm i --save benchmark

下面是一个具体的用例:

Reg vs IndexOf

var suite = new Benchmark.Suite;

// add tests
suite.add('RegExp#test', function() {
  /o/.test('Hello World!');
})
.add('String#indexOf', function() {
  'Hello World!'.indexOf('o') > -1;
})
// add listeners
.on('cycle', function(event) {
  console.log(String(event.target));
})
.on('complete', function() {
  console.log('Fastest is ' + this.filter('fastest').map('name'));
})
// run async
.run({ 'async': true });

// logs:
// => RegExp#test x 4,161,532 +-0.99% (59 cycles)
// => String#indexOf x 6,139,623 +-1.00% (131 cycles)
// => Fastest is String#indexOf

再来看一个 call与apply的对比:

var Benchmark = require('benchmark');
var suite = new Benchmark.Suite;
var arr1 = function (str) {
    return [].slice.apply(str);
};
var str2 = function (str) {
    return [].slice.call(str);
};
// 添加测试
suite.add('arr1', function() {
    arr1('test');
})
    .add('str2', function() {
        str2('test');
    })
// add listeners
    .on('cycle', function(event) {
        console.log(String(event.target));
    })
    .on('complete', function() {
        console.log('Fastest is ' + this.filter('fastest').pluck('name'));
    })
// run async
    .run({ 'async': true });

benckmark是对js运行能效的一个对比测试,让你能够知道自己的JS代码中与其他方案对比的时候,是否有优化的空间。当然,这些新的方案,需要自己去编写去测试。同样,也可以在JsPerf的官网上去看其他的测试结果。

执行办法 :

npm init -y

npm install -S benchmark

touch test.js //Linux

node test.js

代码优化——写出优质的代码

命题很大,但是我们要简化来谈一下。

代码优化,先要写出优质的代码,有几点原则:

之后就是练习与阅读,阅读别人的代码,学习别人的精华,了解别人代码中的缺点。使用JsPerf或者benchemark去对比优化性能。

前端代码规范:Airbnb,github,Google,腾讯,阿里等。

JsPerf

JsPerf使用简介

线上使用方案

需要使用Github进行登录:

image-20190618210408261

点击Login with Github to Create Test Cases:

image-20190619103010414

登录之后,进入授权页面:

image-20190619103140822

登录过后,就可以创建自己的测试用例啦:

image-20190619143549467

线下使用方案:

环境准备:

创建过程:

  1. 安装Mysql

    mysql -uroot -e "CREATE DATABASE jsperf; GRANT ALL ON jsperf.* TO 'jsuser'@'localhost' IDENTIFIED BY 'jspass'; FLUSH PRIVILEGES;"
    
  2. Browserscope.org 注册一个API key ,在 the settings page页面中.

  3. 注册一个 new OAuth GitHub application. 使用callback URL 留空. 复制 "Client ID" 和 "Client Secret"到github的页面上。

  4. 使用npm install安装

  5. 配置.env的文件

NODE_ENV=development
# from Prerequisites step 1
MYSQL_USER=jsuser
MYSQL_PASSWORD=jspass
MYSQL_DATABASE=jsperf
# from Prerequisites step 2
BROWSERSCOPE=
# from Prerequisites step 3
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
GITHUB_CALLBACK=http://localhost:3000

BELL_COOKIE_PASS=password-should-be-32-characters
COOKIE_PASS=password-should-be-32-characters

# customizable but not recommended for local development
# SCHEME=http
# DOMAIN=localhost
# PORT=3000
# MYSQL_HOST=localhost
# MYSQL_PORT=3306
# LOGGLY_TOKEN=
# LOGGLY_SUBDOMAIN=
# REDIS_HOST=
# REDIS_PORT=
# REDIS_PASSWORD=
  1. 运行npm start

代码示例for vs foreach

我们对比一下for 与 foreach

screencapture-jsperf-2019-06-18-21_15_29

测试过程:

image-20190618211820471

测试结果:

image-20190618211915799

这里有一个更全面的对比结果:

https://jsperf.com/for-vs-foreach/75

image-20190618213130251

image-20190618213201840

还有一些比较有意思的测试:

可以通过浏览:https://jsperf.com/popular进行查看

image-20190619143653346

前端项目优化实践

我们来对比下Date()与Moment()中的format

const Benchmark = require('benchmark');
const moment = require('moment')

Date.prototype.Format = function (fmt) {
  var o = {
    "M+": this.getMonth() + 1, //月份
    "d+": this.getDate(), //日
    "h+": this.getHours(), //小时
    "m+": this.getMinutes(), //分
    "s+": this.getSeconds(), //秒
    "q+": Math.floor((this.getMonth() + 3) / 3), //季度
    "S": this.getMilliseconds() //毫秒
  };
  if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
  for (var k in o) {
    if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
  }
  return fmt;
}

var suite = new Benchmark.Suite;

// add tests
suite.add('Date format', function () {
  console.log(new Date().Format('yyyy-MM-dd hh:mm:ss'))
})
  .add('Moment format', function () {
    console.log(moment().format('YYYY-MM-DD HH:mm:ss'))
  })
  // add listeners
  .on('cycle', function (event) {
    console.log(String(event.target));
  })
  .on('complete', function () {
    console.log('Fastest is ' + this.filter('fastest').map('name'));
  })
  // run async
  .run({ 'async': true });

输出结果 :

Moment format x 73,570 ops/sec ±4.93% (82 runs sampled)
Fastest is Moment format

PageSpeed

PageSpeed简介

PageSpeed Insights 的Chrome扩展是由谷歌官方开发的一款可以分析页面载入的各个方面,包括资源、网络、DOM以及时间线等等信息的插件,也提供在线访问方式。

使用Google PageSpeed Insights

打开https://developers.google.com/speed/pagespeed/insights/

输入需要分析的网址即可,这里面需要注意必须在公网可以访问到网址。

image-20190619212956061

使用Chrome插件/Mozila插件

image-20190619203556941

下载安装办法:

  1. 使用插件ID:djfpadpbeemogokhllpiakdeadpoclanhttps://chrome-extension-downloader.com/来下载

    打开:chrome://extensions/

    ctx文件拖进去

  2. 如果有梯子,那么直接在商店里面搜索

插件使用:

image-20190619212836235

前端项目优化实践

补充学习

编码、解码形成DOM树的过程

浏览器从网络或硬盘中获得HTML字节数据后会经过一个流程将字节解析为DOM树:

整个DOM树的构建过程其实就是: 字节 -> 字符 -> 令牌 -> 节点对象 -> 对象模型,下面将通过一个示例HTML代码与配图更形象地解释这个过程。

如下面的这段HTML

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

DOM Rendering

优化工作清单

当小伙伴们出现了页面性能问题时,或者在设计我们的应用之前,就可以参考如下的条目进行快速检查。

优化关键渲染路径

假设有一个HTML页面,它只引入了一个CSS外部文件:

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

它的关键渲染路径如下:

img

首先浏览器要先对服务器发送请求获得HTML文件,得到HTML文件后开始构建DOM树,在遇见<link>标签时浏览器需要向服务器再次发出请求来获得CSS文件,然后则是继续构建DOM树和CSSOM树,浏览器合并出渲染树,根据渲染树进行布局计算,执行绘制操作,页面渲染完成。

有以下几个用于描述关键渲染路径性能的词汇:

接下来,案例代码的需求发生了变化,它新增了一个JavaScript文件。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="app.js"></script>
  </body>
</html>

img

JavaScript文件阻塞了DOM树的构建,并且在执行JavaScript脚本时还需要先等待构建CSSOM树,上图的关键渲染路径特性如下:

现在,我们要优化关键渲染路径,首先将<script>标签添加异步属性async,这样浏览器的HTML解析器就不会阻塞这个JavaScript文件了。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="app.js" async></script>
  </body>
</html>

img

接下来对CSS进行优化,比如添加上媒体查询。

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet" media="print">
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="app.js" async></script>
  </body>
</html>

img

优化关键渲染路径就是在对关键资源、关键路径长度和关键字节进行优化。关键资源越少,浏览器在渲染前的准备工作就越少;同样,关键路径长度和关键字节关系到浏览器下载资源的效率,它们越少,浏览器下载资源的速度就越快。

其他JS在线测试网站

参考资料

  1. 浏览器的工作原理:新式网络浏览器幕后揭秘 强烈推荐
  2. 渲染树构建、布局及绘制
  3. 优化第一个字节的时间
  4. TCP的三次握手与四次挥手(详解+动图)推荐
  5. 常见性能优化策略的总结
  6. Page Speed
  7. web性能优化篇
  8. 2018 前端性能优化清单
  9. (译)2019年前端性能优化清单 — 上篇
  10. (译)2019年前端性能优化清单 — 中篇
  11. (译)2019年前端性能优化清单 — 下篇
  12. PageSpeed Insights 规则
  13. 浏览器渲染过程与性能优化
  14. 如何提升 CSS 选择器性能
  15. 前端性能优化:2018年JavaScript开销及优化工具和方法
  16. 从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理推荐
  17. 彻底理解浏览器的缓存机制 推荐2