JavaScript 10種效能較調方式
隨著開發時間的加長及引入外掛的增加,常常會感覺網頁越跑越慢,這時候就是代表網頁該瘦身了!
本篇文章歸納了 10 種幫 JS 調校的方法,目的是希望能夠讓使用者更快載入網頁,享受到更好的使用者體驗。
1. 調整 JavaScript 的寫法
(1) 在大量資料 (或 render HTML) 時 ,避免字串加法,改用 array.push (用 index.push [i] 取代 index+=i ) ,可減少記憶體使用量
(2) CSS 效能通常較 JS 為佳,所以可以用 CSS 處理的就不用使用 JavaScript
(3) 在大量的迴圈時 (Ex. 10000 次的 for loop) ,可以考慮分段處理,例如切成 10 個 1000 次 for-loop,再將結果相加
(4) 盡量不要使用 eval 、 with
(5) 少用 try & catch
(6) 少用全域變數
(7) 在 for 迴圈少盡量不使用 in (因會自動列舉一次)
(8) 縮短變數名稱
(9) setTimeout & setInterval 時少用字串 (即用變數取代之) , for example :
//較慢
setInterval('doSomethingPeriodically()', 1000);
setTimeOut('doSomethingAfterFiveSeconds()', 5000);
//較快
setInterval(doSomethingPeriodically, 1000);
setTimeOut(doSomethingAfterFiveSeconds, 5000);
2. 使用瀏覽器監測軟體找尋 bottleneck
通常越新的瀏覽器效能也越好,像是 IE9+ 、Firefox 9 、Chrome 最新版等等,但在開發的時候還是會需要一些工具來幫助我們找出真正讓瀏覽器變慢的 bottlenect (尤其是 IE ..)
FireBug :
Firefox 的擴充套件,主要能夠方便網頁設計師快速及方便的修改 CSS、DOM、HTML,即時預覽修改效果、 察看及執行 JavaScript 等,還能偵測網路速度,找出拖累網站速度的 BottleNeck
Chrome Debugger :
Chrome 推出的瀏覽器偵錯功能,可按 F12 叫出,與 FireBug 很類似,
IE Dev Tools :
IE 內建的偵錯功能,熱鍵也是 F12,功能與上面的 firebug、debugger 類似
3. 改良 jQuery 的寫法
Selector 的使用
速度: Id > tag > class > attribute = 偽類,特別是 IE9 以下的版本,針對 Selector 的速度真的很慢
範圍:從越小的範圍來 select 會比較快
以下五種語法,讀者可以猜一下運作的速度:
$('.child', $parent)
$parent.find('.child')
$parent.children('.child')
$('#parent > .child')
$('#parent .child')
$('.child', $('#parent'))
速度比較如下 (來源) :2 > 1 > 6 > 3 > 4 > 5
你猜對了嗎 ?
儘量用 .addClass() 來取代 .CSS
直接用 .CSS 語法換,會比直接在 CSS 定義一個 class,然後設定為該 class 來的慢 ( 少用 .CSS )
所以儘量使用 $jQueryObject.addClass(“className”);
儘量使用原生 JavaScript
因為原生的 JavaScript 速度 > jQuery (但是 jQuery 開發速度較快),舉例來說,this.id的速度 是 $(this).attr(‘id’) 的 20 倍快 (來源),
儘量使用 jQuery 的 link 寫法
jQuery 提供以下這種 link 的寫法:
var len = $kids.parent().sibling().addClass("hilite").length;
採用 Link 寫法時,jQuery自動緩存每一步的結果,因此比 not link 寫法要快。根據測試,link 寫法比(不使用緩存的)not link 寫法,大約快了25%。
儘量使用 delegate 的寫法
如果今天要將 click 事件綁定到 1000 個 <td> 上面,則
$("table").delegate("td","click", function(){
$(this).toggleClass("click");
});
的寫法 (只要執行1次) ,速度會遠快於以下這種寫法:
$("td").bind("click", function(){
$(this).toggleClass("click");
});
儘量少動到 DOM 結構
.append()、.insertBefore() 、 .insetAfter()、 等方法,會改變 HTML DOM 的結構,應儘量避免,若無法避免,可減少操作的次數,根據統計,先將字串合併後一次插入 HTML 中,速度是分多次將字串插入 HTML 的快 10 倍。
4. 使用 CDN 來儲放程式碼
現在已經很流行用 CDN (Content delivery network ) 的方式雲端儲存程式碼,舉例來說,許多人會用以下語法將 jQuery 包進函式
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script type="text/javascript">window.jQuery||document.write('<script type="text/javascript" src="/Scripts/jquery-1.6.2.min.js">\x3C/script>')</script>
即直接從 google 的 code repository 引入函式庫,這樣的好處是使用者不需重新載入,由於使用者「極有可能」已經在其他網站載入這段程式碼,並儲存在 cache 中,那他在瀏覽你的網站的時候就不用重新載入,可加快網頁載入的時間。
值得注意的是這種方式有一點不保險,畢竟程式碼在別人家裡;除此之外,當使用者想要離線瀏覽時,也無法順利讀取到。
5. 把部份 loading 丟到 Server 端處理
把 code loading 放到後端是苦了 server ,但把 code loading 放在前端則是苦了 user ,當今天是為了提供使用者一個很棒的瀏覽速度的話,可以考慮把部份 loading 丟到 server 端處理,再把結果利用 Ajax 等技巧吐回前端呈現即可。
舉例來說:假設現在有一個任務,是要在畫面中畫出 10000 張 <img> ,如果直接使用 10000 次 <img> 的 Tag,將會造成可怕的 loading,取而代之可以先在 Server 端將 10000 張畫好變成同一張圖,再將這一張圖 load 進來即可,可以減少大量的 request 與 draw time
一言以敝之:「Server 端運算能力遠大於 JavaScript,儘量將大量 loading 交給 Server 端處理」
6. 減少 JS script Tag 的數量
網頁在載入時,必須把 <script src = “” > … 這類的 tag 載入完後,才會開始顯示下面的網頁,而這些時間也會造成 user 的等候,而這類的 tag 次數越多,速度不是慢在 request 的內容 (影響不大),而是在於 tag request 的次數,次數越多,網頁載入的時間也越慢,網路上有人提供用 iFrame 把 script tag 包進去的方法,可參考以下文章。
總之,如果不怕搞混的話,把多隻 JS 合併成 1 支,讓 request 的數量從多個變成1個,也可以提升網頁的速度。此部份可參考以下第 8 點:使用 JS 相關管理外掛。
7.善用 G-Zip 與 cache
其實程式碼中間都有許多可供壓縮的空間,在透過某些方法將 JS瘦身之後 (甚至是 HTML、CSS 皆可) ,可大幅減少 server 的 loading 與載入時間,舉例來說,以下為 jquery 的一般程式碼與 G-zip 過的程式碼:
jQuery 一般程式碼 (開發用):http://code.jquery.com/jquery-1.7.2.js
jQuery 一般程式碼 ( G-zip 後):http://code.jquery.com/jquery-1.7.2.min.js
網路上有提供許多自動化的工具,而許多 IDE 也有內建此功能,值得注意的是有時候 G-Zip 壓縮完的程式碼會出現 Bug (例如 IE6) ,所以程式碼在壓縮前務必記得備份。
8. 使用 JS 相關管理外掛
現在有許多網路上的外掛在幫忙管理 JS 檔,最近很有名的就是 Require.js
Require.js 官網:http://requirejs.org/
Def:how to encapsulate a piece of code into a useful unit, and how to register its capability/export a value for the module.
由於隨著網路的演進,現在網站開發越來越依賴 JavaScript,大型專案動輒數百個 js 檔真的會難以管理,Require.js 的目的就在於協助管理 JS 的 Library (類似 Java 的 import) ,利用其特性,養成將 JS 模組化的概念,而非傳統剪下貼上的概念。
Require.js 主要目的:(1) 非同步載入 JS (2) 管理相依性
require.js 的檔案結構如下:
project-directory/
project.html ( HTML 內容)
scripts/
main.js (負責設定會引入的 JS 檔)
require.js ( library)
helper/
util.js ( 擴充功能用)
Require.js 可將所有 JS 檔設定好對應的 path,實際執行的 HTML 檔只要引入 1 支 JS 檔即可,可加速網站載入速度,且不會將 JS 的管理與 HTML 混在一起,載入語法如下:
<!DOCTYPE html>
<html>
<head>
<title>jQuery+RequireJS Sample Page</title>
<script data-main="scripts/main" src="scripts/require-jquery.js"></script>
</head>
<body>
<h1>jQuery+RequireJS Sample Page</h1>
</body>
</html>
而在 main.js 裡則定義相關會使用到的 js 檔,統一管理
require([“some/module”,“a.js”,“b.js”],
function(){
//This function is called once all scripts are downloaded and executed
});
9. 小心 mouseEvent
瀏覽器對於 mouseMoveEvent 的處理效能很差,尤其是 Scrolling 和 Ajax 的時候,由於產生太多 event (不斷重新 render 畫面),即使是 JS 最佳的 Chrome,效能還是無法獲得顯著的提升,所以要非常小心 monseEvent 的使用。
mouseEvent 的 sample code 如下:
<input type="button" value="click me" id="btn">
<input type="button" value="right-click me" id="btn2">
<script>
document.getElementById('btn').onclick = function() {
alert('click!')
}
document.getElementById('btn2').oncontextmenu = function() {
alert('right click!')
}
</script>
10. 使用 CSS-Sprite
由於載入一張圖的速度比載入很多圖的來的快 (即使總大小一樣),所以現在很流行使用 CSS-Sprite 的方式,將網頁常用的 icon 或圖片放在同一張圖裡,再使用 CSS 控制顯示的區塊,此法同樣可以提升網站載入的效率。
舉例來說,網站裡放有以下圖片:
我們可以使用以下語法來切割各 div 要顯示的 icon
#ico1{寬度:容器大小;高度:容器高度;背景位置:X坐標Ÿ坐標}
#ico2{寬度:容器大小;高度:容器高度;背景位置:X坐標Ÿ坐標}
#ico3{寬度:容器大小;高度:容器高度;背景位置:X坐標Ÿ坐標}
簡單來說,CSS-Sprite 的精隨就是:
載入3張圖片合併成1張圖片的時間,總是小於這分別載入3張圖片的總和
但是 CSS – Sprite 也有一些問題,像是維護困難,假設今天只想要變動其中一個 icon ,卻必須使用到 photoshop 之類的編輯軟體,啟不是殺雞還用牛刀 ?
另外,瀏覽器的相容性也是一個問題,使用 CSS – Sprite 有時候會在最棒的 IE6 裡面跑掉..務必小心
結論
為了提升使用者經驗,效能也佔了相當大的影響 ( ex. iPhone 的效能多數較 Andriod 為佳,因此也影響了許多人的使用觀感),對於使用者來說,就算只差 0.1 秒,也能夠影響許多使用者使用的意願。
本篇文章歸納了 10 種幫 JS 調校的方法,目的是希望能夠讓使用者更快載入網頁,享受到更好的使用者體驗。
1. 調整 JavaScript 的寫法
(1) 在大量資料 (或 render HTML) 時 ,避免字串加法,改用 array.push (用 index.push [i] 取代 index+=i ) ,可減少記憶體使用量
(2) CSS 效能通常較 JS 為佳,所以可以用 CSS 處理的就不用使用 JavaScript
(3) 在大量的迴圈時 (Ex. 10000 次的 for loop) ,可以考慮分段處理,例如切成 10 個 1000 次 for-loop,再將結果相加
(4) 盡量不要使用 eval 、 with
(5) 少用 try & catch
(6) 少用全域變數
(7) 在 for 迴圈少盡量不使用 in (因會自動列舉一次)
(8) 縮短變數名稱
(9) setTimeout & setInterval 時少用字串 (即用變數取代之) , for example :
//較慢
setInterval('doSomethingPeriodically()', 1000);
setTimeOut('doSomethingAfterFiveSeconds()', 5000);
//較快
setInterval(doSomethingPeriodically, 1000);
setTimeOut(doSomethingAfterFiveSeconds, 5000);
2. 使用瀏覽器監測軟體找尋 bottleneck
通常越新的瀏覽器效能也越好,像是 IE9+ 、Firefox 9 、Chrome 最新版等等,但在開發的時候還是會需要一些工具來幫助我們找出真正讓瀏覽器變慢的 bottlenect (尤其是 IE ..)
FireBug :
Firefox 的擴充套件,主要能夠方便網頁設計師快速及方便的修改 CSS、DOM、HTML,即時預覽修改效果、 察看及執行 JavaScript 等,還能偵測網路速度,找出拖累網站速度的 BottleNeck
Chrome Debugger :
Chrome 推出的瀏覽器偵錯功能,可按 F12 叫出,與 FireBug 很類似,
IE Dev Tools :
IE 內建的偵錯功能,熱鍵也是 F12,功能與上面的 firebug、debugger 類似
3. 改良 jQuery 的寫法
Selector 的使用
速度: Id > tag > class > attribute = 偽類,特別是 IE9 以下的版本,針對 Selector 的速度真的很慢
範圍:從越小的範圍來 select 會比較快
以下五種語法,讀者可以猜一下運作的速度:
$('.child', $parent)
$parent.find('.child')
$parent.children('.child')
$('#parent > .child')
$('#parent .child')
$('.child', $('#parent'))
速度比較如下 (來源) :2 > 1 > 6 > 3 > 4 > 5
你猜對了嗎 ?
儘量用 .addClass() 來取代 .CSS
直接用 .CSS 語法換,會比直接在 CSS 定義一個 class,然後設定為該 class 來的慢 ( 少用 .CSS )
所以儘量使用 $jQueryObject.addClass(“className”);
儘量使用原生 JavaScript
因為原生的 JavaScript 速度 > jQuery (但是 jQuery 開發速度較快),舉例來說,this.id的速度 是 $(this).attr(‘id’) 的 20 倍快 (來源),
儘量使用 jQuery 的 link 寫法
jQuery 提供以下這種 link 的寫法:
var len = $kids.parent().sibling().addClass("hilite").length;
採用 Link 寫法時,jQuery自動緩存每一步的結果,因此比 not link 寫法要快。根據測試,link 寫法比(不使用緩存的)not link 寫法,大約快了25%。
儘量使用 delegate 的寫法
如果今天要將 click 事件綁定到 1000 個 <td> 上面,則
$("table").delegate("td","click", function(){
$(this).toggleClass("click");
});
的寫法 (只要執行1次) ,速度會遠快於以下這種寫法:
$("td").bind("click", function(){
$(this).toggleClass("click");
});
儘量少動到 DOM 結構
.append()、.insertBefore() 、 .insetAfter()、 等方法,會改變 HTML DOM 的結構,應儘量避免,若無法避免,可減少操作的次數,根據統計,先將字串合併後一次插入 HTML 中,速度是分多次將字串插入 HTML 的快 10 倍。
4. 使用 CDN 來儲放程式碼
現在已經很流行用 CDN (Content delivery network ) 的方式雲端儲存程式碼,舉例來說,許多人會用以下語法將 jQuery 包進函式
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script type="text/javascript">window.jQuery||document.write('<script type="text/javascript" src="/Scripts/jquery-1.6.2.min.js">\x3C/script>')</script>
即直接從 google 的 code repository 引入函式庫,這樣的好處是使用者不需重新載入,由於使用者「極有可能」已經在其他網站載入這段程式碼,並儲存在 cache 中,那他在瀏覽你的網站的時候就不用重新載入,可加快網頁載入的時間。
值得注意的是這種方式有一點不保險,畢竟程式碼在別人家裡;除此之外,當使用者想要離線瀏覽時,也無法順利讀取到。
5. 把部份 loading 丟到 Server 端處理
把 code loading 放到後端是苦了 server ,但把 code loading 放在前端則是苦了 user ,當今天是為了提供使用者一個很棒的瀏覽速度的話,可以考慮把部份 loading 丟到 server 端處理,再把結果利用 Ajax 等技巧吐回前端呈現即可。
舉例來說:假設現在有一個任務,是要在畫面中畫出 10000 張 <img> ,如果直接使用 10000 次 <img> 的 Tag,將會造成可怕的 loading,取而代之可以先在 Server 端將 10000 張畫好變成同一張圖,再將這一張圖 load 進來即可,可以減少大量的 request 與 draw time
一言以敝之:「Server 端運算能力遠大於 JavaScript,儘量將大量 loading 交給 Server 端處理」
6. 減少 JS script Tag 的數量
網頁在載入時,必須把 <script src = “” > … 這類的 tag 載入完後,才會開始顯示下面的網頁,而這些時間也會造成 user 的等候,而這類的 tag 次數越多,速度不是慢在 request 的內容 (影響不大),而是在於 tag request 的次數,次數越多,網頁載入的時間也越慢,網路上有人提供用 iFrame 把 script tag 包進去的方法,可參考以下文章。
總之,如果不怕搞混的話,把多隻 JS 合併成 1 支,讓 request 的數量從多個變成1個,也可以提升網頁的速度。此部份可參考以下第 8 點:使用 JS 相關管理外掛。
7.善用 G-Zip 與 cache
其實程式碼中間都有許多可供壓縮的空間,在透過某些方法將 JS瘦身之後 (甚至是 HTML、CSS 皆可) ,可大幅減少 server 的 loading 與載入時間,舉例來說,以下為 jquery 的一般程式碼與 G-zip 過的程式碼:
jQuery 一般程式碼 (開發用):http://code.jquery.com/jquery-1.7.2.js
jQuery 一般程式碼 ( G-zip 後):http://code.jquery.com/jquery-1.7.2.min.js
網路上有提供許多自動化的工具,而許多 IDE 也有內建此功能,值得注意的是有時候 G-Zip 壓縮完的程式碼會出現 Bug (例如 IE6) ,所以程式碼在壓縮前務必記得備份。
8. 使用 JS 相關管理外掛
現在有許多網路上的外掛在幫忙管理 JS 檔,最近很有名的就是 Require.js
Require.js 官網:http://requirejs.org/
Def:how to encapsulate a piece of code into a useful unit, and how to register its capability/export a value for the module.
由於隨著網路的演進,現在網站開發越來越依賴 JavaScript,大型專案動輒數百個 js 檔真的會難以管理,Require.js 的目的就在於協助管理 JS 的 Library (類似 Java 的 import) ,利用其特性,養成將 JS 模組化的概念,而非傳統剪下貼上的概念。
Require.js 主要目的:(1) 非同步載入 JS (2) 管理相依性
require.js 的檔案結構如下:
project-directory/
project.html ( HTML 內容)
scripts/
main.js (負責設定會引入的 JS 檔)
require.js ( library)
helper/
util.js ( 擴充功能用)
Require.js 可將所有 JS 檔設定好對應的 path,實際執行的 HTML 檔只要引入 1 支 JS 檔即可,可加速網站載入速度,且不會將 JS 的管理與 HTML 混在一起,載入語法如下:
<!DOCTYPE html>
<html>
<head>
<title>jQuery+RequireJS Sample Page</title>
<script data-main="scripts/main" src="scripts/require-jquery.js"></script>
</head>
<body>
<h1>jQuery+RequireJS Sample Page</h1>
</body>
</html>
而在 main.js 裡則定義相關會使用到的 js 檔,統一管理
require([“some/module”,“a.js”,“b.js”],
function(){
//This function is called once all scripts are downloaded and executed
});
9. 小心 mouseEvent
瀏覽器對於 mouseMoveEvent 的處理效能很差,尤其是 Scrolling 和 Ajax 的時候,由於產生太多 event (不斷重新 render 畫面),即使是 JS 最佳的 Chrome,效能還是無法獲得顯著的提升,所以要非常小心 monseEvent 的使用。
mouseEvent 的 sample code 如下:
<input type="button" value="click me" id="btn">
<input type="button" value="right-click me" id="btn2">
<script>
document.getElementById('btn').onclick = function() {
alert('click!')
}
document.getElementById('btn2').oncontextmenu = function() {
alert('right click!')
}
</script>
10. 使用 CSS-Sprite
由於載入一張圖的速度比載入很多圖的來的快 (即使總大小一樣),所以現在很流行使用 CSS-Sprite 的方式,將網頁常用的 icon 或圖片放在同一張圖裡,再使用 CSS 控制顯示的區塊,此法同樣可以提升網站載入的效率。
舉例來說,網站裡放有以下圖片:
我們可以使用以下語法來切割各 div 要顯示的 icon
#ico1{寬度:容器大小;高度:容器高度;背景位置:X坐標Ÿ坐標}
#ico2{寬度:容器大小;高度:容器高度;背景位置:X坐標Ÿ坐標}
#ico3{寬度:容器大小;高度:容器高度;背景位置:X坐標Ÿ坐標}
簡單來說,CSS-Sprite 的精隨就是:
載入3張圖片合併成1張圖片的時間,總是小於這分別載入3張圖片的總和
但是 CSS – Sprite 也有一些問題,像是維護困難,假設今天只想要變動其中一個 icon ,卻必須使用到 photoshop 之類的編輯軟體,啟不是殺雞還用牛刀 ?
另外,瀏覽器的相容性也是一個問題,使用 CSS – Sprite 有時候會在最棒的 IE6 裡面跑掉..務必小心
結論
為了提升使用者經驗,效能也佔了相當大的影響 ( ex. iPhone 的效能多數較 Andriod 為佳,因此也影響了許多人的使用觀感),對於使用者來說,就算只差 0.1 秒,也能夠影響許多使用者使用的意願。
留言
張貼留言