回復“資源”即可獲贈Python學習資料
大家好,我是皮皮。
JavaScript 是單線程運行的,所以在在執行效率上并不是很高,隨著用戶體驗的日益重視,前端性能對用戶體驗的影響備受關注,但由于性能問題相對復雜,接下來我們來了解下JavaScript如何提高性能;
由于 JavaScript 的阻塞特性,在每一個<script>
出現的時候,無論是內嵌還是外鏈的方式,它都會讓頁面等待腳本的加載解析和執行,
并且<script>
標簽可以放在頁面的<head>
或者<body>
中,因此,如果我們頁面中的 css 和 js 的引用順序或者位置不一樣,即使是同樣
的代碼,加載體驗都是不一樣的。示例如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>js 引用的位置性能優化</title>
<script type="text/javascript" src="index-1.js"></script>
<script type="text/javascript" src="index-2.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
以上代碼是一個簡單的 html 界面,其中加載了兩個 js 腳本文件和一個 css 樣式文件,由于 js 的阻塞問題,當加載到 index-1.js 的時候,
其后面的內容將會被掛起等待,直到index-1.js
加載、執行完畢,才會執行第二個腳本文件 index-2.js
,這個時候頁面又將被掛起等待腳
本的加載和執行完成,一次類推,這樣用戶打開該界面的時候,界面內容會明顯被延遲,我們就會看到一個空白的頁面閃過,這種體驗是
明顯不好的,因此 我們應該盡量的讓內容和樣式先展示出來,將 js 文件放在 最后,以此來優化用戶體驗。如下所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>js 引用的位置性能優化</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="index-1.js"></script>
<script type="text/javascript" src="index-2.js"></script>
</body>
</html>
這段代碼展示了在 HTML 文檔中放置<script>
標簽的推薦位置。盡管腳本下載會阻塞另一個腳本,但是頁面的大部分內容都已經下載完
成并顯示給了用戶,因此頁面下載不會顯得太慢。這是雅虎特別性能小組提出的優化 JavaScript 的首要規則:將腳本放在底部。
由于每個<script>
標簽初始下載時都會阻塞頁面渲染,所以減少頁面包含的<script>
標簽數量有助于改善這一情況。這不僅針對外鏈腳本,內嵌腳本的數量同樣也要限制。瀏覽器在解析 HTML 頁面的過程中每遇到一個<script>
標簽,都會因執行腳本而導致一定的延時,因此最小化延遲時間將會明顯改善頁面的總體性能。
這個問題在處理外鏈 JavaScript 文件時略有不同。考慮到 HTTP 請求會帶來額外的性能開銷,因此下載單個 100Kb 的文件將比下載 5 個 20Kb 的文件更快。也就是說,減少頁面中外鏈腳本的數量將會改善性能。
通常一個大型網站或應用需要依賴數個 JavaScript 文件。您可以把多個文件合并成一個,這樣只需要引用一個<script>
標簽,就可以減少性能消耗。文件合并的工作可通過離線的打包工具或者一些實時的在線服務來實現。
需要特別提醒的是,把一段內嵌腳本放在引用外鏈樣式表的之后會導致頁面阻塞去等待樣式表的下載。這樣做是為了確保內嵌腳本在執行時能獲得最精確的樣式信息。因此,建議不要把內嵌腳本緊跟在標簽后面。
有一點我們需要知道:頁面加載的過程中,最耗時間的不是 js 本身的加載和執行,相比之下,每一次去后端獲取資源,客戶端與后臺建立鏈接才是最耗時的,也就是大名鼎鼎的Http 三次握手,當然,http 請求不是我們這一次討論的主題,因此,減少 HTTP 請求,是我們著重優化的一項,事實上,在頁面中 js 腳本文件加載很很多情況下,它的優化效果是很顯著的。
在 JavaScript 性能優化上,減少腳本文件大小并限制 HTTP 請求的次數僅僅是讓界面響應 迅速的第一步,現在的 web 應用功能豐富,js 腳本越來越多,光靠精簡源碼大小和減少 次數不總是可行的,即使是一次 HTTP 請求,但文件過于龐大,界面也會被鎖死很長一段 時間,這明顯不好的,因此,無阻塞加載技術應運而生。簡單來說, 就是 頁面在加載完成后才加載 s js 代碼,也就是在 w window 對象的 d load 事件觸 發后才去下載腳本。要實現這種方式,常用以下幾種方式:
HTML4 為<script>
標簽定義了一個擴展屬性:defer。Defer 屬性指明本元素所含的腳本不會修改 DOM,因此代碼能安全地延遲執行。defer 屬性只被 IE 4 和 Firefox 3.5 更高版本的瀏覽器所支持,所以它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器中,defer 屬性會被直接忽略,因此<script>
標簽會以默認的方式處理,也就是說會造成阻塞。然而,如果您的目標瀏覽器支持的話,這仍然是個有用的解決方案。
<script type="text/javascript" src="index-1.js" defer></script>
帶有 defer 屬性的<script>
標簽可以放置在文檔的任何位置。對應的 JavaScript 文件將在頁面解析到<script>
標簽時開始下載,但不會執行,直到 DOM 加載完成,即 onload事件觸發前才會被執行。當一個帶有 defer 屬性的 JavaScript 文件下載時,它不會阻塞瀏覽的其他進程,因此這類文件可以與其他資源文件一起并行下載。·任何帶有 defer 屬性的<script>
元素在 DOM 完成加載之前都不會被執行,無論內嵌或者是外鏈腳本都是如此。
HTML5 規范中也引入了 async 屬性,用于異步加載腳本,其大致作用和 defer 是一樣的,都是采用的并行下載,下載過程中不會有阻塞,但 不同點在于他們的執行時機,c async 需要加載完成后就會自動執行代碼 ,但是 r defer 需要等待頁面加載完成后才會執行。
把代碼以動態的方式添加的好處是:無論這段腳本是在何時啟動下載,它的下載和執行過程都不會阻塞頁面的其他進程,我們甚至可以直接添加帶頭部 head 標簽中,都不會影響其他部分。因此,作為開發的你肯定見到過諸如此類的代碼塊:
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'file.js';
document.getElementsByTagName('head')[0].appendChild(script);
這種方式便是動態創建腳本的方式,也就是我們現在所說的動態腳本創建。通過這種方式下載文件后,代碼就會自動執行。但是在現代瀏覽器中,這段腳本會等待所有動態節點加載完成后再執行。這種情況下,為了確保當前代碼中包含的別的代碼的接口或者方法能夠被成功調用,就必須在別的代碼加載前完成這段代碼的準備。解決的具體操作思路是:現代瀏覽器會在 script 標簽內容下載完成后接收一個load 事件,我們就可以在 load 事件后再去執行我們想要執行的代碼加載和運行,在 IE 中,它會接收 loaded 和 complete事件,理論上是 loaded 完成后才會有 completed,但實踐告訴我們他兩似乎并沒有個先后,甚至有時候只會拿到其中的一個事件,我們可以單獨的封裝一個專門的函數來體現這個功能的實踐性,因此一個統一的寫法是:
function LoadScript(url, callback) {
var script = document.createElement('script');
script.type = 'text/javascript';
// IE 瀏覽器下
if (script.readyState) {
script.onreadystatechange = function () {
if (script.readyState == 'loaded' || script.readyState ==
'complete') {
// 確保執行兩次
script.onreadystatechange = null;
// todo 執行要執行的代碼
callback()
}
}
} else {
script.onload = function () {
callback();
}
}
script.src = 'file.js';
document.getElementsByTagName('head')[0].appendChild(script);
}
LoadScript 函數接收兩個參數,分別是要加載的腳本路徑和加載成功后需要執行的回調函數,LoadScript 函數本身具有特征檢測功能,根據檢測結果(IE 和其他瀏覽器),來決定腳本處理過程中監聽哪一個事件。實際上這里的 LoadScript()函數,就是我們所說的 LazyLoad.js(懶加載)的原型。
通過 XMLHttpRequest 對象來獲取腳本并注入到頁面也是實現無阻塞加載的另一種方式,這個我覺得不難理解,這其實和動態添加腳本的方式是一樣的思想,來看具體代碼:
var xhr = new XMLHttpRequest();
xhr.open('get', 'file-1.js', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
// 如果從后臺或者緩存中拿到數據,則添加到 script 中并加載執行。
var script = document.createElement('script');
script.type = 'text/javascript';
script.text = xhr.responseText;
// 將創建的 script 添加到文檔頁面
document.body.appendChild(script);
}
}
}
通過這種方式拿到的數據有兩個優點:其一,我們可以控制腳本是否要立即執行,因為我們知道新創建的 script 標簽只要添加到文檔界面中它就會立即執行,因此,在添加到文檔界面之前,也就是在 appendChild()之前,我們可以根據自己實際的業務邏輯去實現需求,到想要讓它執行的時候,再 appendChild()即可。其二:它的兼容性很好,所有主流瀏覽器都支持,它不需要想動態添加腳本的方式那樣,我們自己去寫特性檢測代碼;但由于是使用了 XHR 對象,所以不足之處是獲取這種資源有“域”的限制。資源 必須在同一個域下才可以,不可以跨域操作。
減少 JavaScript 對性能的影響有以下幾種方法:
<script>
標簽放到頁面底部,也就是</body>
閉合標簽之前,這能確保在
腳本執行前頁面已經完成了渲染。<script>
標簽越少,加載也就越快,響應也越迅速。無論是外鏈腳本還是內嵌腳本都是如此。<script>
標簽的 defer 屬性(僅適用于 IE 和 Firefox 3.5 以上版
本);<script>
元素來下載并執行代碼;通過以上策略,可以在很大程度上提高那些需要使用大量 JavaScript 的 Web 網站和應用的實際性能。
------------------- End -------------------