译文出处

作者: 前端  发布:2019-10-12

Web性能优化系列(1):Web性能优化分析

2015/04/08 · CSS, HTML5, JavaScript · 性能优化

本文由 伯乐在线 - 鸭梨山大 翻译,sunbiaobiao 校稿。未经许可,禁止转载!
英文出处:gokulkrishh.github.io。欢迎加入翻译组。

如果你的网站在1000ms内加载完成,那么会有平均一个用户停留下来。2014年,平均网页的大小是1.9MB。看下图了解更多统计信息。

图片 1

网站的核心内容需要在1000ms内呈现出来。如果失败了,用户将永远不会再访问你的网站。通过降低页面加载的时间,很多著名公司的收入和下载量有显著的提升。比如

  • Walmart 每降低100ms的加载时间, 他们的收入就提高1%.
  • Yahoo 每降低400ms的加载时间,他们的访问量就提升9%。
  • Mozilla 将他们的页面速度提升了2.2秒,每年多获得了1.6亿firefox的下载量。

浏览器的渲染:过程与原理

2017/10/18 · 基础技术 · 2 评论 · 浏览器

原文出处: 天方夜   

 图片 2内内容说明

本文不是关于浏览器渲染的底层原理或前端优化具体细节的讲解,而是关于浏览器对页面的渲染——这一过程的描述及其背后原理的解释。这是因为前端优化是一个非常庞大且零散的知识集合,一篇文章如果要写优化的具体方法恐怕只能做一些有限的列举。

然而,如果了解清楚浏览器的渲染过程、渲染原理,其实就掌握了指导原则。根据优化原则,可以实现出无数种具体的优化方案,各种预编译、预加载、资源合并、按需加载方案都是针对浏览器渲染习惯的优化。

更快地构建 DOM: 使用预解析, async, defer 以及 preload

2017/09/28 · JavaScript · async, defer, DOM, preload

原文出处: Milica Mihajlija   译文出处:众成翻译   

在 2017年,保证页面快速加载的手段涵盖了方方面面,从压缩和资源优化,到缓存,CDN,代码分割以及 tree shaking 等。 然而,即便你不熟悉上面的这些概念,或者你感到无从下手,你仍然可以通过几个关键字以及精细的代码结构使得你的页面获得巨大的性能提升。

新的 Web 标准 ``使你能够更快地加载关键资源,这个月晚些时候,Firefox 就会支持这个特性。同时在 Firefox Nightly 版本或者 开发者版本 上已经可以使用这些功能。与此同时,这也是回顾基本原理,深入了解 DOM 解析相关性能的一个好时机。

理解浏览器的内部机制是每个 web 开发者最强有力的工具。我们看看浏览器是如何解释代码以及如何使用推测解析(speculative parsing)来帮助页面快速加载的。我们会分析 deferasync 是如何生效的以及如何利用新的关键字 preload

网站优化的步骤

  1. 设定性能预算。
  2. 测试当前的性能。
  3. 找出导致性能问题的地方。
  4. 最后,duang,使用优化特技。

下面有几种方法可以提升你的页面性能,让我们来看看

关键渲染路径

提到页面渲染,有几个相关度非常高的概念,最重要的是关键渲染路径,其他几个概念都可以从它展开,下面稍作说明。

关键渲染路径(Critical Rendering Path)是指与当前用户操作有关的内容。例如用户刚刚打开一个页面,首屏的显示就是当前用户操作相关的内容,具体就是浏览器收到 HTML、CSS 和 JavaScript 等资源并对其进行处理从而渲染出 Web 页面。

了解浏览器渲染的过程与原理,很大程度上是为了优化关键渲染路径,但优化应该是针对具体问题的解决方案,所以优化没有一定之规。例如为了保障首屏内容的最快速显示,通常会提到渐进式页面渲染,但是为了渐进式页面渲染,就需要做资源的拆分,那么以什么粒度拆分、要不要拆分,不同页面、不同场景策略不同。具体方案的确定既要考虑体验问题,也要考虑工程问题。

构建模块

HTML 描述了一个页面的结构。为了理解 HTML,浏览器首先会将HTML转换成其能够理解的一种格式 – 文档对象模型(Document Object Model) 或者简称为 DOM。 浏览器引擎有这么一段特殊的代码叫做解析器,用来将数据从一种格式转换成另外一种格式。一个 HTML 解析器就能将数据从 HTML 转换到 DOM。

在 HTML 当中,嵌套(nesting)定义了不同标签的父子关系。在 DOM 当中,对象被关联在树(一种数据结构)中用于捕获这些关系。每一个 HTML 标签都对应着树种的某个节点(DOM节点)。

浏览器一个比特一个比特地构建 DOM。一旦第一个代码块加载到浏览器当中,它就开始解析 HTML,添加节点到树中。

图片 3

DOM 扮演着两种角色:它既是 HTML 文档的对象表示,也充当着外界(比如JavaScript)和页面交互的接口。 当你调用 document.getElementById(),返回的元素是一个 DOM 节点。每个 DOM 节点都有很多函数可以用来访问和改变它,用户可以看到相应的变化。

图片 4

页面上的 CSS 样式被映射到 CSSOM 上 – CSS 对象模型(CSS Object Model)。它就像 DOM,但是只针对于 CSS 而不是 HTML。不像 DOM,它不能增量地构建。因为 CSS 规则会相互覆盖,所以浏览器引擎要进行复杂的计算来确定 CSS 代码如何应用到 DOM 上。

图片 5

速度指标

速度指标是指页面的可视部分被呈现在浏览器中的平均速度。表示为毫秒的形式,并且取决于viewport的大小。请看下图(用视频帧的形式展现页面加载时间,以秒为单位)。

速度指标越低越好。

图片 6

速度指标可以通过Webpagetest 来测试(由Google维护)

浏览器渲染页面的过程

从耗时的角度,浏览器请求、加载、渲染一个页面,时间花在下面五件事情上:

  1. DNS 查询
  2. TCP 连接
  3. HTTP 请求即响应
  4. 服务器响应
  5. 客户端渲染

本文讨论第五个部分,即浏览器对内容的渲染,这一部分(渲染树构建、布局及绘制),又可以分为下面五个步骤:

  1. 处理 HTML 标记并构建 DOM 树。
  2. 处理 CSS 标记并构建 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个渲染树。
  4. 根据渲染树来布局,以计算每个节点的几何信息。
  5. 将各个节点绘制到屏幕上。

需要明白,这五个步骤并不一定一次性顺序完成。如果 DOM 或 CSSOM 被修改,以上过程需要重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染。实际页面中,CSS 与 JavaScript 往往会多次修改 DOM 和 CSSOM,下面就来看看它们的影响方式。

关于``标签的历史

当浏览器构建 DOM 的时候,如果在 HTML 中遇到了一个 ``标签,它必须立即执行。如果脚本是来自于外部的,那么它必须首先下载脚本。

在过去,为了执行一个脚本,HTML 的解析必须暂停。只有在 JavaScript 引擎执行完代码之后它才会重新开始解析。

图片 7

那位为什么解析必须要暂停呢?那是因为脚本可以改变 HTML以及它的产物 —— DOM。 脚本可以通过 document.createElement()方法添加节点来改变 DOM 结构。为了改变 HTML,脚本可以使用臭名昭著的document.write()方法来添加内容。它之所以臭名昭著是因为它能以进一步影响 HTML 解析的方式来改变 HTML。比如,该方法可以插入一个打开的注释标签来使得剩余的 HTML 都变得不合法。

图片 8

脚本还可以查询关于 DOM 的一些东西,如果是在 DOM 还在在构建的时候,它可能会返回意外的结果。

图片 9

document.write() 是一个遗留的方法,它能够以预料之外的方式破坏你的页面,你应该避免使用它。处于这些原因,浏览器开发出了一些复杂的方法来应对脚本阻塞导致的性能问题,稍后我会解释。

长话短说

Webpage test 有很多特性,比如在不同的地方用不同的浏览器跑多个测试。 还可以测算其他的数据比如加载时间,dom元素的数量,首字节时间等等…

例如:查看amazon在webpagetest上的测试结果 。

可以看看这个视频,了解由 Patrick Meenan 讲解的关于webpagetest的更多信息(需要梯子)。

阻塞渲染:CSS 与 JavaScript

谈论资源的阻塞时,我们要清楚,现代浏览器总是并行加载资源。例如,当 HTML 解析器(HTML Parser)被脚本阻塞时,解析器虽然会停止构建 DOM,但仍会识别该脚本后面的资源,并进行预加载。

同时,由于下面两点:

  1. 默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。
  2. JavaScript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性。

存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建。另外:

  1. 当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。
  2. JavaScript 可以查询和修改 DOM 与 CSSOM。
  3. CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。

所以,script 标签的位置很重要。实际使用时,可以遵循下面两个原则:

  1. CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。
  2. JavaScript 应尽量少影响 DOM 的构建。

浏览器的发展日益加快(目前的 Chrome 官方稳定版是 61),具体的渲染策略会不断进化,但了解这些原理后,就能想通它进化的逻辑。下面来看看 CSS 与 JavaScript 具体会怎样阻塞资源。

那么 CSS 会阻塞页面吗 ?

JavaScript 阻塞页面解析是因为它可以修改文档。CSS 不能修改文档,所以看起来它没有理由去阻塞页面解析,对吗?

那么,如果脚本需要样式信息,但样式还没有被解析呢?浏览器并不知道脚本要怎么执行——它可能会需要类似 DOM 节点的background-color 属性,而这个属性又依赖于样式表,或者它期望能够直接访问 CSSOM。

图片 10

正因为如此,CSS 可能会阻塞解析,取决于外部样式表和脚本在文档中的顺序。如果在文档中外部样式表放置在脚本之前,DOM 对象和 CSSOM 对象的构建可以互相干扰。 当解析器获取到一个 script 标签,DOM 将无法继续构建直到 JavaScript 执行完毕,而 JavaScript 在 CSS 下载完,解析完,并且 CSSOM 可以使用的时候,才能执行。

图片 11

另外一件要注意的事是,即使 CSS 不阻塞 DOM 的构建,它也会阻塞 DOM 的渲染。直到 DOM 和 CSSOM 准备好之前,浏览器什么都不会显示。这是因为页面没有 CSS 通常无法使用。如果一个浏览器给你显示了一个没有 CSS 的凌乱的页面,而几分钟之后又突然变成了一个有样式的页面,变换的内容和突然视觉变化使得用户体验变得非常糟糕。

具体可以参考由 Milica (@micikato) 在 CodePen 上制作的例子 —— Flash of Unstyled Content。

这种糟糕的用户体验有一个名字 — Flash of Unstyled Content 或是 FOUC

为了避免这个问题,你应该尽快地呈现 CSS。记得流行的“样式放顶部,脚本放底部”的最佳实践吗?你现在知道它是怎么来的了!

渲染阻塞

如果你知道浏览器如何运行,那么你应该知道HTML, CSS, JS是怎么被浏览器解析的以及其中哪个阻塞了页面的渲染。如果你不知道,请看下图。

图片 12

点击how a browser works了解更多浏览器工作原理(作者为Tali Garsiel 和Paul Irish).

CSS

JavaScript

<style> p { color: red; }</style> <link rel="stylesheet" href="index.css">

1
2
<style> p { color: red; }</style>
<link rel="stylesheet" href="index.css">

这样的 link 标签(无论是否 inline)会被视为阻塞渲染的资源,浏览器会优先处理这些 CSS 资源,直至 CSSOM 构建完毕。

渲染树(Render-Tree)的关键渲染路径中,要求同时具有 DOM 和 CSSOM,之后才会构建渲染树。即,HTML 和 CSS 都是阻塞渲染的资源。HTML 显然是必需的,因为包括我们希望显示的文本在内的内容,都在 DOM 中存放,那么可以从 CSS 上想办法。

最容易想到的当然是精简 CSS 并尽快提供它。除此之外,还可以用媒体类型(media type)和媒体查询(media query)来解除对渲染的阻塞。

JavaScript

<link href="index.css" rel="stylesheet"> <link href="print.css" rel="stylesheet" media="print"> <link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)">

1
2
3
<link href="index.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 30em) and (orientation: landscape)">

第一个资源会加载并阻塞。
第二个资源设置了媒体类型,会加载但不会阻塞,print 声明只在打印网页时使用。
第三个资源提供了媒体查询,会在符合条件时阻塞渲染。

回到未来 – 预解析(speculative parsing)

每当解析器遇到一个脚本就暂停意味着每个你加载的脚本都会推迟发现链接到 HTML 的其他资源。

如果你有几个类似的脚本和图片要加载,例如:

<script src="slider.js"></script> <script src="animate.js"></script> <script src="cookie.js"></script> <img src="slide1.png"> <img src="slide2.png">

1
2
3
4
5
  <script src="slider.js"></script>
  <script src="animate.js"></script>
  <script src="cookie.js"></script>
  <img src="slide1.png">
  <img src="slide2.png">

这个过程过去是这样的:

图片 13

这个状况在 2008 年左右改变了,当时 IE 引入了一个概念叫做 “先行下载”。 这是一种在同步的脚步执行的时候保持文件的下载的一种方法。Firefox,Chrome 和 Safari 随后效仿,如今大多数的浏览器都使用了这个技术,它们有着不同的名称。Chrome 和 Safari 称它为 “预扫描器” 而 Firefox 称它为预解析器。

它的概念是:虽然在执行脚本时构建 DOM 是不安全的,但是你仍然可以解析 HTML 来查看其它需要检索的资源。找到的文件会被添加到一个列表里并开始在后台并行地下载。当脚本执行完毕之后,这些文件很可能已经下载完成了。

上面例子的瀑布图现在看起来是这样的:

图片 14

以这种方式触发的下载请求称之为 “预测”,因为很有可能脚本还是会改变 HTML 结构(还记得document.write吗?),导致了预测的浪费。虽然这是有可能的,但是却不常见,所以这就是为什么预解析仍然能够带来很大的性能提升。

而且其他浏览器只会对链接的资源进行这样的预加载。在 Firefox 中,HTML 解析器对 DOM 树的构建也是算法预测的。有利的一面是,当推测成功的时候,就没有必要重新解析文件的一部分了。缺点是,如果推测失败了,就需要更多的工作。

浏览器渲染的步骤

  1. 首先浏览器解析HTML标记去构造DOM树(DOM = Document Object Model 文档对象模型)
  2. 然后解析CSS去构造CSSOM树( CSSOM = CSS Object Model CSS对象模型)
  3. 在将DOM和CSSOM树结合成渲染树之前,JS文件被解析和执行。

现在你知道浏览器如何进行解析了,让我们看看是哪一部分阻塞了渲染树的生成。

JavaScript

JavaScript 的情况比 CSS 要更复杂一些。观察下面的代码:

JavaScript

<p>Do not go gentle into that good night,</p> <script>console.log("inline")</script> <p>Old age should burn and rave at close of day;</p> <script src="app.js"></script> <p>Rage, rage against the dying of the light.</p> <p>Do not go gentle into that good night,</p> <script src="app.js"></script> <p>Old age should burn and rave at close of day;</p> <script>console.log("inline")</script> <p>Rage, rage against the dying of the light.</p>

1
2
3
4
5
6
7
8
9
10
11
<p>Do not go gentle into that good night,</p>
<script>console.log("inline")</script>
<p>Old age should burn and rave at close of day;</p>
<script src="app.js"></script>
<p>Rage, rage against the dying of the light.</p>
 
<p>Do not go gentle into that good night,</p>
<script src="app.js"></script>
<p>Old age should burn and rave at close of day;</p>
<script>console.log("inline")</script>
<p>Rage, rage against the dying of the light.</p>

这样的 script 标签会阻塞 HTML 解析,无论是不是 inline-script。上面的 P 标签会从上到下解析,这个过程会被两段 JavaScript 分别打算一次(加载、执行)。

所以实际工程中,我们常常将资源放到文档底部。

关于(预)加载

这种资源加载的方式带来了显著地性能提升,你不需要做任何事情就可以使用这种优势。然而,作为一个 web 开发者,了解预解析是如何工作的能帮你最大程度地利用它。

可以预加载的东西在浏览器之间有所不同,但所有的主要的浏览器都会预加载:

  • 脚本
  • 外部 CSS
  • 来自 img 标签的图片

Firefox 也会预加载 video 元素的 poster 属性,而 Chrome 和 Safari 会预加载 @import 规则的内联样式。

浏览器能够并行下载的文件的数量是有限制的。这个限制在不同浏览器之间是不同的,并且取决于不同的因素,比如:你是否从同一个服务器或是不同的服务器下载所有的文件,又或者是你使用的是 HTTP/1.1 或是 HTTP/2 协议。为了更快地渲染页面,浏览器对每个要下载的文件都设置优先级来优化下载。为了弄清这些的优先级,他们遵守基于资源类型、标记位置以及页面渲染的进度的复杂方案。

在进行预解析时,浏览不会执行内联的 JavaScript 代码块。这意味着它不会发现任何的脚本注入资源,这些资源会排到抓取队列的最后面。

var script = document.createElement('script'); script.src = "//somehost.com/widget.js"; document.getElementsByTagName('head')[0].appendChild(script);

1
2
3
4
var script = document.createElement('script');
script.src = "//somehost.com/widget.js";
document.getElementsByTagName('head')[0].appendChild(script);
 

你应该尽可能使浏览器能更轻松访问到重要的资源。你可以把他们放到 HTML 标签当中或者将要加载的脚本内联到文档的前面。然而,有时候需要一些不重要的资源晚一点被加载。这种情况,你通过 JavaScript 来加载他们来避免预解析。

你也可以看看这个 MDN 指南,里面讲述了如何针对预解析优化你的页面。

1. 阻塞渲染的CSS

有人认为CSS阻塞了渲染。在构造CSSOM时,所有的CSS都会被下载,无论它们是否在当前页面中被使用。

为了解决这个渲染阻塞,跟着下面的两个步骤做

  1. 将关键CSS内嵌入页面中,即将最重要的(首次加载时可见的部分页面所使用到的)style写入head中的 <style></style>里。
  2. 移除没用到的CSS。

那么我是如何找出没用到的CSS的呢。

  1. 使用Pagespeed Insight 去得到像未使用的CSS,阻塞渲染的CSS和JS文件等等的统计数据。例如:Flipkart的Pagespeed Insight统计结果。
  2. 使用Gulp任务,如gulp-uncss或是使用Grunt 任务,如grunt-uncss。如果你不知道他们是什么,请阅读我之前的文章。

改变阻塞模式:defer 与 async

为什么要将 script 加载的 defer 与 async 方式放到后面呢?因为这两种方式是的出现,全是由于前面讲的那些阻塞条件的存在。换句话说,defer 与 async 方式可以改变之前的那些阻塞情形。

首先,注意 async 与 defer 属性对于 inline-script 都是无效的,所以下面这个示例中三个 script 标签的代码会从上到下依次执行。

JavaScript

<!-- 按照从上到下的顺序输出 1 2 3 --> <script async> console.log("1"); </script> <script defer> console.log("2"); </script> <script> console.log("3"); </script>

1
2
3
4
5
6
7
8
9
10
<!-- 按照从上到下的顺序输出 1 2 3 -->
<script async>
  console.log("1");
</script>
<script defer>
  console.log("2");
</script>
<script>
  console.log("3");
</script>

故,下面两节讨论的内容都是针对设置了 src 属性的 script 标签。

defer 和 async

不过,同步的脚本阻塞解析器仍旧是个问题。并不是所有的脚本对用户体验都是同等的重要,例如那些用于监测和分析的脚本。解决方法呢?就是去尽可能地异步加载这些不那么重要的脚本。

deferasync 属性 提供给开发者一个方式来告诉浏览器哪些脚本是需要异步加载的。

这两个属性都告诉浏览器,它可以 “在后台” 加载脚本的同时继续解析 HTML,并在脚本加载完之后再执行。这样,脚本下载就不会阻塞 DOM 构建和页面渲染了。结果就是,用户可以在所有的脚本加载完成之前就能看到页面。

deferasync 之间的不同是他们开始执行脚本的时机的不同。

deferasync 要先引入浏览器。它的执行在解析完全完成之后才开始,它处在DOMContentLoaded事件之前。 它保证脚本会按照它在 HTML 中出现的顺序执行,并且不会阻塞解析。

图片 15

async 脚本在它们完成下载完成后的第一时间执行,它处在 window 的load 事件之前。 这意味着有可能(并且很有可能)设置了 async 的脚本不会按照它们在 HTML 中出现的顺序执行。这也意味着他们可能会中断 DOM 的构建。

无论它们在何处被指定,设置async 的脚本的加载有着较低的优先级。他们通常在所有其他脚本加载之后才加载,而不阻塞 DOM 构建。然而,如果一个指定async 的脚本很快就完成了下载,那么它的执行会阻塞 DOM 构建以及所有在之后才完成下载的同步脚。

图片 16

注: async 和 defer 属性只对外部脚本起作用,如果没有 src 属性它们会被忽略。

##专业小贴士

  1. 使用CSS Stats保证页面中完全没有未被用到的元素,唯一的样式和字体等等。
  2. Pagespeed Insight Chrome 插件.
  3. Tag Counter Chrome 插件.

defer

JavaScript

<script src="app1.js" defer></script> <script src="app2.js" defer></script> <script src="app3.js" defer></script>

1
2
3
<script src="app1.js" defer></script>
<script src="app2.js" defer></script>
<script src="app3.js" defer></script>

defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的。整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件。

defer 不会改变 script 中代码的执行顺序,示例代码会按照 1、2、3 的顺序执行。所以,defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后。

preload

如果你想要延迟处理一些脚本,那么asyncdefer 非常棒。那网页上那些对用户体验至关重要的东西呢?预解析器很方便,但是它们只会预加载少数类型的资源并遵循其逻辑。通常的目的都是首先交付 CSS,因为它会阻塞渲染。同步的脚本总是比异步的脚本拥有更高的优先级。视口中可见的图像会比那些底下的图片先下载完。还有字体,视频,SVG… 总而言之 — 这个过程很复杂。

作为作者,你知道哪些资源对你的页面渲染来说是最重要的。它们其中一些经常深藏在 CSS 或者是脚本当中,甚至浏览器需要花上很长一段时间才会发现他们。对于那些重要的资源,你现在可以使用`` 来告诉浏览器你需要尽快地加载它们。

你只需要写上:

<link rel="preload" href="very_important.js" as="script">

1
  <link rel="preload" href="very_important.js" as="script">

你几乎可以链接到任何东西上,as 属性告诉浏览器要下载的是什么。一些可能的值是:

  • script
  • style
  • image
  • font
  • audio
  • video

你可以在MDN上查看剩余的内容类型。

字体可能是隐藏在CSS中最重要的东西。它们对页面上文字的渲染非常地关键,但是它们知道浏览器确认它们会被使用之前都不会被加载。 这个检查只发生在 CSS 已经被解析,应用,并且浏览器已经将 CSS 规则匹配到对应的 DOM 节点上时。这个过程在页面加载的过程中发生的相当晚,并且常常导致文字渲染中不必要的延迟。你可以通过使用 preload 属性来避免。

有一点要注意,要预加载字体你还必须设置crossorigin 属性,即使字体在同一个域名下:

<link rel="preload" href="font.woff" as="font" crossorigin>

1
  <link rel="preload" href="font.woff" as="font" crossorigin>

preload 特性目前只有有限的支持度,因为其他浏览器还在推出它的过程中。你可以在这里查看进度。

2. 渲染阻塞的JavaScript

如果在解析HTML标记时,浏览器遇到了JavaScript,解析会停止。只有在该脚本执行完毕后,HTML渲染才会继续进行。所以这阻塞了页面的渲染。

为了解决它

在<script></script>标签中使用 async或defer特性。

  1. <script async>将会在HTML解析时下载该文件并在下载完成后马上执行。
  2. <script defer> 将会在HTML解析式下载该文件并在HTML解析完成后执行。

例如: async and defer都在Google Analytics中使用

点击查看async 和defer的浏览器支持。

async

JavaScript

<script src="app.js" async></script> <script src="ad.js" async></script> <script src="statistics.js" async></script>

1
2
3
<script src="app.js" async></script>
<script src="ad.js" async></script>
<script src="statistics.js" async></script>

async 属性表示异步执行引入的 JavaScript,与 defer 的区别在于,如果已经加载好,就会开始执行——无论此刻是 HTML 解析阶段还是 DOMContentLoaded 触发之后。需要注意的是,这种方式加载的 JavaScript 依然会阻塞 load 事件。换句话说,async-script 可能在 DOMContentLoaded 触发之前或之后执行,但一定在 load 触发之前执行。

从上一段也能推出,多个 async-script 的执行顺序是不确定的。值得注意的是,向 document 动态添加 script 标签时,async 属性默认是 true,下一节会继续这个话题。

结论

浏览器是自 90 年代以来一直在进化的极其复杂的野兽。我们已经讨论了一些遗留问题以及 Web 开发中的一些最新标准。根据这些指南书写你的代码能够帮助你选择最好的策略来提供更加流畅的浏览器体验。

如果你想了解更多关于浏览器的工作原理,你可以查看其他的文章:

走进 Quantum : 什么是浏览器引擎?

深入了解一个超级快的 CSS 引擎: Quantum CSS (也称作 Stylo)

1 赞 1 收藏 评论

图片 17

内存泄漏

内存泄漏和页面臃肿 是前端开发者所要面对的问题之一。让我们来看看如何发现并解决内存泄漏。

在JavaScript中寻找内存泄漏

使用Chrome Task Manager(任务管理器)去检测app所使用的内存以及js内存(总体内存+实时内存)。如果你的内存一直随着你的每次操作而提高,那么你可以怀疑有内存泄漏了。

下面是Chrome Task Manager的截图。

图片 18

document.createElement

使用 document.createElement 创建的 script 默认是异步的,示例如下。

JavaScript

console.log(document.createElement("script").async); // true

1
console.log(document.createElement("script").async); // true

所以,通过动态添加 script 标签引入 JavaScript 文件默认是不会阻塞页面的。如果想同步执行,需要将 async 属性人为设置为 false。

如果使用 document.createElement 创建 link 标签会怎样呢?

JavaScript

const style = document.createElement("link"); style.rel = "stylesheet"; style.href = "index.css"; document.head.appendChild(style); // 阻塞?

1
2
3
4
const style = document.createElement("link");
style.rel = "stylesheet";
style.href = "index.css";
document.head.appendChild(style); // 阻塞?

其实这只能通过试验确定,已知的是,Chrome 中已经不会阻塞渲染,Firefox、IE 在以前是阻塞的,现在会怎样我没有试验。

Chrome DevTools分析

使用 Heap Profiler 去查看内存泄漏。打开Chrome devTools 然后点击profiles 标签,接着选中 take heap snapshot。如果你不了解Chrome DevTools,请阅读之前的文章.

图片 19

Heap Profiler有四个快照视图(snapshot view)

  1. Summary 视图 – 展示对象的总体数量以及它们的实例总数,浅部(Shallow)大小(对象本身的内存大小)以及保留(Retained)大小(自动GC发生后所释放的内存大小+无法执行到的对象的内存大小)。
  2. Comparison 视图- 用于比较一个操作的前后的两个或多个快照,可以检测内存泄漏。
  3. Containment 视图- 展示了你的app对象架构的整体视图 + DOMWindow 对象(全局对象下的), GC 根部, 本地对象 (来自浏览器)。
  4. Dominators 视图- 展示了 dominators 树的堆图。

点击了解更多 Heap profiler。

document.write 与 innerHTML

通过 document.write 添加的 link 或 script 标签都相当于添加在 document 中的标签,因为它操作的是 document stream(所以对于 loaded 状态的页面使用 document.write 会自动调用 document.open,这会覆盖原有文档内容)。即正常情况下, link 会阻塞渲染,script 会同步执行。不过这是不推荐的方式,Chrome 已经会显示警告,提示未来有可能禁止这样引入。如果给这种方式引入的 script 添加 async 属性,Chrome 会检查是否同源,对于非同源的 async-script 是不允许这么引入的。

如果使用 innerHTML 引入 script 标签,其中的 JavaScript 不会执行。当然,可以通过 eval() 来手工处理,不过不推荐。如果引入 link 标签,我试验过在 Chrome 中是可以起作用的。另外,outerHTML、insertAdjacentHTML() 应该也是相同的行为,我并没有试验。这三者应该用于文本的操作,即只使用它们添加 text 或普通 HTML Element。

DOM泄漏

对DOM元素的引用会导致DOM泄漏并且阻碍自动垃圾回收(GC)的进行。

来看一个例子

XHTML

<div> <div id="container"> <h1 id="heading">I am just a heading nothing much</h1> </div> </div>

1
2
3
4
5
<div>
    <div id="container">
        <h1 id="heading">I am just a heading nothing much</h1>
    </div>
</div>

JavaScript

var parentEle = document.getElementById('container'); //get parent ele reference 得到父元素的引用 var headingEle = document.getElementById('heading'); //get child ele reference 得到子元素的引用 parentEle.remove(); //removes parent element from DOM 从DOM中移除父元素 //but its child ref still exist, So parentEle won't collect GC'd and causes DOM Leak //但是它的子元素引用仍然存在,所以parentEle不会被GC回收,因此导致了DOM泄漏。

1
2
3
4
5
6
7
8
var parentEle = document.getElementById('container'); //get parent ele reference 得到父元素的引用
 
var headingEle = document.getElementById('heading'); //get child ele reference 得到子元素的引用
 
parentEle.remove(); //removes parent element from DOM 从DOM中移除父元素
 
//but its child ref still exist, So parentEle won't collect GC'd and causes DOM Leak
//但是它的子元素引用仍然存在,所以parentEle不会被GC回收,因此导致了DOM泄漏。

将它的引用设置为null即可修复DOM泄漏。

JavaScript

headingEle = null; //Now parentEle will be GC'd

1
headingEle = null; //Now parentEle will be GC'd

上面就是前端开发者常遇到的问题。今天就讲到这。如果你喜欢我的文章,请分享或者在下面评论。谢谢!!

2 赞 6 收藏 评论

参考资料

Mobile Analysis in PageSpeed Insights

Web Fundamentals

MDN – HTML element reference

1 赞 4 收藏 2 评论

图片 20

关于作者:鸭梨山大

图片 21

Life hacker 个人主页 · 我的文章 · 14

图片 22

本文由9159.com发布于前端,转载请注明出处:   译文出处

关键词:

上一篇:没有了
下一篇:没有了