轉(zhuǎn)帖|其它|編輯:郝浩|2011-03-17 13:49:37.000|閱讀 415 次
概述:這篇文章不僅僅從代碼本身來(lái)考慮如何優(yōu)化編碼,也從代碼的設(shè)計(jì)階段來(lái)考慮,包括書寫API文檔,同事的review,使用JSLint。這些習(xí)慣都能幫助你編寫更加高質(zhì)量的、更易于理解的、可維護(hù)的代碼(讓你的代碼在多年之后仍使你引以為傲)。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
優(yōu)秀的Stoyan Stefanov在他的新書中(《Javascript Patterns》)介紹了很多編寫高質(zhì)量代碼的技巧,比如避免使用全局變量,使用單一的var關(guān)鍵字,循環(huán)式預(yù)存長(zhǎng)度等等。
這篇文章不僅僅從代碼本身來(lái)考慮如何優(yōu)化編碼,也從代碼的設(shè)計(jì)階段來(lái)考慮,包括書寫API文檔,同事的review,使用JSLint。這些習(xí)慣都能幫助你編寫更加高質(zhì)量的、更易于理解的、可維護(hù)的代碼(讓你的代碼在多年之后仍使你引以為傲)。
軟件的BUG修復(fù)需要花費(fèi)大量的精力。尤其當(dāng)代碼已經(jīng)發(fā)布之后,隨著時(shí)間的增長(zhǎng),維護(hù)的成本愈發(fā)的高。當(dāng)你一發(fā)現(xiàn)BUG的時(shí)候,就立即去修復(fù),這時(shí)候你的代碼還是熱乎的,你也不需要回憶,因?yàn)榫褪莿倓倢懞玫摹5钱?dāng)你做了其他任務(wù),幾乎完全忘記了這份代碼,這時(shí)候就需要:
另外一個(gè)問題是,在大項(xiàng)目或者大公司里面,經(jīng)常是解決BUG的人不是產(chǎn)生BUG的人,而且也不是發(fā)現(xiàn)BUG的人。所以減少理解代碼的時(shí)間就是最重要的問題,無(wú)論這個(gè)代碼是你自己以前寫的還是團(tuán)隊(duì)中的其他成員寫的,因?yàn)槲覀兌枷肴ジ愀阈碌挠幸馑嫉臇|西,而不是去維護(hù)那些個(gè)陳舊的代碼。
還有一個(gè)開發(fā)中的普遍問題就是,往往讀代碼的時(shí)間比寫代碼的時(shí)間還要多。有時(shí)候你鉆研一個(gè)問題,可以花整整一個(gè)下午的時(shí)間來(lái)考慮代碼的編寫。這個(gè)代碼當(dāng)時(shí)是可以工作的,但是隨著開發(fā)的進(jìn)行,其他東西發(fā)生了很大的變化,這時(shí)候也就需要你自己來(lái)重新審查修改編寫代碼。比如:
因?yàn)檫@些原因,也許你當(dāng)時(shí)一個(gè)下午寫好的代碼,后面需要花費(fèi)幾周的時(shí)間來(lái)閱讀。所以編寫可維護(hù)的代碼對(duì)于軟件的成功至關(guān)重要。
可維護(hù)的代碼包括:
Javascript使用函數(shù)來(lái)約定作用域。一個(gè)在函數(shù)內(nèi)部聲明的變量在外部是不可見的。所以,全局變量也就是聲明在任何函數(shù)之外的或者沒有被聲明的變量。
Javascript中,在任何函數(shù)之外有個(gè)可訪問的全局對(duì)象,每一個(gè)你創(chuàng)建的全局變量都是這個(gè)對(duì)象的一個(gè)屬性。在瀏覽器中,為了方便,通常用window來(lái)指代這個(gè)全局變量。下面的代碼就是說(shuō)明如何創(chuàng)建一個(gè)全局變量:
myglobal = "hello"; // antipattern
console.log(myglobal); // "hello"
console.log(window.myglobal); // "hello"
console.log(window["myglobal"]); // "hello"
console.log(this.myglobal); // "hello
全局變量的問題在于,他在你的所有代碼或者一個(gè)頁(yè)面中都共享。他們?cè)谕粋€(gè)命名空間下面,這通常會(huì)造成變量名沖突–兩個(gè)同名的變量,但是確實(shí)不同的用處。
通常在一些頁(yè)面中需要引入一些其他人的代碼,比如:
加入其中一個(gè)第三方組件定義了一個(gè)全局變量:result。然后在你的程序中,也定義了一個(gè)全局變量result。最后的這個(gè)result會(huì)覆蓋點(diǎn)之前的result,這樣第三方的腳本就會(huì)停止工作。
所以,為了對(duì)其他的腳本友好,在一個(gè)頁(yè)面中使用越少的全局變量越好。在后面會(huì)有一些方法來(lái)告訴你如何減少全局變量,比如使用命名空間,或者自執(zhí)行的匿名函數(shù),但是最好的避免全局變量的方法就是使用var關(guān)鍵字來(lái)聲明變量。
因?yàn)閖avascript的兩個(gè)特性,創(chuàng)建一個(gè)全局變量非常的簡(jiǎn)單。第一,你可以使用一個(gè)甚至沒有聲明的變量,第二,在javascript中,所有未聲明的變量都會(huì)成為全局對(duì)象的一個(gè)屬性(就像一個(gè)聲明了的全局變量一樣)。看看這個(gè)例子:
function sum(x,y){
result = x + y;
return result;
}
在這個(gè)代碼中,result在沒有被聲明的情況下就被使用了,這個(gè)代碼也能很好的工作,但是在調(diào)用了這個(gè)函數(shù)之后,就會(huì)多一個(gè)名為result的全局變量,這是所有問題的根源了。
解決這個(gè)問題的辦法就是使用var:
function sum(x,y){
var result = x + y;
return result;
}
兩外一個(gè)不好的習(xí)慣就是在聲明變量的時(shí)候使用鏈?zhǔn)降姆椒▉?lái)賦值,這時(shí)候,a是局部變量,但是b就成為了全局變量。
function foo(){
var a=b=0;
....
}
這是因?yàn)椋琤 = 0這個(gè)表達(dá)式先執(zhí)行,執(zhí)行的時(shí)候b并沒有被聲明,所以b就成為了全局變量,然后返回這個(gè)表達(dá)式的值0,給聲明了的變量a,換句話說(shuō),就好像你輸入的是:
var a = (b=0);
如果你已經(jīng)聲明變量,那么這種鏈?zhǔn)降馁x值沒有問題:
function foo(){
var a,b;
...
}
另外一個(gè)避免使用全局變量的原因是考慮到程序的可移植性。如果你想讓你的代碼在不同的環(huán)境中都可以工作,那么使用全局變量就很可能會(huì)與新的系統(tǒng)中的全局變量沖突(或許在之前的系統(tǒng)中沒有問題)。
使用var聲明的全局變量和沒有使用var生成的全局變量還有一個(gè)區(qū)別在于刪除:
使用var聲明創(chuàng)建的全局變量不能被刪除
沒有使用var聲明的全局變量可以被刪除
這說(shuō)明沒有使用var聲明生成的全局變量不是真正的變量,他們只是全局對(duì)象的屬性。屬性可以通過delete刪除,但是變量不行:
// define three globals
var global_var = 1;
global_novar = 2; // antipattern
(function () {
global_fromfunc = 3; // antipattern
}());
// attempt to delete
delete global_var; // false
delete global_novar; // true
delete global_fromfunc; // true
// test the deletion
typeof global_var; // "number"
typeof global_novar; // "undefined"
typeof global_fromfunc; // "undefined"
在ES5的嚴(yán)格模式下,給一個(gè)為聲明的變量賦值會(huì)報(bào)錯(cuò)。
在瀏覽器中,你可以通過window變量來(lái)讀取全局對(duì)象(除非你在函數(shù)內(nèi)部重新定義了window對(duì)象)。但在有的環(huán)境中,可能不叫window,那么你可以使用下面的代碼來(lái)獲取全局對(duì)象:
var global = (function(){
return this;
})();
這樣可以獲取到全局對(duì)象的原因是在function的內(nèi)部,this指向全局對(duì)象。但是這在ES5的嚴(yán)格模式下會(huì)不起作用,你需要適配一些其他模式。當(dāng)你開發(fā)自己的庫(kù)的時(shí)候,你可以把你的代碼封裝在一個(gè)立即函數(shù)中,然后將this作為一個(gè)參數(shù)傳進(jìn)來(lái)。
在你的代碼的頂部只是用一個(gè)var關(guān)鍵字,會(huì)有以下的好處:
書寫很簡(jiǎn)單:
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i,
j;
// function body...
}
通過一個(gè)var和逗號(hào)來(lái)聲明多個(gè)變量。在聲明的時(shí)候給變量賦默認(rèn)值也是不錯(cuò)的做法,可以避免一些邏輯錯(cuò)誤,提高代碼的可讀性。而后你閱讀的代碼的時(shí)候也可以根據(jù)變量的默認(rèn)值來(lái)方便的猜測(cè)變量的用途。
你也可以在聲明變量的時(shí)候做一些實(shí)際的工作,比如sum = a + b;另外,在操作DOM元素的時(shí)候,你也可以把DOM元素的引用保存在一個(gè)變量中:
function updateElement() {
var el = document.getElementById("result"),
style = el.style;
// do something with el and style...
}
JavaScript允許你在函數(shù)內(nèi)部有多個(gè)var語(yǔ)句,但是卻都表現(xiàn)的如同在函數(shù)的頂部聲明一樣。這個(gè)特性在你使用一個(gè)變量然后在后面又聲明了這個(gè)變量時(shí)會(huì)導(dǎo)致一些奇怪的邏輯問題。對(duì)于JavaScript來(lái)說(shuō),只要變量在同一個(gè)作用域,那么就認(rèn)為是聲明了的,就算是在var語(yǔ)句之前使用也一樣。看看這個(gè)例子:
myname = "global"; // global variable
function func() {
alert(myname); // "undefined"
var myname = "local";
alert(myname); // "local"
}
func();
在這個(gè)例子中,或許你期望第一次會(huì)彈出global,第二次彈出local。因?yàn)榈谝淮蔚臅r(shí)候沒有還沒有使用var聲明myname,這是應(yīng)該是全局變量的myname,第二次聲明了,然后alert之后應(yīng)該是local的值。而事實(shí)上不是這樣的,只要你在函數(shù)中出現(xiàn)了var myname,那么js就認(rèn)為你在這個(gè)函數(shù)中聲明了這個(gè)變量,但是在讀取這個(gè)變量的值的時(shí)候,因?yàn)関ar語(yǔ)句還沒有執(zhí)行,所以是undefined,很奇怪的邏輯吧。上面的代碼相當(dāng)于:
myname = "global"; // global variable
function func() {
var myname; // same as -> var myname = undefined;
alert(myname); // "undefined"
myname = "local";
alert(myname); // "local"
}
func();
我們來(lái)解釋一下這個(gè)現(xiàn)象,在代碼的解析中,分兩個(gè)步驟,第一步先處理變量函數(shù)的聲明,這一步處理整個(gè)代碼的上下文。第二步就是代碼的運(yùn)行時(shí),創(chuàng)建函數(shù)表達(dá)式以及未定義的變量。實(shí)際上,我們只是假設(shè)了這個(gè)概念,這并不在ECMAScript的規(guī)范中,但是這個(gè)行為常常就是這樣解釋的。
在for循環(huán)中你會(huì)去迭代一些數(shù)組元素或者一些HTML元素。for循環(huán)常常如此:
for (var i = 0; i < myarray.length; i++) {
// do something with myarray[i]
}
這樣寫的問題在于,每一次迭代的時(shí)候都會(huì)計(jì)算數(shù)組的長(zhǎng)度,尤其在這個(gè)參數(shù)不是一個(gè)數(shù)組而是一組HTML元素的時(shí)候會(huì)降低你的程序的性能。
HTML元素的集合在頁(yè)面上,這樣每次都會(huì)去再頁(yè)面上查找相應(yīng)的元素,這是非常耗時(shí)的。所以對(duì)于for循環(huán),你需要預(yù)先保存數(shù)組的長(zhǎng)度,這樣寫:
for (var i = 0, max = myarray.length; i < max; i++) {
// do something with myarray[i]
}
這樣緩存了參數(shù)的長(zhǎng)度,在每次迭代的時(shí)候就不用再去查找計(jì)算了。
在查找HTML元素集合的時(shí)候,緩存參數(shù)長(zhǎng)度可以帶來(lái)可觀的性能提升,Safari下面提高兩倍的速度,在IE7下面提高190倍的速度。
需要注意的是,當(dāng)你需要操作修改DOM元素的數(shù)量的時(shí)候,你肯定希望這個(gè)值是隨時(shí)更新的而不是一個(gè)常量。
使用下面的單一var模式,你也可以把var提到循環(huán)之外:
function looper() {
var i = 0,
max,
myarray = [];
// ...
for (i = 0, max = myarray.length; i < max; i++) {
// do something with myarray[i]
}
}
這個(gè)模式可以增強(qiáng)整個(gè)代碼的連續(xù)性,但是不好的一點(diǎn)是當(dāng)你重構(gòu)代碼的時(shí)候復(fù)制粘貼就沒那么容易了。例如:如果你想在其他函數(shù)中也使用這個(gè)循環(huán),那你需要確定在新的函數(shù)中處理好了i和max(或許還需要?jiǎng)h掉這個(gè))。
這個(gè)函數(shù)還有兩個(gè)點(diǎn)可以優(yōu)化的:
所以就可以寫為:
var i, myarray = [];
for (i = myarray.length; i--;) {
// do something with myarray[i]
}
針對(duì)第二點(diǎn):
var myarray = [],
i = myarray.length;
while (i--) {
// do something with myarray[i]
}
這是兩個(gè)比較微小的點(diǎn)的優(yōu)化。另外,JSLint可能對(duì)于i–會(huì)有意見。
for-in循環(huán)用來(lái)迭代非數(shù)組的對(duì)象。使用for-in循環(huán)通常也成為枚舉。
從技術(shù)上來(lái)說(shuō),你也可以用for-in來(lái)循環(huán)數(shù)組,因?yàn)閿?shù)組也是對(duì)象,但是不推薦。如果數(shù)組有一些自定義的擴(kuò)展函數(shù),那么就會(huì)出錯(cuò)。另外,對(duì)象屬性的順序在for-in循環(huán)中也是不確定的。所以最好還是用普通的循環(huán)來(lái)循環(huán)數(shù)組用for-in來(lái)循環(huán)對(duì)象。
在循環(huán)對(duì)象的過程中,使用hasOwnProperty()方法來(lái)檢驗(yàn)是對(duì)象本身的屬性還是原型鏈上的屬性很重要。
看看下面的這個(gè)例子。
// the object
var man = {
hands: 2,
legs: 2,
heads: 1
};
// somewhere else in the code
// a method was added to all objects
if (typeof Object.prototype.clone === "undefined") {
Object.prototype.clone = function () {};
}
在這個(gè)例子中,我們有一個(gè)簡(jiǎn)單的稱作man的對(duì)象字面量。在其他man定義之前或之后的地方,對(duì)象原型有一個(gè)很有用的clone()方法。因?yàn)樵?鏈的原因,所有的對(duì)象都自動(dòng)獲得了這個(gè)方法。為了在枚舉man對(duì)象的時(shí)候出現(xiàn)clone方法,你需要使用hasOwnProperty方法來(lái)區(qū)別。如果沒有區(qū)別來(lái)自原型鏈的方法,那么就會(huì)有一些意想不到的事情發(fā)生:
// 1.
// for-in loop
for (var i in man) {
if (man.hasOwnProperty(i)) { // filter
console.log(i, ":", man[i]);
}
}
/* result in the console
hands : 2
legs : 2
heads : 1
*/
// 2.
// antipattern:
// for-in loop without checking hasOwnProperty()
for (var i in man) {
console.log(i, ":", man[i]);
}
/*
result in the console
hands : 2
legs : 2
heads : 1
clone: function()
*/
另外一種使用方法如下:
for (var i in man) {
if (Object.prototype.hasOwnProperty.call(man, i)) { // filter
console.log(i, ":", man[i]);
}
}
這樣寫的好處是可以防止man重新定義了hasOwnProperty方法導(dǎo)致的沖突。如果不想寫這么長(zhǎng)的一串,你也可以這樣:
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
if (hasOwn.call(man, i)) { // filter
console.log(i, ":", man[i]);
}
}
嚴(yán)格意義上講,不適用hasOwnProperty也不是什么錯(cuò)誤。根據(jù)任務(wù)的難度和你對(duì)代碼的自信程度,你也可以不用這個(gè)直接循環(huán)。但是當(dāng)你不確定的時(shí)候,最好還是使用這個(gè)方法檢測(cè)一下。
另外一種格式上的改變(不會(huì)通過jsLint的檢查),去掉for的大括號(hào),然后把if放在同一行。這樣做的好處可以讓循環(huán)體更加突出,縮進(jìn)也就少一些:
// Warning: doesn't pass JSLint
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) if (hasOwn.call(man, i)) { // filter
console.log(i, ":", man[i]);
}
擴(kuò)展原型的構(gòu)造函數(shù),可以提供一些很強(qiáng)大的功能,但是有時(shí)候他太強(qiáng)大了。
有時(shí)候你會(huì)去擴(kuò)展Object(),Array(),Fucntion()的原型方法,這樣會(huì)導(dǎo)致可維護(hù)性的問題,因?yàn)檫@會(huì)讓你的代碼的移植性變差。其他的開發(fā)人員使用你的代碼的時(shí)候,可能只需要原生的方法,并不需要額外的功能。
另外,你添加進(jìn)去的方法,如果在循環(huán)的時(shí)候沒有使用hasOwnProperty方法就會(huì)被遍歷出來(lái),這會(huì)讓人很迷惑。
所以,最好還是不要擴(kuò)展基本的對(duì)象。除非是下面的情況:
如果在這些情況之下,那么你就可以添加,最好是下面這種形式:
if (typeof Object.prototype.myMethod !== "function") {
Object.prototype.myMethod = function () {
// implementation...
};
}
按照下面的風(fēng)格寫switch的話,可以提高你的代碼可讀性和健壯性:
var inspect_me = 0,
result = '';
switch (inspect_me) {
case 0:
result = "zero";
break;
case 1:
result = "one";
break;
default:
result = "unknown";
}
需要注意下面幾個(gè)方面:
Javascript在你比較兩個(gè)變量的時(shí)候會(huì)進(jìn)行類型的轉(zhuǎn)換,這就是為什么 false == 0或者”" == 0會(huì)返回true。
為了避免這種隱藏的類型轉(zhuǎn)換帶來(lái)的迷惑,最好使用===或者!==操作符來(lái)比較:
var zero = 0;
if (zero === false) {
// not executing because zero is 0, not false
}
// antipattern
if (zero == false) {
// this block is executed...
}
還有另外一種流派持這樣的觀點(diǎn):當(dāng)==夠用時(shí)使用===就是多余的。比如,當(dāng)你使用typeof的時(shí)候你知道會(huì)返回string,所以沒必要使用嚴(yán)格的檢驗(yàn)。然而,JSLint要求嚴(yán)格檢驗(yàn);他最大程度使代碼在閱讀的時(shí)候減少歧義,(“這個(gè)==是故意呢還是疏漏?”)。
如果你在你的代碼中使用。如果代碼是在運(yùn)行時(shí)動(dòng)態(tài)確定的,那么也有其他更安全的辦法。例如使用方括號(hào)形式訪問元素的屬性:
// antipattern
var property = "name";
alert(;
// preferred
var property = "name";
alert(obj[property]);
使用還有安全問題,比如你運(yùn)行網(wǎng)絡(luò)上的一段代碼,而這段代碼又被別人篡改了。在處理Ajax請(qǐng)求返回的JSON數(shù)據(jù)的時(shí)候,最好還是使用瀏覽器內(nèi)建的處理方法,如果對(duì)于低端的瀏覽器不支持的,可以從JSON.org上下載對(duì)應(yīng)的處理庫(kù)。
另外還要記住使用setTimeout、setInterval以及Function的構(gòu)造函數(shù)的是,傳入的字符串的參數(shù),js的處理方法跟類似,所以也要注意。因為,js會(huì)把你傳入的字符串解析執(zhí)行:
// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);
// preferred
setTimeout(myFunc, 1000);
setTimeout(function () {
myFunc(1, 2, 3);
}, 1000);
使用Function的構(gòu)造函數(shù),跟生成全局變量的辦法就是使用匿名函數(shù)。
看看下面這個(gè)例子,只有un變量最終是全局的:
console.log(typeof un); // "undefined"
console.log(typeof deux); // "undefined"
console.log(typeof trois); // "undefined"
var jsstring = "var un = 1; console.log(un);";
; // logs "1"
jsstring = "var deux = 2; console.log(deux);";
new Function(jsstring)(); // logs "2"
jsstring = "var trois = 3; console.log(trois);";
(function () {
;
}()); // logs "3"
console.log(typeof un); // number
console.log(typeof deux); // undefined
console.log(typeof trois); // undefined
會(huì)影響到作用域,而Function則相當(dāng)于一個(gè)沙盒。例如:
(function () {
var local = 1;
; // logs 3
console.log(local); // logs 3
}());
(function () {
var local = 1;
Function("console.log(typeof local);")(); // logs undefined
}());
使用parseInt()你可以將字符串轉(zhuǎn)為數(shù)字。這個(gè)方法支持第二個(gè)表示進(jìn)制的參數(shù),常常被忽略。問題常常在處理一段以0開始的字符串的時(shí)候。在ECMAS3標(biāo)準(zhǔn)中,以0開始表示八進(jìn)制,但是在ES5中又改了,所以為了避免麻煩,最好還是標(biāo)明第二個(gè)參數(shù)。
var month = "06",
year = "09";
month = parseInt(month, 10);
year = parseInt(year, 10);
在這個(gè)例子中,如果你使用parseInt(year),就會(huì)返回0,因?yàn)?9被認(rèn)為是8進(jìn)制數(shù)字,然而9是非法的八進(jìn)制字符,所以返回0。
其他的可以把字符串轉(zhuǎn)為數(shù)字的方法有:
+"08" // result is 8
Number("08") // 8
這些通常都比parseInt()快一些,因?yàn)閜arseInt并不只是簡(jiǎn)單的轉(zhuǎn)換。但是如果你的輸入是”08 hello”這樣的,那么parseInt()也會(huì)返回8,但是其他的方法就只能返回NaN。
編碼的時(shí)候遵循一定的規(guī)范,可以讓你的代碼增強(qiáng)可移植性,并且更加便于閱讀和理解。加入團(tuán)隊(duì)的新人,在閱讀了代碼規(guī)范之后,可以更加快速的溶入團(tuán)隊(duì),并理解其他人員開發(fā)的代碼。
在一些討論會(huì)議上,規(guī)范往往都是爭(zhēng)論的焦點(diǎn)(比如縮進(jìn)的形式)。所以如果你打算為你團(tuán)隊(duì)的編碼規(guī)范提一些建議,那就準(zhǔn)備好一場(chǎng)激烈的辯論和反對(duì)意見。要記住,建立和實(shí)施規(guī)范是非常重要的。
代碼如果沒有縮進(jìn),那基本上沒法閱讀了。比這更糟的是不規(guī)范的縮進(jìn),看著好像縮進(jìn)了,但是亂七八糟摸不著頭腦。所以縮進(jìn)的使用必須規(guī)范。
有些開發(fā)人員喜歡使用tab鍵來(lái)縮進(jìn),因?yàn)樵诿恳粋€(gè)編輯器里面都可以自己設(shè)置想要的tab值。有的人喜歡四個(gè)空格。如果團(tuán)隊(duì)遵循統(tǒng)一的規(guī)范,這也不是什么問題。比如本文就是四個(gè)空格,這也是JSLint推薦的。
那么什么該縮進(jìn)呢?很簡(jiǎn)單,大括號(hào)。這樣就是說(shuō)包括函數(shù)體,循環(huán),ifs,switch,以及對(duì)象字面量的屬性。看看這個(gè)例子:
function outer(a, b) {
var c = 1,
d = 2,
inner;
if (a > b) {
inner = function () {
return {
r: c - d
};
};
} else {
inner = function () {
return {
r: c + d
};
};
}
return inner;
}
應(yīng)該使用大括號(hào),尤其在那些可用可不用的地方,如果你的if語(yǔ)句或者for循環(huán)只有一句話,那么大括號(hào)不是必須的,但是這種時(shí)候最好用大括號(hào)。這可以讓代碼保持一致,并且便于升級(jí)。
假設(shè)你的for循環(huán)只有一句。你可以不用大括號(hào),也不會(huì)有什么錯(cuò)誤。
// bad practice
for (var i = 0; i < 10; i += 1)
alert(i);
但是假如你以后要在這個(gè)循環(huán)里面添加其他東西呢?
// bad practice
for (var i = 0; i < 10; i += 1)
alert(i);
alert(i + " is " + (i % 2 ? "odd" : "even"));
這時(shí)候,雖然第二個(gè)alert有縮進(jìn),但他還是在循環(huán)之外的。所以,無(wú)論何時(shí),都應(yīng)該是用大括號(hào)。if語(yǔ)句一樣:
// bad
if (true)
alert(1);
else
alert(2);
// better
if (true) {
alert(1);
} else {
alert(2);
}
開發(fā)人員也經(jīng)常爭(zhēng)論大括號(hào)的位置,放在同一行還是下一行呢?
在具體的例子中,這是個(gè)見仁見智的問題。但也有例外,假如程序根據(jù)不同的位置做不同的解析呢?這是因?yàn)椴迦敕痔?hào)機(jī)制,js對(duì)此并不挑剔,他會(huì)在你沒有添加分號(hào)的行之后幫你添加。這在函數(shù)返回一個(gè)對(duì)象字面量然后大括號(hào)寫在下一行的時(shí)候出問題:
// warning: unexpected return value
function func() {
return
// 下面的讀取不到
{
name : "Batman"
}
}
如果你想讓這個(gè)函數(shù)返回一個(gè)有name屬性的對(duì)象字面量,這個(gè)函數(shù)是做不到的,因?yàn)椴迦氲姆痔?hào),返回的應(yīng)該是一個(gè)undefied值。
所以,最后的結(jié)論是,必須使用大括號(hào),并且寫在同一行。
function func() {
return {
name : "Batman"
};
}
關(guān)于分號(hào):跟大括號(hào)一樣,必須寫。這不只是推行嚴(yán)格的寫程序的規(guī)范,更是在必要的時(shí)候解決一些不清楚的地方,比如前面的例子。
正確的使用空格也可以增加程序的可讀性和連貫性。寫句子的時(shí)候你會(huì)在逗號(hào)和句號(hào)之后有一些停頓。在js中可以模仿這樣的邏輯。
應(yīng)該使用空格地方有:
另外一些使用空格比較好的地方就是在那些操作符的兩邊,比如+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=,等等。
// generous and consistent spacing
// makes the code easier to read
// allowing it to "breathe"
var d = 0,
a = b + 1;
if (a && b && c) {
d = a % c;
a += d;
}
// antipattern
// missing or inconsistent spaces
// make the code confusing
var d = 0,
a = b + 1;
if (a && b && c) {
d = a % c;
a += d;
}
最后一個(gè)關(guān)于空格要注意的,大括號(hào)前面的空格。最好使用空格:
反對(duì)增加空格的一個(gè)說(shuō)法是增加文件體積,但是在壓縮之后并不存在這個(gè)問題。提高代碼可讀性經(jīng)常被忽視的一個(gè)方面就是垂直的空格,你可以使用空行來(lái)分開代碼,就好像寫文章時(shí)候的段落一樣。
可以提高代碼移植性和可維護(hù)性的一個(gè)方面是命名規(guī)范。也就是說(shuō),在取變量名的時(shí)候總是采取一貫的做法。
無(wú)論采用什么樣的命名規(guī)范,其實(shí)都不是很重要,重要的是確定下來(lái)這個(gè)規(guī)范,然后遵守它。
javascript中沒有類,但是可以使用new來(lái)達(dá)到同樣的目的。
因?yàn)闃?gòu)造函數(shù)也是函數(shù),如果能從名字上就能區(qū)別它是構(gòu)造函數(shù)還是普通函數(shù),對(duì)于開發(fā)者是非常有用的。所以將構(gòu)造函數(shù)的首字母大寫,普通函數(shù)的首字母小寫作為提示。這樣一眼就能區(qū)別。
當(dāng)你的變量名或者函數(shù)名是由好幾個(gè)單詞構(gòu)成的時(shí)候,如果能順利區(qū)分變量名由那幾個(gè)單詞構(gòu)成,也是非常不錯(cuò)的體驗(yàn)。這種命名規(guī)范成為駝峰式。所謂駝峰式就是以小寫字母開始,后面的每個(gè)單詞第一個(gè)字母大寫。
對(duì)于構(gòu)造函數(shù)第一個(gè)字母大寫,MyConstructor(),對(duì)于普通的函數(shù),就采用駝峰式myFunction(), calculateArea()。
那么變量怎么辦呢,有的人使用駝峰式,但是更好的辦法是使用下劃線來(lái)區(qū)分。first_name,favorite_bands, 以及 old_company_name。這也可以讓你一眼就能區(qū)分函數(shù)和變量。
有時(shí)候,開發(fā)人員也會(huì)使用命名規(guī)范來(lái)替代和彌補(bǔ)一些語(yǔ)言的特性。
例如,在javascript中,并沒有提供定義常量的辦法(雖然有Number.MAX_VALUE),所以開發(fā)人員使用全大寫的名稱來(lái)表示不可更改的常量。var PI = 3.14, MAX_WIDTH = 800。
另外一種規(guī)范是使用全局變量名的首字母。這樣做可以強(qiáng)化開發(fā)者使全局變量最少,并且容易辨認(rèn)。
另外一種規(guī)范是在函數(shù)中模擬私有成員。雖然可以在javascript中實(shí)現(xiàn)私有變量,但是開發(fā)人員為了更加容易區(qū)別,所以給他加一個(gè)下劃線的前綴。例如:
var person = {
getName: function () {
return this._getFirst() + ' ' + this._getLast();
},
_getFirst: function () {
// ...
},
_getLast: function () {
// ...
}
};
在這個(gè)例子中,getName是一個(gè)公有函數(shù),是API的一部分,_getFirst,_getLast本意是私有的。雖然仍然是公有函數(shù),但hi加上了這個(gè)前綴,表示在以后的版本中不保證能運(yùn)行,所以不應(yīng)該被直接使用。注意在JSLint中不推薦這樣做,除非你設(shè)置nomen選項(xiàng)為false。
還有其他幾種表示私有成員的規(guī)范:
必須給你的代碼寫注釋,就算它看起來(lái)不會(huì)被別人接手。有時(shí)候,你研究完一個(gè)問題,然后你看著代碼覺得那是顯而易見的,但是過一兩周之后回頭再看,你也會(huì)摸不著頭腦的。
當(dāng)然,也不能過分的注釋:每個(gè)變量每一行代碼都注釋。但是通常都需要對(duì)函數(shù)的功能,參數(shù),返回值寫文檔,以及一些其他的復(fù)雜的邏輯和算法。想想,你的代碼的閱讀者,只需要讀注釋就能大體上了解你的代碼在做什么需要什么,這比直接讀代碼理解要快的多。當(dāng)你有五六行的代碼是做一個(gè)具體的任務(wù),那么閱讀者就可以通過一行代碼了解你的目的,然后跳過這些代碼。關(guān)于注釋,沒有硬性的比例說(shuō)是多少代碼需要多少注釋。有時(shí)候,有些代碼(比如正則表達(dá)式)注釋的內(nèi)容肯定比代碼本身多。
寫注釋是必須遵守的規(guī)范,而且要保持注釋的更新,一個(gè)過時(shí)的注釋帶給人的迷惑還不如不寫注釋。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:博客園