JavaScript項(xiàng)目優(yōu)化總結(jié)
前端時間對公司已有項(xiàng)目JavaScript代碼進(jìn)行優(yōu)化,本文的是對優(yōu)化工作的一個總結(jié),拿出來與大家分享。當(dāng)然我的優(yōu)化方式可能并不是***的,或者說有些不對的地方,請指教。
JavaScript優(yōu)化總結(jié)分為以下幾點(diǎn)
優(yōu)化前后對比
模塊化(類編程):代碼清晰、有效防止變量污染問題、代碼重用方便擴(kuò)展等;
JavaScript壓縮混淆:減少size優(yōu)化加載時間,混淆保護(hù)代碼;
JavaScript文件合并:減少http請求優(yōu)化網(wǎng)絡(luò)耗時提升性能;
生成文檔:方便公共庫的使用,查找接口方便。
模塊化(類編程)
對于靜態(tài)類來說JavaScript實(shí)現(xiàn)比較簡單,使用Object直接量就已經(jīng)夠用了;但是要創(chuàng)建實(shí)例化、可繼承經(jīng)典的類需要做一番工作。因?yàn)镴avaScript是基于原型的(prototype-based)編程語言,并沒有包含內(nèi)置類的實(shí)現(xiàn)(它沒有訪問控制符,它沒有定義類的關(guān)鍵字class,它沒有支持繼承的extend或冒號,它也沒有用來支持虛函數(shù)的virtual等),但是我們通過JavaScript可以輕易地模擬出經(jīng)典的類。
靜態(tài)類
根據(jù)寶寶JS公共接口的特性,它們不需要實(shí)例化,所以優(yōu)化使用了該方式。下面以PetConfigParser為例介紹下實(shí)現(xiàn)方式:
- var PetConfigParser;
- if (!PetConfigParser) {
- PetConfigParser = {};
- }
- (function () {
- //private 變量、函數(shù)
- /**
- * 寶寶所有配置字典,以【cate * 10000 + (lvl - 1) * 10 + dex - 1】為key
- * @attribute petDic
- * @type {Object}
- * @private
- */
- var petDic = null; //寶寶字典
- /**
- * 根據(jù)__pet_config構(gòu)建一個Object字典,以cate、dex、lvl組合作為key
- * @method buildPetDic
- * @private
- * @return {void}
- */
- function buildPetDic() {
- petDic = new Object();
- for (var item in __pet_config) {
- var lvl = parseInt(__pet_config[item]['lvl']);
- var dex = parseInt(__pet_config[item]['dex']);
- var cate = parseInt(__pet_config[item]['cate']);
- var key = cate * 10000 + (lvl - 1) * 10 + dex;
- petDic[key] = __pet_config[item];
- }
- }
- //public 接口
- /**
- * 根據(jù)寶寶id,讀取__pet_config中對應(yīng)寶寶的信息
- * @method getPetById
- * @param {String/int} petId 寶寶id
- * @return {Object} pet 寶寶的所有靜態(tài)信息,如{id:"300003289", lvl:"1", dex:"2", price:"200", life:"2592000", cate:"3", name:"飛天小使等級1熟練2", intro:"", skill:"護(hù)身符", skill1_prob:"30", skill2_prob:"0"}
- */
- if (typeof PetConfigParser.getPetById !== 'function') {
- PetConfigParser.getPetById = function (petId) {
- var pet = ("undefined" == typeof (__pet_config)) ? null : __pet_config["pet_" + petId];
- return pet;
- }
- }
- })();
這種方式利用了JavaScript匿名函數(shù)來創(chuàng)建私有作用域,這些私有作用域只能在內(nèi)部訪問??偨Y(jié)上述過程分為以下幾個步驟:
1、定義一個全局的變量(var PetConfigParser),注意變量首字母大寫與普通變量區(qū)別;
2、然后創(chuàng)建一個匿名函數(shù)并運(yùn)行( (function () {/*xxxx*/ })(); ),在匿名函數(shù)內(nèi)部創(chuàng)建局部變量和函數(shù),它們只能在當(dāng)前作用域中被訪問到;
3、全局變量(var PetConfigParser)可以在任何地方訪問到,在匿名函數(shù)內(nèi)部操作PetConfigParser添加靜態(tài)函數(shù)。
使用實(shí)例:
- $(function () {
- DialogManager.init();
- $('#showDialog').click(function () {
- DialogManager.show("#msgBoxTest", "#closeId");
- return false;
- });
- $('#cofirmBtn').click(function () {
- DialogManager.hide();
- return false;
- });
- })
實(shí)例類
JavaScript實(shí)現(xiàn)經(jīng)典的類,總結(jié)有三種方法:
構(gòu)造函數(shù)方式;
原型方式;
構(gòu)造函數(shù)+原型的混合方式
構(gòu)造函數(shù)方式
構(gòu)造函數(shù)用來初始化實(shí)例對象的屬性和值。任何JavaScript函數(shù)都可以用作構(gòu)造函數(shù),構(gòu)造函數(shù)必須使用new運(yùn)算符作為前綴來創(chuàng)建新的實(shí)例。
- var Person = function (name) {
- this.name = name;
- this.sayName = function(){
- alert(this.name);
- };
- }
- //實(shí)例化
- var tyler = new Person("tylerzhu");
- var saylor = new Person("saylorzhu");
- tyler.sayName();
- saylor.sayName();
- //檢查實(shí)例
- alert(tyler instanceof Person);
構(gòu)造函數(shù)方式跟傳統(tǒng)的面向?qū)ο笳Z言是不是很相識!只不過是class關(guān)鍵字用function替換了。
注意:不要省略new否則Person(“tylerzhu”) //==>undefined。當(dāng)使用new關(guān)鍵字來調(diào)用構(gòu)造函數(shù)時,執(zhí)行上下文(context)從全局對象(window)變成一個空的上下文,這個上下文代表了新生成的實(shí)例。因此,this關(guān)鍵子指向當(dāng)前創(chuàng)建的實(shí)例。所以省略new時,沒有進(jìn)行上下文切換會在全局對象中查找name,沒有找到而創(chuàng)建一個全局變量name返回undefined。
原型方式
構(gòu)造函數(shù)方式簡單,但是存在一個浪費(fèi)內(nèi)存的問題。如上面的例子中實(shí)例化了兩個對象tyler、saylor,表面上好像沒什么問題,但是實(shí)際上對于每一個實(shí)例對象,sayName()方法都是一模一樣的內(nèi)容,每一次生成一個實(shí)例,都必須為重復(fù)的內(nèi)容申請內(nèi)容。
alert(tyler. sayName == saylor. sayName) 輸出 false!?。?/p>
Javascript中每一個構(gòu)造函數(shù)都有一個prototype屬性,指向另一個對象。這個對象的所有屬性和方法,都會被構(gòu)造函數(shù)的實(shí)例共享。
- var Person = function (name) {
- Person.prototype = name;
- Person.prototype.sayName = function(){
- alert(this.name);
- }
- }
- //實(shí)例化
- var tyler = new Person("tylerzhu");
- var saylor = new Person("saylorzhu");
- tyler.sayName();
- saylor.sayName();
- //檢查實(shí)例
- alert(tyler instanceof Person);
這時tyler、saylor實(shí)例的sayName方法,都是同一個內(nèi)存地址(指向prototype對象),因此原型方法更節(jié)省內(nèi)存。
但是看 tyler.sayName(); saylor.sayName(); 兩者輸出,會看出問題 —— 它們都輸出“saylorzhu”。因?yàn)樵退袑傩远脊蚕?,只要一個實(shí)例改變其他的都會跟著改變,所以實(shí)例化對象saylor覆蓋了tyler。
構(gòu)造函數(shù)+原型的混合方式
構(gòu)造函數(shù)方式可以為同一個類的每一個對象分配不同的內(nèi)存,這很適合寫類的時候設(shè)置屬性;但是設(shè)置方法的時候我們就需要讓同一個類的不同對象共享同一個內(nèi)存了,寫方法用原型的方式***。所以寫類的時候需要把構(gòu)造方法和原型兩種方式混合著用(很多類庫提供的創(chuàng)建類的方法或框架的寫類方式本質(zhì)上都是:構(gòu)造函數(shù)+原型)。
- var Person = function (name) {
- Person.prototype = name;
- Person.prototype.sayName = function(){
- alert(this.name);
- }
- }
- //實(shí)例化
- var tyler = new Person("tylerzhu");
- var saylor = new Person("saylorzhu");
- tyler.sayName();
- saylor.sayName();
- //檢查實(shí)例
- alert(tyler instanceof Person);
這樣即可通過構(gòu)造函數(shù)構(gòu)造不同name的人,對象實(shí)例也都共享sayName方法,不會造成內(nèi)存浪費(fèi)。
JavaScript壓縮/合并
JavaScript代碼壓縮混淆的意義:簡單的說就是為了減小js文件大小,去掉多余的注釋和換行縮進(jìn)等,使得下載起來更快,提高用戶體驗(yàn)。
JavaScript壓縮工具有很多,我推薦使用jQuery現(xiàn)在使用的工具UglifyJS(jQuery以前也使用過多種壓縮工具,如Packer),因?yàn)樗鼔嚎s性能很好。
“jQuery 1.5 發(fā)布的時候 john resig 大神說所用的代碼優(yōu)化程序從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最早是運(yùn)行在瀏覽器中,然而瀏覽器只是提供了一個上下文,它定義了使用JavaScript可以做什么,但并沒有“說”太多關(guān)于JavaScript語言本身可以做什么。事實(shí)上,JavaScript是一門“完整”的語言:它可以使用在不同的上下文中,其能力與其他同類語言相比有過之而無不及。Node.js事實(shí)上就是另外一種上下文,它允許在后端(脫離瀏覽器環(huán)境)運(yùn)行JavaScript代碼。
要實(shí)現(xiàn)在后臺運(yùn)行JavaScript代碼,代碼需要先被解釋然后正確的執(zhí)行。Node.js的原理正是如此,它使用了Google的V8虛擬機(jī)(Google的Chrome瀏覽器使用的JavaScript執(zhí)行環(huán)境),來解釋和執(zhí)行JavaScript代碼。
除此之外,伴隨著Node.js的還有許多有用的模塊,它們可以簡化很多重復(fù)的勞作,比如向終端輸出字符串。因此,Node.js事實(shí)上既是一個運(yùn)行時環(huán)境,同時又是一個庫。
Windows下面直接下載exe文件執(zhí)行即可。(http://nodejs.org/)
C:\Users\tyler>node -v v0.8.2
設(shè)置代理(公司網(wǎng)絡(luò)不設(shè)置代理無法下載,外網(wǎng)環(huán)境不需要)
“npm,全稱是"node packagemanager",它是node包管理器,第三方的package全是通過npm去安裝的。”
為npm設(shè)在代理:npm config set proxy=http://proxy.tencent.com:8080
為npm默認(rèn)選擇http方式,不選用https:npm config set registry http://registry.npmjs.org
npm安裝uglify-js:npm –g install uglify-js
驗(yàn)證安裝是否成功:C:\Users\tyler>npm -v 1.1.36
UglifyJS使用
uglifyjs [ 選項(xiàng)... ] [ 文件 ]
文件參數(shù)應(yīng)該放在選項(xiàng)后面,uglifyjs 會讀取文件中的javascript代碼進(jìn)行處理。如果你不指定輸出的文件名,那么他會把處理后的內(nèi)容輸出到命令行中。
支持的選項(xiàng) :
-b 或 --beautify - 輸出格式化代碼,當(dāng)傳入該參數(shù),下面的附加選項(xiàng)用于更美觀的控制格式化:
-i N 或 --indent N - 縮進(jìn)級別(空格數(shù)量)
-q 或 --quote-keys - 是否用引號引起字符串對象的鍵(默認(rèn)只會引起不能被正確標(biāo)志的鍵名)
--ascii -默認(rèn) UglifyJS 不處理字符編碼而直接輸出 Unicode 字符,通過傳入該參數(shù)將非ASCII編碼的字符轉(zhuǎn)化為\cXXXX的序列(輸出總按照UTF8編碼,但傳入該選項(xiàng)能得到ASCII編碼的輸出)。
-nm 或 --no-mangle - 不改變變量名稱
-ns 或 --no-squeeze - 不調(diào)用 ast_squeeze() 函數(shù)(該函數(shù)會做多種優(yōu)化使得結(jié)果更小,可讀性略有降低)
-mt 或 --mangle-toplevel - 在***作用域打亂變量名稱(默認(rèn)不開啟)
--no-seqs - 當(dāng)調(diào)用 ast_squeeze() 將會合并多個語句塊為一個語句塊,如 "a=10; b=20; foo()" 將被轉(zhuǎn)換為 "a=10,b=20,foo()"
--no-dead-code - 默認(rèn) UglifyJS 將會刪除不被用到的代碼,傳入該參數(shù)禁用此功能。
-nc 或 --no-copyright - 默認(rèn) uglifyjs 會在輸出后的代碼中添加版權(quán)信息等注釋代碼,傳入該參數(shù)禁用此功能。
-o 文件名 或 --output 文件名 - 指定輸出文件名,如果不指定,則打印到標(biāo)準(zhǔn)輸出(STDOUT)
--overwrite - 如果傳入的JS代碼來自文件而不是標(biāo)準(zhǔn)輸入,傳入該參數(shù),輸出會覆蓋該文件。
--ast - 傳入該參數(shù)會得到抽象的語法樹而不是Javascript,對調(diào)試或了解內(nèi)部代碼很有用。
-v 或 --verbose - 在標(biāo)準(zhǔn)錯誤輸出一些信息(目前的版本僅輸出操作用時)
--extra - 開啟附加優(yōu)化,這些優(yōu)化并未得到全面的測試。
--unsafe - 開啟其他附加優(yōu)化,這些優(yōu)化已知在特定情況下并不安全,目前僅支持:foo.toString() ==> foo+””
--max-line-len (默認(rèn)32K字節(jié)) - 在32K字節(jié)出增加換行符,傳入0禁用此功能。
--reserved-names - 一些類庫會依賴一些變量,該參數(shù)指定的名稱不會被混淆掉,多個用逗號隔開
下面是我們使用uglifyjs壓縮,PetConfigParser.js的例子:
uglifyjs -nc -mt PetConfigParser.js > PetConfigParser.min.js
PetConfigParser.js壓縮前后對比
JavaScript文件合并
規(guī)則1——減少HTTP請求(Minimize HTTP Requests)
Yahoo前端優(yōu)化性能規(guī)則[5]
只有10%~20%的最終用戶響應(yīng)時間花在接收請求的HTML文檔上,剩下的80%~90%時間都花在HTML文檔所引用的所有組件(圖片、腳本、樣式表、Flash等)進(jìn)行的HTTP請求上。因此,改善響應(yīng)時間最簡單的辦法就是減少組件數(shù)量并由此減少HTTP請求數(shù)。
對公共庫合并壓縮在減少size的同時,減少http請求優(yōu)化網(wǎng)絡(luò)耗時提升性能。
文檔生成
YUIDoc 是一個基于 Node.js 的應(yīng)用程序,用來根據(jù) JavaScript 的注釋中生成 API 文檔,類似 JavaDoc、ASDoc,這也是當(dāng)前 YUI 用來生成文檔的工具。
YUIDoc安裝與使用
YUIDoc安裝
與UglifyJS一樣,YUIDoc也是基于Nodejs的一個應(yīng)用程序,使用npm安裝即可。
npm -g install yuidocjs.
校驗(yàn)安裝是否成功
C:\Users\tyler>yuidoc -v 0. 3.15
生成文檔(一次性生成)
yuidoc .
一次性生成該目錄及其子目錄下所有JS的文檔 默認(rèn)在不配置的情況下會生成在當(dāng)前目錄的out目錄中。
-o, --out <directory path> Path to put the generated files (defaults to ./out)
生成文檔(實(shí)時生成)
YUIDoc還提供了一種實(shí)時文檔生成的方式,有利于團(tuán)隊(duì)協(xié)作開發(fā) 比如在SVN上部署YUIDoc實(shí)時文檔,遞交到SVN的代碼都會及時生成文檔提供團(tuán)隊(duì)使用查閱
yuidoc --server
默認(rèn)開放監(jiān)聽當(dāng)前目錄文件變動,開放3000端口 可以通過
來訪問文檔 如果3000端口被占用,也可以指定特定端口號
yuidoc --server 5000
來通過開放5000端口提供文檔訪問
YUIDoc標(biāo)簽
要使用YUIDoc,那么所有注釋都得安裝YUIDoc的標(biāo)準(zhǔn)來,否則不能正確解析出文檔。YUIDoc使用的標(biāo)簽和其它語言類同,比較容易理解。下面不詳細(xì)說明每個標(biāo)簽,只列舉幾個例子,具體可參加官方文檔。例如:
對PetConfigParser類進(jìn)行注釋:
- /**
- * 寶寶配置文件解析,及提供查詢寶寶配置相關(guān)的操作方法<br/>
- * 1. getPetById 根據(jù)寶寶id獲取對應(yīng)寶寶的信息<br/>
- * 2. getPetName 根據(jù)寶寶的id,讀取寶寶信息,然后拼接出寶寶的名字,如3+10天蝎寶寶<br/>
- * 等等<br/>
- * @author tylerzhu
- * @class PetConfigParser
- * @static
- */
對類中的變量進(jìn)行注釋:
- /**
- * 寶寶所有配置字典,以【cate * 10000 + (lvl - 1) * 10 + dex - 1】為key
- * @attribute petDic
- * @type {Object}
- * @private
- */
對類中函數(shù)進(jìn)行注釋:
- /**
- * 根據(jù)寶寶id,讀取__pet_config中對應(yīng)寶寶的信息
- * @method getPetById
- * @param {String/int} petId 寶寶id
- * @return {Object} pet 寶寶的所有靜態(tài)信息,如{id:"300003289", lvl:"1", dex:"2", price:"200", life:"2592000", cate:"3", name:"飛天小使等級1熟練2", intro:"", skill:"護(hù)身符", skill1_prob:"30", skill2_prob:"0"}
- */
默認(rèn)生成的文檔樣式如下:
參考鏈接、進(jìn)一步閱讀
[1]NodeJS,http://nodejs.org/
[2]UglifyJS,https://github.com/mishoo/UglifyJS/
[3]用UglifyJS解析/壓縮/格式化你的Javascript,http://goo.gl/bwf8U
[4]Yahoo前端優(yōu)化性能規(guī)則,http://goo.gl/nfEBg
[5]用YUIDoc文檔化JavaScript代碼,http://goo.gl/5RJxn
[6]YUIDoc官方,http://yui.github.com/yuidoc/