HTTP Requests转载
网页构成所需的内容 —— 文本、图形、样式、脚本,等这些所有内容 —— 都必须通过 HTTP 请求从服务器下载。 毫不夸张地说,页面总显示的绝大部分时间都花在了下载其组件上,而不是实际"显示"上。 到目前为止,我们已经讨论了通过压缩图像、缩小 CSS 和 JavaScript、压缩文件等来减少这些下载的大小。 但是还有一个更基本的方法:除了减少下载大小之外,我们还可以考虑减少下载频率。
减少页面所需的组件数量会成比例地减少它必须发出的 HTTP 请求的数量。 然而这并不意味着需要去删除内容,而是如何更有效地去构建。
合并文本资源
许多网页会使用多个样式表和脚本文件。 当我们开发页面并添加格式和行为代码时,我们经常会将不同的代码分散在单独的文件中,以保持清晰和易于维护。 或者,我们可能会将自己的样式表与市场部要求我们使用的样式表分开。 又或者,我们可能会在测试期间将实验规则或脚本放在单独的文件中。 这些都是拥有多个资源文件的正当原因。
但是因为每个文件都会发起HTTP请求,而且每个请求都需要时间,所以我们可以通过合并文件来加快页面加载速度; 一个请求相比三个或四个请求,肯定会节省时间。 乍一看,这似乎很简单——只需将所有 CSS(例如)放入主样式表中,然后从页面中删除除此之外的其他所有 CSS。以这种方式删除的每个额外文件都会删除一个 HTTP 请求,并节省往返时间。这儿会有一些 CSS 文件合并的警告。
对于级联样式表,请注意“级联”。 级联优先级允许后面的规则在没有警告的情况下覆盖前面的规则——字面意思。当先前定义的属性被更新的规则重置时,CSS不会抛出错误,因此将样式表放在一起可能会自找麻烦。 相反,寻找冲突的规则并确定这些规则是否应该总是取代另一个,或者是否应该使用更具体的选择器来正确应用。 例如,考虑如下这两个简单的规则,第一个来自主样式表,第二个来自 市场部 提供的样式表。
h2 { font-size: 1em; color: #000080; } /* primary stylesheet */
. . .
h2 { font-size: 2em; color: #ff0000; } /* Marketing stylesheet */
市场营销希望他们的 h2s 突出,而你的 h2s 则更柔和。 但由于级联顺序,它们的规则优先,页面中的每个 h2 都会变大变红。很明显,你不能只是简单的颠倒一下样式表的顺序,否则你还是会遇到同样的问题。
一些研究可能表明 Marketing 的 h2s 总是出现在特定类别的部分中,因此对第二条规则的选择器进行调整可以解决冲突。 Marketing 引人注目的 h2 仍然看起来完全符合他们的要求,但不会影响页面其他地方的 h2。
h2 { font-size: 1em; color: #000080; }
. . .
section.product h2 { font-size: 2em; color: #ff0000; }
在组合 JavaScript 文件时,您可能会遇到类似的情况。 完全不同的函数可能具有相同的名称,或者名称相同的变量可能具有不同的范围和用途。 如果您积极寻找它们,这些都不是不可克服的障碍。
组合文本资源以减少 HTTP 请求是值得的,但这样做时要小心。 请参阅下面的警告。
合并图像资源
从表面上看,这种技术听起来有点荒谬。 当然,将多个 CSS 或 JavaScript 资源组合到一个文件中是合乎逻辑的,但是图像呢? 实际上,它相当简单,减少 HTTP 请求数量的效果与组合文本资源的效果相同——有时甚至更显著。
尽管这种技术可以应用于任何图像组,但它最常用于图标等小图像,其中额外的 HTTP 请求来获取多个小图形特别浪费。
其基本思想是将小图像组合成一个物理图像文件,然后使用 CSS 背景定位在页面的正确位置仅显示图像的右侧部分(通常称为精灵或雪碧图)。 CSS 重新定位快速且无缝,适用于已下载的资源,并为原本需要的多个 HTTP 请求和图像下载做出了很好的权衡。
例如,您可能有一系列社交媒体图标,其中包含指向其各自网站或应用程序的链接。 您可以像这样将它们组合成一个图像文件,而不是下载三个(或更多)单独的图像。
然后,不要为链接使用不同的图像,只需检索整个图像一次,并为每个链接使用 CSS 背景定位(“精灵”)以显示链接图像的正确部分。
CSS 规则如下:
a.facebook {
display: inline-block;
width: 64px; height: 64px;
background-image: url("socialmediaicons.png");
background-position: 0px 0px;
}
a.twitter {
display: inline-block;
width: 64px; height: 64px;
background-image: url("socialmediaicons.png");
background-position: -64px 0px;
}
a.pinterest {
display: inline-block;
width: 64px; height: 64px;
background-image: url("socialmediaicons.png");
background-position: -128px 0px;
}
请注意 facebook class 中无关的 background-position
属性。 这不是必需的,因为默认位置是 0,0,但为了保持一致而包含在此处。 其他两个类只是简单地将图像相对于其容器水平向左移动 64 像素和 128 像素,使相应的图像部分在 64×64 像素链接“窗口”中可见。
HTML 和 CSS 代码如下:
<p>Find us on:</p>
<p><a class="facebook" href="https://facebook.com"></a></p>
<p><a class="twitter" href="https://twitter.com"></a></p>
<p><a class="pinterest" href="https://pinterest.com"></a></p>
无需在链接本身中包含单独的图像,只需应用 CSS 类并将链接内容留空。 这种简单的技术通过让 CSS 在幕后进行图像移动,为您节省了两个 HTTP 请求。 如果您有很多小图像——例如导航图标或功能按钮——它可以为您节省很多次访问服务器的次数。
您可以在 WellStyled 上找到一篇关于此技术的简短但出色的文章,包括示例。
警告
在我们对结合文本和图形的讨论中,我们应该注意到较新的 HTTP/2 协议可能会改变您考虑合并资源的方式。 例如,缩小、服务器压缩和图像优化等常见且有价值的技术应该在 HTTP/2 上继续使用。 但是,如上讨论的物理组合文件可能无法在 HTTP/2 上达到预期的结果。
这主要是因为服务器请求在 HTTP/2 上更快,因此合并文件以消除请求可能不会显著提高效率。 此外,如果您将一个静态的资源与一个动态的资源结合起来保存请求,您可能会因为强制重新加载资源的静态部分以获取动态部分而对缓存效率产生不利影响。
在这种情况下,HTTP/2 的特性和好处值得探索。
JavaScript 定位和内联推送
到目前为止,我们假设所有 CSS 和 JavaScript 资源都存在于外部文件中,这通常是交付它们的最佳方式。 请记住,脚本加载是一个大而复杂的问题——请参阅这篇很棒的 HTML5Rocks 文章,Deep Dive Into the Murky Waters of Script Loading。 然而,有两个关于 JavaScript 的相当直接的位置因素值得考虑。
脚本位置
常见的约定是将脚本放在页面head中引入。 这种定位带来的问题在于,通常在页面显示之前几乎没有脚本真正打算执行,但是在加载时,它会不必要地阻塞页面渲染。 识别渲染阻止脚本是 PageSpeed Insights 的报告规则之一。
一个简单有效的解决方案是在页面末尾重新定位延迟脚本块。 也就是说,将脚本引用放在最后,就在结束 body 标记之前。 这允许浏览器加载和呈现页面内容,然后在用户感知初始内容时让它下载脚本。 例如:
<html>
<head>
</head>
<body>
[Body content goes here.]
<script src="mainscript.js"></script>
</body>
</html>
这种技术的一个例外是任何在页面渲染之前或过程中需要操纵初始内容或 DOM 或在呈现之前提供所需页面功能的脚本。诸如此类的关键脚本可以像往常一样放在单独的文件中并加载到页头中,其余的仍然可以放在页面中的最后一个内容,以便仅在页面渲染后才加载。
为获得最大效率而必须加载资源的顺序称为关键渲染路径; 您可以在 Bits of Code 中找到有关它的详尽文章。
代码位置
当然,上述技术将您的 JavaScript 拆分为服务器上的两个文件,因此需要两个 HTTP 请求而不是一个,这正是我们试图避免的情况。 重新定位关键的预渲染脚本的更好解决方案可能是将它们直接放置在页面本身内,称为“内联推送”。
在这里,没有将关键脚本放在单独的文件并在head中引用,而是在head或正文中添加 <script>...</script> 块,然后插入脚本本身(不是 文件引用,而是实际的脚本代码)在需要它的地方。 假设脚本不是太大,该方法将脚本与 HTML 一起加载并立即执行,并避免将其放在页眉中的额外 HTTP 请求开销。
例如,如果返回用户的姓名已经可用,您可能希望通过调用 JavaScript 函数尽快将其显示在页面中,而不是等到所有内容加载完毕。
<p>Welcome back, <script>insertText(username)</script>!</p>
或者您可能需要在页面加载时就地执行整个函数,以便正确呈现某些内容。
<h1>Our Site</h1>
<h2 id="greethead">, and welcome to Our Site!</h2>
<script>
//insert time of day greeting from computer time
var hr = new Date().getHours();
var greeting = "Good morning";
if (hr > 11) {
greeting = "Good afternoon";
}
if (hr > 17) {
greeting = "Good evening";
}
h2 = document.getElementById("greethead");
h2.innerHTML = greeting + h2.innerHTML;
</script>
<p>Blah blah blah</p>
这种简单的技术避免了单独的 HTTP 请求来检索少量代码,并允许脚本立即在页面中的适当位置运行,而只需在 HTML 页面中增加几十个字节。
小结
在本节中,我们介绍了减少页面发出的 HTTP 请求数量的方法,并考虑了文本和图形资源的技术。 我们可以避免的每次往返服务器都可以节省时间,加快页面加载速度,并更快地将其内容提供给我们的用户。