引爆你的Javascript代碼進化
方才在程序里看到一段JS代碼,寫法極為高明,私心想著若是其按照規范來寫,定可培養對這門語言的理解,對JS編程能力提高必是極好的。說人話:丫代碼寫的太亂,看的窩火!
最近閑暇無事,準備對自己JS學習做一個總結。眾所周知,JS是一種語法極其靈活的語言,一千個人會有一千種JS書寫方式。這造成的結果往往就是給項目日后的開發及維護留下一個不小的隱患,也對你和團隊再次開發及閱讀代碼造成一定困難,個人認為良好的書寫規范是應該首當其沖的。所以參考一些優秀前端開發團隊的代碼規范后,總結了幾點,希望能讓你的Javascript代碼上升一個臺階。
變量命名:
變量名包括全局變量,局部變量,類變量,函數參數等等,他們都屬于這一類。
變量命名都以類型前綴+有意義的單詞組成,用駝峰式命名法增加變量和函式的可讀性。例如:sUserName,nCount。
前綴規范:
每個局部變量都需要有一個類型前綴,按照類型可以分為:
s:表示字符串。例如:sName,sHtml;
n:表示數字。例如:nPage,nTotal;
b:表示邏輯。例如:bChecked,bHasLogin;
a:表示數組。例如:aList,aGroup;
r:表示正則表達式。例如:rDomain,rEmail;
f:表示函數。例如:fGetHtml,fInit;
o:表示以上未涉及到的其他對象,例如:oButton,oDate;
g:表示全局變量,例如:gUserName,gLoginTime;
當然,也可以根據團隊及項目需要增加前綴規范,例如我們團隊會用到:
$:表示Jquery對象。例如:$Content,$Module;
一種比較廣泛的Jquery對象變量命名規范。
j:表示Jquery對象。例如:jContent, jModule;
另一種Jquery對象變量命名方式。
fn:表示函數。例如:fnGetName,fnSetAge;
和上面函數的前綴略有不同,改用fn來代替,個人認為fn能夠更好的區分普通變量和函數變量。
dom:表示Dom對象,例如:domForm,domInput;
項目中很多地方會用到原生的Dom方法及屬性,可以根據團隊需要適當修改。
這里可以根據項目及團隊需要,設計出針對項目需要的前綴規范,從而達到團隊開發協作便利的目的。
例外情況:
1:作用域不大臨時變量可以簡寫,比如:str,num,bol,obj,fun,arr。
2:循環變量可以簡寫,比如:i,j,k等。
3:某些作為不允許修改值的變量認為是常量,全部字母都大寫。例如:COPYRIGHT,PI。常量可以存在于函數中,也可以存在于全局。
為什么需要這樣強制定義變量前綴?正式因為javascript是弱語言造成的。在定義大量變量的時候,我們需要很明確的知道當前變量是什么屬性,如果只通過普通單詞,是很難區分的。
//普通代碼 var checked = false; var check = function() { return true; } /** some code **/ if(check) {//已經無法很確切知道這里是要用checked還是check()從而導致邏輯錯誤 //do some thing } //規范后代碼 var bChecked = false; var fnCheck = function() { return true; } /** some code **/ if(bChecked) { // do some thing } if(fnCheck()) { // do other thing }
函數命名:
統一使用動詞或者動詞+名詞形式,例如:fnGetVersion(),fnSubmitForm(),fnInit();涉及返回邏輯值的函數可以使用is,has,contains等表示邏輯的詞語代替動詞,例如:fnIsObject(),fnHasClass(),fnContainsElment()。
如果有內部函數,使用_fn+動詞+名詞形式,內部函數必需在函數最后定義。例如:
function fnGetNumber(nTotal) { if (nTotal < 100) { nTotal = 100; } return _fnAdd(nTotal); function _fnAdd(nNumber) { nNumber++; return nNumber; } } alert(fGetNumber(10)); //alert 101
對象方法與事件響應函數:
對象方法命名使用fn+對象類名+動詞+名詞形式;例如 fnAddressGetEmail(),主觀覺得加上對象類名略有些雞肋,個人認為一個對象公開的屬性與方法應該做到簡潔易讀。多增加一個對象類名單詞,看著統一了,但有點為了規范而規范的味道,這里根據自身喜好仁者見仁智者見智吧。
某事件響應函數命名方式為fn+觸發事件對象名+事件名或者模塊名,例如:fnDivClick(),fnAddressSubmitButtonClick()
補充一些函數方法常用的動詞:
get 獲取/set 設置, add 增加/remove 刪除
create 創建/destory 移除 start 啟動/stop 停止
open 打開/close 關閉, read 讀取/write 寫入
load 載入/save 保存, create 創建/destroy 銷毀
begin 開始/end 結束, backup 備份/restore 恢復
import 導入/export 導出, split 分割/merge 合并
inject 注入/extract 提取, attach 附著/detach 脫離
bind 綁定/separate 分離, view 查看/browse 瀏覽
edit 編輯/modify 修改, select 選取/mark 標記
copy 復制/paste 粘貼, undo 撤銷/redo 重做
insert 插入/delete 移除, add 加入/append 添加
clean 清理/clear 清除, index 索引/sort 排序
find 查找/search 搜索, increase 增加/decrease 減少
play 播放/pause 暫停, launch 啟動/run 運行
compile 編譯/execute 執行, debug 調試/trace 跟蹤
observe 觀察/listen 監聽, build 構建/publish 發布
input 輸入/output 輸出, encode 編碼/decode 解碼
encrypt 加密/decrypt 解密, compress 壓縮/decompress 解壓縮
pack 打包/unpack 解包, parse 解析/emit 生成
connect 連接/disconnect 斷開, send 發送/receive 接收
download 下載/upload 上傳, refresh 刷新/synchronize 同步
update 更新/revert 復原, lock 鎖定/unlock 解鎖
check out 簽出/check in 簽入, submit 提交/commit 交付
push 推/pull 拉, expand 展開/collapse 折疊
begin 起始/end 結束, start 開始/finish 完成
enter 進入/exit 退出, abort 放棄/quit 離開
obsolete 廢棄/depreciate 廢舊, collect 收集/aggregate 聚集
上面討論了基本的JS書寫命名規范,按我個人看法,只要能夠按照上面的規范寫出來的代碼,一般不會太糟糕,最不濟沒人會說你代碼亂的難以閱讀。代碼規范對于大團隊的維護建設是毋庸置疑的,當然對于個人的代碼素養也是很有幫助的,希望你能夠通過上文能夠加強你的代碼規范,寫出易讀易維護的代碼。
代碼的規范屬于戰術上的進化,下面我們再來看看如何讓你的Javascript在戰略上進化。
面向對象書寫Javascript
面向對象書寫Javascript想必你一定不會陌生,但我敢說,大多數前端一般不會通過面向對象來書寫JS代碼。第一是Javascript本身的語言機制原因造成面向對象的書寫困難,由于Javascript是原型式繼承又是一個類C的語言,他的面向對象的東西相對于C++/Java比較奇怪。第二是Javascript作為一種語法極其靈活的語言,直接導致了面向對象書寫JS又有多種寫法,讓許多初學者分不清到底哪個才是正確的寫法。基于上述和個人的經驗推薦如下倆種書寫方式:
第一類:
(function(){ function Circle(nRadius){ this.nR = nRadius; } Circle.prototype = { PI : 3.14, fnGetArea : function(){ return this.PI * this.nR * this.nR; } } var c1 = new Circle(5); alert(c1.fnGetArea()); //78.5 })();
上面這種可以說是很標準的面向對象JS書寫方式了我們又稱之為工廠模式,優點就是簡單容易上手,新手常常喜歡這么寫。以上代碼略微做些改動,會有如下這個變種:
(function(){ function Circle(nRadius, sMessage){ this.init.apply(this, arguments); } Circle.prototype = { init : function(nRadius, sMessage){ this.nR = nRadius; this.sMessage = sMessage; }, PI : 3.14, fnGetArea : function(){ return this.sMessage + ": " + this.PI * this.nR * this.nR; } } var c = new Circle(5, "構造初始化 面積"); alert(c.fnGetArea()); //構造初始化 面積: 78.5 })();
上面這個變種,就比較有意思了,this.init.apply(this, arguments);這行代碼把初始化的任務交接給了init()方法,這么做的好處就是可以把所有初始化的東西都放在一個地方進行,增加可閱讀性,需要理解一定的Javascript運行機制原理。
第二類:
(function(){ function Circle(){ } Circle.prototype = { init : function(nRadius, sMessage){ this.nR = nRadius; this.sMessage = sMessage; }, PI : 3.14, fnGetArea : function(){ return this.sMessage + ": " + this.PI * this.nR * this.nR; } } var c = new Circle(); c.init(5, "手動構造初始化 面積"); alert(c.fnGetArea()); //手動構造初始化 面積: 78.5 })();
這類寫法是我現在比較喜歡的寫法,簡潔高效。從書寫角度來看省去了構造函數初始化屬性,改用其init()中初始化。另一個好處在于不在new Circle()構造之初傳入參數,個人認為當new構造對象的時候,最好不要摻雜參數,這樣做很“危險”,改為“手動型”初始化更易于代碼排查修改。當然這種寫法還有一個原因就是他可以很好的轉換成一般前端接受的“封裝型”代碼,我們把上面的代碼也略微改動一下:
(function(){ var Circle = { init : function(nRadius, sMessage){ this.nR = nRadius; this.sMessage = sMessage; }, PI : 3.14, fnGetArea : function(){ return this.sMessage + ": " + this.PI * this.nR * this.nR; } } Circle.init(5, "封裝型 面積"); alert(Circle.fnGetArea()); //封裝型 面積: 78.5 })();
是不是對上面這類代碼很熟悉,很多網站上的例子都是使用這類封裝型代碼書寫的,優點是代碼的封裝性良好,可以有效的重用,多用于頁面功能性效果實現,封裝一個Tab控件、封裝一個跑馬燈效果等等。缺點就是不能很好的用作繼承,這是和上面三種格式最大區別。可話又說回來一般JS代碼很少會用到繼承的地方,除非是寫一個大型庫(類似YUI)會用到繼承外,一般寫一個功能模塊用到封裝型代碼就夠用了,所以大多數前端喜歡使用這類封裝型的書寫風格。
上面介紹了2類4種面向對象的寫法,一般面向對象書寫格式基本都在上面了,熟悉面向對象書寫可以有效的增加你對JS的理解。熟練使用上面4中寫法也能夠很好的在工作中給代碼維護修改帶來便利。最后我們再來談一個技巧,讓你的Javascript代碼在技巧上進化。
用對象字面量構造對象
一個對象字面量就是包含在一對花括號中的0個或多個“名/值”對。上文在面向對象書寫格式的時候我們就大量的使用了對象字面量的書寫格式。
對象字面量書寫Javascript可以很好的簡化代碼,又能極大的增加代碼可讀性,尤其作為參數使用可以有化腐朽為神奇的表現。我們看下面代碼:
(function(){ function Person(sName, nAge, nWeight, bSingle){ this.sName = sName; this.nAge = nAge; this.nWeight = nWeight; this.bSingle = bSingle; } Person.prototype.showInfo = function(){ return this.sName + " " + this.nAge + " " + this.nWeight + " " + this.bSingle; } var p = new Person("海玉", 25, 75, true); alert(p.showInfo()); //海玉 25 75 true })();
上面是一個很標準的工廠模式,一般而言這類寫法屬于那種規規矩矩沒有大錯也沒有亮點的代碼,而且參數不少,一個不小心還會傳入錯誤的參數,而應用對象字面量技巧可以很好的規避此類問題,我們來看改動過后的代碼:
(function(){ function Person(){ } Person.prototype = { init : function(option){ if(typeof option == "undefined"){ option = {}; } this.sName = option.sName || "海玉"; this.nAge = option.nAge || 25; this.nWeight = option.nWeight || 75; this.bSingle = (typeof option.bSingle != "undefined") ? option.bSingle : true; }, showInfo : function(){ return this.sName + " " + this.nAge + " " + this.nWeight + " " + this.bSingle; } } var p = new Person(); p.init({ nWeight : 80, sName : "Hank" }) alert(p.showInfo()); //Hank 25 80 true })();
這里使用第三種面向對象寫法,有興趣的朋友可以自行嘗試改成封裝型寫法。上面的改寫看出哪里改動最大嗎?對的,傳入參數改成了一個對象字面量,而且傳入參數可以是隨意設置,位置顛倒也不會有任何問題。這里充分利用對象字面量優點,利用鍵值對代替原始的傳參方式大大提升了可讀性和容錯性。還有一個改進就是默認值的處理,如果沒有傳入任何參數,此代碼也能很好的運行下去,不會有任何問題。
注1:這里形參命名為option,沒有遵守上面的變量命名規范是為了方便書寫與閱讀,具體情況具體分析。注2:由于||運算符對于布爾值默認賦值會出現賦值問題,所以要先進行判斷是否為undefined,再利用三元運算符可以很好的完成布爾值的默認值賦值。
總的來說上面闡述的代碼進化只能算的上是“修身”層面,想要真正的讓代碼“修心”還是得多寫,多看,多練。只有這樣你的代碼才會更精練,更有擴展性,更好的維護性。
林林總總寫了這些個總結,一來是對自己學習的記錄,二來也是想同大家探討JS還有哪些地方有潛力可挖,腦子里還有許多零零碎碎的片段,待日后再次整理驗證再與大家一起分享吧。