歡迎您光臨本站 註冊首頁

JavaScript項目優化總結

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  

 

前端時間對公司已有項目JavaScript代碼進行優化,本文的是對優化工作的一個總結,拿出來與大家分享。當然我的優化方式可能並不是最優的,或者說有些不對的地方,請指教。

JavaScript優化總結分為以下幾點

優化前後對比

模塊化(類編程):代碼清晰、有效防止變數污染問題、代碼重用方便擴展等;

JavaScript壓縮混淆:減少size優化載入時間,混淆保護代碼;

JavaScript文件合併:減少http請求優化網路耗時提升性能;

生成文檔:方便公共庫的使用,查找介面方便。

模塊化(類編程)

對於靜態類來說JavaScript實現比較簡單,使用Object直接量就已經夠用了;但是要創建實例化、可繼承經典的類需要做一番工作。因為JavaScript是基於原型的(prototype-based)編程語言,並沒有包含內置類的實現(它沒有訪問控制符,它沒有定義類的關鍵字class,它沒有支持繼承的extend或冒號,它也沒有用來支持虛函數的virtual等),但是我們通過JavaScript可以輕易地模擬出經典的類。

靜態類

根據寶寶JS公共介面的特性,它們不需要實例化,所以優化使用了該方式。下面以PetConfigParser為例介紹下實現方式:

  1. var PetConfigParser;  
  2. if (!PetConfigParser) {  
  3.     PetConfigParser = {};  
  4. }  
  5. (function () {  
  6.     //private 變數、函數  
  7.     /**  
  8.      * 寶寶所有配置字典,以【cate * 10000 + (lvl - 1) * 10 + dex - 1】為key  
  9.      * @attribute    petDic  
  10.      * @type {Object}  
  11.      * @private  
  12.      */ 
  13.     var petDic = null;  //寶寶字典  
  14.     /**  
  15.      * 根據__pet_config構建一個Object字典,以cate、dex、lvl組合作為key  
  16.      * @method buildPetDic  
  17.      * @private  
  18.      * @return {void}  
  19.      */ 
  20.     function buildPetDic() {  
  21.         petDic = new Object();  
  22.         for (var item in __pet_config) {  
  23.             var lvl = parseInt(__pet_config[item]['lvl']);  
  24.             var dex = parseInt(__pet_config[item]['dex']);  
  25.             var cate = parseInt(__pet_config[item]['cate']);  
  26.             var key = cate * 10000 + (lvl - 1) * 10 + dex;  
  27.             petDic[key] = __pet_config[item];  
  28.         }  
  29.     }  
  30.     //public 介面  
  31.     /**  
  32.      * 根據寶寶id,讀取__pet_config中對應寶寶的信息  
  33.      * @method getPetById  
  34.      * @param   {String/int} petId 寶寶id  
  35.      * @return  {Object} pet 寶寶的所有靜態信息,如{id:"300003289", lvl:"1", dex:"2", price:"200", life:"2592000", cate:"3", name:"飛天小使等級1熟練2", intro:"", skill:"護身符", skill1_prob:"30", skill2_prob:"0"}  
  36.      */ 
  37.     if (typeof PetConfigParser.getPetById !== 'function') {  
  38.         PetConfigParser.getPetById = function (petId) {  
  39.             var pet = ("undefined" == typeof (__pet_config)) ? null : __pet_config["pet_" + petId];  
  40.             return pet;  
  41.         }  
  42. }  
  43. })();  

這種方式利用了JavaScript匿名函數來創建私有作用域,這些私有作用域只能在內部訪問。總結上述過程分為以下幾個步驟:

1、定義一個全局的變數(var PetConfigParser),注意變數首字母大寫與普通變數區別;

2、然後創建一個匿名函數並運行( (function () {/*xxxx*/ })(); ),在匿名函數內部創建局部變數和函數,它們只能在當前作用域中被訪問到;

3、全局變數(var PetConfigParser)可以在任何地方訪問到,在匿名函數內部操作PetConfigParser添加靜態函數。

使用實例:

  1. $(function () {  
  2.         DialogManager.init();  
  3.         $('#showDialog').click(function () {  
  4.             DialogManager.show("#msgBoxTest", "#closeId");  
  5.             return false;  
  6.         });  
  7.         $('#cofirmBtn').click(function () {  
  8.             DialogManager.hide();  
  9.             return false;  
  10.         });  
  11. }) 

實例類

JavaScript實現經典的類,總結有三種方法:

構造函數方式;

原型方式;

構造函數+原型的混合方式

構造函數方式

構造函數用來初始化實例對象的屬性和值。任何JavaScript函數都可以用作構造函數,構造函數必須使用new運算符作為前綴來創建新的實例。

  1. var Person = function (name) {  
  2.     this.name = name;  
  3.     this.sayName = function(){  
  4.         alert(this.name);  
  5.     };  
  6. }  
  7. //實例化  
  8. var tyler = new Person("tylerzhu");  
  9. var saylor = new Person("saylorzhu");  
  10. tyler.sayName();  
  11. saylor.sayName();  
  12. //檢查實例  
  13. alert(tyler instanceof Person);  

構造函數方式跟傳統的面向對象語言是不是很相識!只不過是class關鍵字用function替換了。

注意:不要省略new否則Person(“tylerzhu”) //==>undefined。當使用new關鍵字來調用構造函數時,執行上下文(context)從全局對象(window)變成一個空的上下文,這個上下文代表了新生成的實例。因此,this關鍵子指向當前創建的實例。所以省略new時,沒有進行上下文切換會在全局對象中查找name,沒有找到而創建一個全局變數name返回undefined。

原型方式

構造函數方式簡單,但是存在一個浪費內存的問題。如上面的例子中實例化了兩個對象tyler、saylor,表面上好像沒什麼問題,但是實際上對於每一個實例對象,sayName()方法都是一模一樣的內容,每一次生成一個實例,都必須為重複的內容申請內容。

alert(tyler. sayName == saylor. sayName) 輸出 false!!!

Javascript中每一個構造函數都有一個prototype屬性,指向另一個對象。這個對象的所有屬性和方法,都會被構造函數的實例共享。

  1. var Person = function (name) {  
  2.     Person.prototype = name;  
  3.     Person.prototype.sayName = function(){  
  4.         alert(this.name);  
  5.     }  
  6. }  
  7. //實例化  
  8. var tyler = new Person("tylerzhu");  
  9. var saylor = new Person("saylorzhu");  
  10. tyler.sayName();  
  11. saylor.sayName();  
  12. //檢查實例  
  13. alert(tyler instanceof Person);  

這時tyler、saylor實例的sayName方法,都是同一個內存地址(指向prototype對象),因此原型方法更節省內存。

但是看 tyler.sayName(); saylor.sayName(); 兩者輸出,會看出問題 —— 它們都輸出“saylorzhu”。因為原型所有屬性都共享,只要一個實例改變其他的都會跟著改變,所以實例化對象saylor覆蓋了tyler。

構造函數+原型的混合方式

構造函數方式可以為同一個類的每一個對象分配不同的內存,這很適合寫類的時候設置屬性;但是設置方法的時候我們就需要讓同一個類的不同對象共享同一個內存了,寫方法用原型的方式最好。所以寫類的時候需要把構造方法和原型兩種方式混合著用(很多類庫提供的創建類的方法或框架的寫類方式本質上都是:構造函數+原型)。

  1. var Person = function (name) {  
  2.     Person.prototype = name;  
  3.     Person.prototype.sayName = function(){  
  4.         alert(this.name);  
  5.     }  
  6. }   
  7. //實例化  
  8. var tyler = new Person("tylerzhu");  
  9. var saylor = new Person("saylorzhu");  
  10. tyler.sayName();  
  11. saylor.sayName();  
  12. //檢查實例  
  13. alert(tyler instanceof Person);  

這樣即可通過構造函數構造不同name的人,對象實例也都共享sayName方法,不會造成內存浪費。

JavaScript壓縮/合併

JavaScript代碼壓縮混淆的意義:簡單的說就是為了減小js文件大小,去掉多餘的註釋和換行縮進等,使得下載起來更快,提高用戶體驗。

JavaScript壓縮工具有很多,我推薦使用jQuery現在使用的工具UglifyJS(jQuery以前也使用過多種壓縮工具,如Packer),因為它壓縮性能很好。

“jQuery 1.5 發布的時候 john resig 大神說所用的代碼優化程序從Google Closure切換到UglifyJS,新工具的壓縮效果非常令人滿意”

下面是官方性能對比:We’re still a lot better than YUI in terms of compression, though slightly slower. We’re still a lot faster than Closure, and compression after gzip is comparable.

Uglifyjs安裝

UglifyJS是基於 NodeJS 的Javascript語法解析/壓縮/格式化工具,所以我們要安裝NodeJS。

Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

JavaScript最早是運行在瀏覽器中,然而瀏覽器只是提供了一個上下文,它定義了使用JavaScript可以做什麼,但並沒有“說”太多關於JavaScript語言本身可以做什麼。事實上,JavaScript是一門“完整”的語言:它可以使用在不同的上下文中,其能力與其他同類語言相比有過之而無不及。Node.js事實上就是另外一種上下文,它允許在後端(脫離瀏覽器環境)運行JavaScript代碼。
 

要實現在後台運行JavaScript代碼,代碼需要先被解釋然後正確的執行。Node.js的原理正是如此,它使用了Google的V8虛擬機(Google的Chrome瀏覽器使用的JavaScript執行環境),來解釋和執行JavaScript代碼。
 

除此之外,伴隨著Node.js的還有許多有用的模塊,它們可以簡化很多重複的勞作,比如向終端輸出字元串。因此,Node.js事實上既是一個運行時環境,同時又是一個庫。

Windows下面直接下載exe文件執行即可。(http://nodejs.org/)

C:\Users\tyler>node -v v0.8.2

設置代理(公司網路不設置代理無法下載,外網環境不需要)

“npm,全稱是"node packagemanager",它是node包管理器,第三方的package全是通過npm去安裝的。”

為npm設在代理:npm config set proxy=http://proxy.tencent.com:8080

為npm默認選擇http方式,不選用https:npm config set registry http://registry.npmjs.org

npm安裝uglify-js:npm –g install uglify-js

驗證安裝是否成功:C:\Users\tyler>npm -v 1.1.36

UglifyJS使用

uglifyjs [ 選項... ] [ 文件 ]

文件參數應該放在選項後面,uglifyjs 會讀取文件中的javascript代碼進行處理。如果你不指定輸出的文件名,那麼他會把處理后的內容輸出到命令行中。

支持的選項 :

-b 或 --beautify - 輸出格式化代碼,當傳入該參數,下面的附加選項用於更美觀的控制格式化:

-i N 或 --indent N - 縮進級別(空格數量)

-q 或 --quote-keys - 是否用引號引起字元串對象的鍵(默認只會引起不能被正確標誌的鍵名)

--ascii -默認 UglifyJS 不處理字元編碼而直接輸出 Unicode 字元,通過傳入該參數將非ASCII編碼的字元轉化為\cXXXX的序列(輸出總按照UTF8編碼,但傳入該選項能得到ASCII編碼的輸出)。

-nm 或 --no-mangle - 不改變變數名稱

-ns 或 --no-squeeze - 不調用 ast_squeeze() 函數(該函數會做多種優化使得結果更小,可讀性略有降低)

-mt 或 --mangle-toplevel - 在頂級作用域打亂變數名稱(默認不開啟)

--no-seqs - 當調用 ast_squeeze() 將會合併多個語句塊為一個語句塊,如 "a=10; b=20; foo()" 將被轉換為 "a=10,b=20,foo()"

--no-dead-code - 默認 UglifyJS 將會刪除不被用到的代碼,傳入該參數禁用此功能。

-nc 或 --no-copyright - 默認 uglifyjs 會在輸出后的代碼中添加版權信息等註釋代碼,傳入該參數禁用此功能。

-o 文件名 或 --output 文件名 - 指定輸出文件名,如果不指定,則列印到標準輸出(STDOUT)

--overwrite - 如果傳入的JS代碼來自文件而不是標準輸入,傳入該參數,輸出會覆蓋該文件。

--ast - 傳入該參數會得到抽象的語法樹而不是Javascript,對調試或了解內部代碼很有用。

-v 或 --verbose - 在標準錯誤輸出一些信息(目前的版本僅輸出操作用時)

--extra - 開啟附加優化,這些優化並未得到全面的測試。

--unsafe - 開啟其他附加優化,這些優化已知在特定情況下並不安全,目前僅支持:foo.toString() ==> foo+””

--max-line-len (默認32K位元組) - 在32K位元組出增加換行符,傳入0禁用此功能。

--reserved-names - 一些類庫會依賴一些變數,該參數指定的名稱不會被混淆掉,多個用逗號隔開

下面是我們使用uglifyjs壓縮,PetConfigParser.js的例子:

uglifyjs -nc -mt PetConfigParser.js > PetConfigParser.min.js

PetConfigParser.js壓縮前後對比

JavaScript文件合併

規則1——減少HTTP請求(Minimize HTTP Requests)

Yahoo前端優化性能規則[5]

只有10%~20%的最終用戶響應時間花在接收請求的HTML文檔上,剩下的80%~90%時間都花在HTML文檔所引用的所有組件(圖片、腳本、樣式表、Flash等)進行的HTTP請求上。因此,改善響應時間最簡單的辦法就是減少組件數量並由此減少HTTP請求數。

對公共庫合併壓縮在減少size的同時,減少http請求優化網路耗時提升性能。

文檔生成

YUIDoc 是一個基於 Node.js 的應用程序,用來根據 JavaScript 的註釋中生成 API 文檔,類似 JavaDoc、ASDoc,這也是當前 YUI 用來生成文檔的工具。

YUIDoc安裝與使用

YUIDoc安裝

與UglifyJS一樣,YUIDoc也是基於Nodejs的一個應用程序,使用npm安裝即可。

npm -g install yuidocjs.

校驗安裝是否成功

C:\Users\tyler>yuidoc -v 0. 3.15

生成文檔(一次性生成)

yuidoc .

一次性生成該目錄及其子目錄下所有JS的文檔 默認在不配置的情況下會生成在當前目錄的out目錄中。

-o, --out Path to put the generated files (defaults to ./out)

生成文檔(實時生成)

YUIDoc還提供了一種實時文檔生成的方式,有利於團隊協作開發 比如在SVN上部署YUIDoc實時文檔,遞交到SVN的代碼都會及時生成文檔提供團隊使用查閱

yuidoc --server

默認開放監聽當前目錄文件變動,開放3000埠 可以通過

http://127.0.0.1:3000/

來訪問文檔 如果3000埠被佔用,也可以指定特定埠號

yuidoc --server 5000

來通過開放5000埠提供文檔訪問

YUIDoc標籤

要使用YUIDoc,那麼所有註釋都得安裝YUIDoc的標準來,否則不能正確解析出文檔。YUIDoc使用的標籤和其它語言類同,比較容易理解。下面不詳細說明每個標籤,只列舉幾個例子,具體可參加官方文檔。例如:

對PetConfigParser類進行註釋:

  1. /**  
  2.  * 寶寶配置文件解析,及提供查詢寶寶配置相關的操作方法
     
  3.  * 1. getPetById 根據寶寶id獲取對應寶寶的信息
     
  4.  * 2. getPetName 根據寶寶的id,讀取寶寶信息,然後拼接出寶寶的名字,如3+10天蠍寶寶
     
  5.  * 等等
     
  6.  * @author tylerzhu  
  7.  * @class PetConfigParser  
  8.  * @static  
  9.  */  

對類中的變數進行註釋:

  1. /**  
  2. * 寶寶所有配置字典,以【cate * 10000 + (lvl - 1) * 10 + dex - 1】為key  
  3. * @attribute    petDic  
  4. * @type {Object}  
  5. * @private  
  6. */  

對類中函數進行註釋:

  1. /**  
  2. * 根據寶寶id,讀取__pet_config中對應寶寶的信息  
  3. * @method getPetById  
  4. * @param   {String/int} petId 寶寶id  
  5. * @return  {Object} pet 寶寶的所有靜態信息,如{id:"300003289", lvl:"1", dex:"2", price:"200", life:"2592000", cate:"3", name:"飛天小使等級1熟練2", intro:"", skill:"護身符", skill1_prob:"30", skill2_prob:"0"}  
  6. */  

默認生成的文檔樣式如下:

參考鏈接、進一步閱讀

[1]NodeJS,http://nodejs.org/

[2]UglifyJS,https://github.com/mishoo/UglifyJS/

[3]用UglifyJS解析/壓縮/格式化你的Javascript,http://goo.gl/bwf8U

[4]Yahoo前端優化性能規則,http://goo.gl/nfEBg

[5]用YUIDoc文檔化JavaScript代碼,http://goo.gl/5RJxn

[6]YUIDoc官方,http://yui.github.com/yuidoc/

原文鏈接:http://kb.cnblogs.com/page/155286/

 



[火星人 ] JavaScript項目優化總結已經有986次圍觀

http://coctec.com/docs/program/show-post-71298.html