原文連結(需越牆):developers.google.com/web/fundame…
原文作者:Dave Gash 譯者:西樓聽雨
譯註:此作是谷歌開發者網站關於Web效能優化的入門系列文章之一,該系列的其他篇章,及對應的高階系列文章,本人後續會持續釋出,歡迎大家關注。(轉載請註明出處)
Everything a web page needs to be a web page -- text, graphics, styles, scripts, everything -- must be downloaded from a server via an HTTP request. It's no stretch to say that the vast majority of a page's total display time is spent in downloading its components, not in actually displaying them. So far, we've talked about reducing the size of those downloads by compressing images, minifying CSS and JavaScript, zipping the files, and so on. But there's a more fundamental approach: in addition to just reducing download size, let's also consider reducing download frequency.
一張網頁所需要的所有東西——文字,影像,樣式,指令碼,等等——一定都是通過HTTP請求下載下來的。無可辯駁地說,呈現一張網頁的耗時絕大多數都是用在下載這些部件的過程中,而不是實際展示他們所需的時間。目前為止,我們討論減少下載這些檔案的體積,都是在說如何壓縮圖片,緊湊(minification) CSS 和 Javascript ,並打包(zipping)這些檔案,等等。但實際上還有更接近底層的方式:除了減小下載大小,我們還可以考慮降低下載次數。
Reducing the number of components that a page requires proportionally reduces the number of HTTP requests it has to make. This doesn't mean omitting content, it just means structuring it more efficiently.
減少一張網頁所需的部件數量,可以適當地減少HTTP的請求次數。但這是並不是說讓你丟棄一些內容,而是以更高效的方式來編排這些部件。
合併文字資源(Combine Text Resources)
Many web pages use multiple stylesheets and script files. As we develop pages and add useful formatting and behavior code to them, we often put pieces of the code into separate files for clarity and ease of maintenance. Or we might keep our own stylesheet separate from the one the Marketing Department requires us to use. Or we might put experimental rules or scripts in separate files during testing. Those are all valid reasons to have multiple resource files.
現在的許多網頁都會使用多個樣式檔案和指令碼檔案。隨著我們不斷地的開發頁面並往裡面新增需要的樣式和行為程式碼,我們經常會將一些程式碼塊放在一些單獨的檔案中,以確保程式碼清晰便於維護。比如,我們會將自己的樣式檔案和市場部要求的檔案單獨隔開;在測試的過程中,會將一些實驗性的樣式規則或者指令碼隔開。這些做法對於使用多個樣式檔案來說都是沒錯的。
But because each file requires its own HTTP request, and each request takes time, we can speed up page load by combining files; one request instead of three or four will certainly save time. At first blush, this seems like a no-brainer -- just put all the CSS (for example) into a master stylesheet and then remove all but one from the page. Every extra file you eliminate in this way removes one HTTP request and saves round-trip time. But there are caveats.
但是由於每個檔案都需要有自己的請求,而每次請求又需要一定的時間,因此我們可以將檔案合併來加速頁面的載入;一次請求而不是三四次請求,明顯地節約時間。雖然乍一看,這種操作顯得很無知——將所有CSS(這裡只是舉個例子,也可以是其他檔案)合併為一個大的樣式檔案並將其他檔案從頁面中移除。但是每個被移除的檔案都可以減少一次請求,進而減少伺服器來回時間。儘管如此,還是有一些需要注意的地方。
For Cascading Style Sheets, beware the "C". Cascade precedence allows later rules to override earlier ones without warning -- literally. CSS doesn't throw an error when a previously-defined property is reset by a more recent rule, so just tossing stylesheets together is asking for trouble. Instead, look for conflicting rules and determine whether one should always supercede the other, or if one should use more specific selectors to be applied properly. For example, consider these two simple rules, the first from a master stylesheet, the second imported from a stylesheet provided by Marketing.
對於“層疊樣式表”來說,要特別注意“層疊”。層疊的特性,會導致位於後面的樣式規則在無任何提示的情況下覆蓋掉前面的樣式規則。另外,當定義在前面的屬性被重置的時候,CSS也不會丟擲任何錯誤。所以,僅僅是簡單地將樣式檔案合併到一起是會引起一些麻煩的。除此之外,我們還需要查詢樣式規則的衝突,並決定他們之間的順序,或者判斷某個樣式規則是否應該使用更限定化的選擇器,以確保他們正常。例如下面程式碼中的兩條簡單的樣式規則,第一條來自主樣式表檔案,第二條是從市場部提供的樣式表檔案中匯入的。
h2 { font-size: 1em; color: #000080; } /* master stylesheet */
. . .
h2 { font-size: 2em; color: #ff0000; } /* Marketing stylesheet */
複製程式碼
Marketing wants their h2s to be prominent, while yours are meant to be more subdued. But due to the cascading order, their rule takes precedence and every h2 in the page will be big and red. And clearly, you can't just flip the order of the stylesheets or you'll have the same problem in reverse.
市場部希望他們的h2
們突出顯示,而你的則普通暗淡。但是因為層疊順序的原因,他們的規則獲得了更高的優先順序,這樣頁面上的每個h2
元素都會變大變成紅色。而且,你也無法僅僅通過調換這些檔案的順序就解決問題。
A little research might show that Marketing's h2s always appear inside a specific class of section, so a tweak to the second rule's selector resolves the conflict. Marketing's splashy h2s will still look exactly as they want, but without affecting h2s elsewhere in the page.
稍微做一點研究,你就會發現,市場部的h2
們總是位於某一個固定的 class 塊中,所以我們對第二條規則的選擇器做一點微調即可解決這個衝突。這樣,市場部的h2
們與之前他們想要的效果一樣,同時又不影響到頁面中其他地方的h2
們。
h2 { font-size: 1em; color: #000080; }
. . .
section.product h2 { font-size: 2em; color: #ff0000; }
複製程式碼
You may run into similar situations when combining JavaScript files. Completely different functions might have the same names, or identically-named variables might have different scopes and uses. These are not insurmountable obstacles if you actively look for them.
不過,即便在合併的是 JavaScript 檔案,你也可能會遇到同樣的問題。功能風馬牛不相及的函式可能有著相同的名稱;相同名字的變數卻有著不同的scope和用處。如果你認真排除,其實這些都不是無法避免的障礙。
Combining text resources to reduce HTTP requests is worth doing, but take care when doing so. See A Caveat below.
合併文字型別的資源可以減少HTTP請求,這是值得去做的事情,但在做的過程中也需要額外留意。請檢視後文中的“一個注意點”一節。
合併影像資源(Combine Graphical Resources)
On its face, this technique sounds a bit nonsensical. Sure, it's logical to combine multiple CSS or JavaScript resources into one file, but images? Actually, it is fairly simple, and it has the same effect of reducing the number of HTTP requests as combining text resources -- sometimes even more dramatically.
表面看來,這種技術聽上去有點不現實。對於將多個 CSS 或 JavaScript 資源合併為一個資源邏輯上當然是合理的,但圖片呢?實際上,圖片的合併也非常簡單,而且和文字資源的合併有同樣的效果——有時效果甚至更強烈。
Although this technique can be applied to any group of images, it is most frequently used with small images such as icons, where extra HTTP requests to fetch multiple small graphics are particularly wasteful.
雖然這種技術可以應用到任何一組圖片,但大多數時候更常被用於小尺寸的圖片,例如圖示,(因為在這種情形下)花費額外的請求來獲取多個這樣的小尺寸影像是非常浪費的。
The basic idea is to combine small images into one physical image file and then use CSS background positioning to display only the right part of the image -- commonly called a sprite -- at the right place on the page. The CSS repositioning is fast and seamless, works on an already-downloaded resource, and makes an excellent tradeoff for the otherwise-required multiple HTTP requests and image downloads.
這種技術的基本思路是,將小尺寸圖片在物理上拼成一個圖片檔案,然後利用CSS的背景定位特性將相應需要展示的部分展示在頁面的相應位置上——這通常稱為“一個sprite
”。CSS的定位特性不僅速度快而且效果自然,可以非常有效地抵消HTTP請求和圖片下載次數。
For example, you might have a series of social media icons with links to their respective sites or apps. Rather than downloading three (or perhaps many more) individual images, you might combine them into one image file, like this.
舉個例子,假設你有一組社交網站的圖示,每個圖示連結到他們各自對應的網站或者應用。相對於通過三次(或許更多)請求來下載每個圖示,(現在)你可能會選擇將他們合併為一個圖片檔案,像這樣:
Then, instead of using different images for the links, just retrieve the entire image once and use CSS background positioning ("spriting") for each link to display the correct part of the image for the link.
然後,僅僅通過一次請求獲取整體圖片,並利用CSS的背景定位(spriting)來確保每個連結正確展示整體圖片中的相應的部分。
Here's some sample 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;
}
複製程式碼
Note the extraneous background-position property in the facebook class. It's not necessary, as the default position is 0,0 but is included here for consistency. The other two classes simply shift the image left horizontally relative to its container by 64px and 128px, respectively, leaving the appropriate image section visible in the 64-by-64 pixel link "window".
注意,上面程式碼中的 facebook 類中的backgournd-position
屬性,其實並不是必須的,因為positon
屬性的預設值就是0,0
,在這把它寫出來是為了一致性的原因。另外兩個類則分別相對於包含他們的容器進行了64px
和128px
的水平位移,以確保連結在圖片中的對應部分得以以64X64
畫素的大小正確顯示。
Here's some HTML to use with that CSS.
下面是一段使用了上面CSS程式碼的HTML示例程式碼:
<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>
複製程式碼
Instead of including separate images in the links themselves, just apply the CSS classes and leave the link content empty. This simple technique saves you two HTTP requests by letting CSS do the image shifting behind the scenes. If you have lots of small images -- navigation icons or function buttons, for example -- it could save you many, many trips to the server.
上面的程式碼,不是分別將圖片置於每個連結標籤之間,而是僅僅通過應用 CSS 類並留空連結的內容來實現相同的效果。這種技巧利用了 CSS 來做圖片位移,減少了兩次HTTP請求。當你有大量小尺寸圖片時——導航選單圖示、操作按鈕——這會減少許多,許多和伺服器互動的來回。
You can find a brief but excellent article about this technique, including working examples, at WellStyled.
在 Wellstyled 上,有一篇關於這種技術的簡單但非常棒的文章,還包含了一些可用的示例,你可以查閱下。
一個注意點(A Caveat)
In our discussion of combining text and graphics, we should note that the newer HTTP/2 protocol may change how you consider combining resources. For example, common and valuable techniques like minification, server compression, and image optimization should be continued on HTTP/2. However, physically combining files as discussed above might not achieve the desired result on HTTP/2.
在上面合併文字和影像的討論中,我們應該留意的一點是,新的HTTP/2
協議可能會改變我們對資源合併的想法。例如,常見的和有價值的技術,像程式碼緊湊(minification),服務端壓縮,和圖片優化等都應該繼續保留。但是,前面我們討論的,從物理上對檔案進行合併則可能並不會達到我們想要的結果。
This is primarily because server requests are faster on HTTP/2, so combining files to eliminate a request may not be substantially productive. Also, if you combine a fairly static resource with a fairly dynamic one to save a request, you may adversely affect your caching effectiveness by forcing the static portion of the resource to be reloaded just to fetch the dynamic portion.
這主要是因為在HTTP/2
協議裡,請求伺服器的效率變得更高效了,所以以合併檔案來減少請求可能並沒有什麼特別的效果。同樣,如果將相當數量的靜態資源合併為一個相當動態的資源,以此來減少請求,相反,你可能還會影響到快取的生效,因為這會導致,為獲取動態的部分而強制重新整理靜態的部分的問題。
The features and benefits of HTTP/2 are worth exploring in this context.
HTTP/2
的這些特性和益處,在本文的主題中,都是值得進行一翻探究的。
JavaScript 的位置和內聯推入 (JavaScript Position and Inline Push)
We're assuming so far that all CSS and JavaScript resources exist in external files, and that's generally the best way to deliver them. Bear in mind that script loading is a large and complex issue -- see this great HTML5Rocks article, Deep Dive Into the Murky Waters of Script Loading, for a full treatment. There are, however, two fairly straightforward positional factors about JavaScript that are worth considering.
目前我們都是假設所有 CSS 和 JavaScript 都是外部資源,這也是傳輸他們的最好方式。不過請記住,指令碼的載入是一個大而又複雜的問題——可以參考 HTML5Rocks 上的這篇不錯的文章,Deep Dive Into the Murky Waters of Script Loading。下面是兩種值得思考的,“位置影響”的直接因素。
指令碼位置(Script Location)
Common convention is to put script blocks in the page head. The problem with this positioning is that, typically, little to none of the script is really meant to execute until the page is displayed but, while it is loading, it unnecessarily blocks page rendering. Identifying render-blocking script is one of the reporting rules of PageSpeed Insights.
通常我們是將指令碼塊放置到頁面的頭部(head)。這種指令碼置放方式有一個問題,通常,幾乎沒有那個指令碼是真實需要在頁面載入且並未顯示時就執行的,這就導致頁面渲染會被阻塞。也正因為如此,鑑別阻塞性程式碼也是PageSpeed Insights工具設定報表規則中的其中一項。
A simple and effective solution is to reposition the deferred script block at the end of the page. That is, put the script reference last, just before the closing body tag. This allows the browser to load and render the page content, and then lets it download the script while the user perceives the initial content. For example:
解決這個問題的一種簡單而又有效的方法是,將這些可以延後執行的指令碼塊移至頁面的尾部。即,將指令碼放置於body
標籤的結束標籤之前。這樣就可以達到瀏覽器在載入和渲染頁面內容之後,在下載這些指令碼的同時使用者可以感受到初始的內容。例如:
<html>
<head>
</head>
<body>
[Body content goes here.]
<script src="mainscript.js"></script>
</body>
</html>
複製程式碼
An exception to this technique is any script that manipulates the initial content or DOM, or provides required page functionality prior to or during rendering. Critical scripts such as these can be put into a separate file and loaded in the page head as usual, and the rest can still be placed last thing in the page for loading only after the page is rendered.
但有一種特殊情況,一些指令碼在頁面渲染之前或者渲染過程中,就需要對初始的內容(initial content)、DOM進行操作或者需要保障一些必要的頁面功能。這種情況,我們可以將這些關鍵性指令碼(Critical script)放在一個單獨的檔案中,然後像通常的做法一樣,將其放在頁面的頭部進行載入,同時,其他可以在頁面渲染完成後才載入的指令碼則放置在頁面的最後。
The order in which resources must be loaded for maximum efficiency is called the Critical Rendering Path; you can find a thorough article about it at Bits of Code.
我們稱這種使得哪些必須被載入的資源達到最高效載入效果的載入順序為“關鍵渲染路徑”(Critical Rendering Path);關於這個話題,在Bits of Code上面有一篇詳盡的介紹。
程式碼位置(Code Location)
Of course, the technique described above splits your JavaScript into two files on the server and thus requires two HTTP requests instead of one, exactly the situation we're trying to avoid. A better solution for relocating critical, pre-render scripts might be to place them directly inside the page itself, referred to as an "inline push".
上面提到的這種技術,會將 JavaScript 分割成兩個檔案,因此也需要兩次 HTTP 請求而不是一次,這種情形顯然是需要儘量避免的。一個好的解決方法是,將那些關鍵性的、預載入的指令碼直接提取到頁面上,這稱之為“內聯推入(inline-push)”
Here, instead of putting the critical script in a separate file and referencing it in the page head, add a block, either in the head or in the body, and insert the script itself (not a file reference, but the actual script code) at the point at which it's needed. Assuming the script isn't too big, this method gets the script loaded along with the HTML and executed immediately, and avoids the extra HTTP request overhead of putting it in the page head.
就是說,不是將“關鍵性指令碼”放在一個單獨的檔案中,並在頁面的頭部引用它;而是說,將指令碼本身(不是檔案引用,而是實實在在的程式碼)放在一個<script>...</script>
塊中,並將其插入需要的地方(要麼是頭部,要麼是體部)。假設指令碼不是很大,這種方法可以使得指令碼伴隨著HTML進行載入並即刻執行,進而避免額外的 HTTP 請求消耗。
For example, if a returning user's name is already available, you might want to display it in the page as soon as possible by calling a JavaScript function, rather than wait until after all the content is loaded.
舉個例子,假設某個返回的使用者名稱字已經可以獲取到,那麼你可能希望它可在頁面中儘快展示,而不是等待所有的內容都已經載入完了才顯示。
<p>Welcome back, <script>insertText(username)</script>!</p>
複製程式碼
Or you might need an entire function to execute in place as the page loads, in order to render certain content correctly.
或者,有時你可能需要一個完整的函式隨著頁面的載入適當地執行,以此來正確地渲染內容。
<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>
複製程式碼
This simple technique avoids a separate HTTP request to retrieve a small amount of code and allows the script to run immediately at its appropriate place in the page, at the minor cost of a few dozen extra bytes in the HTML page.
這種簡單的技術,只花費了幾十個位元組的微小代價,卻換來了,既避免了額外的HTTP請求,又使得指令碼得以在它正確得位置立刻執行的效果。
總結 (Summary)
In this section, we covered ways to reduce the number of HTTP requests our pages make, and considered techniques for both text and graphical resources. Every round-trip to the server we can avoid saves time, speeds up the page load, and gets its content to our users sooner.
在本篇中,我們提到了一些減少HTTP請求次數的方法,考察了這方面一些針對檔案和影像資源的技術。每一個我們可以避免的到伺服器的來回,都可以加速頁面的載入,進而可以將內容很快地呈現給使用者。