JavaScript 編程風(fēng)格

2018-06-23 14:45 更新

目錄

所謂”編程風(fēng)格”(programming style),指的是編寫代碼的樣式規(guī)則。不同的程序員,往往有不同的編程風(fēng)格。

有人說,編譯器的規(guī)范叫做”語法規(guī)則”(grammar),這是程序員必須遵守的;而編譯器忽略的部分,就叫”編程風(fēng)格”(programming style),這是程序員可以自由選擇的。這種說法不完全正確,程序員固然可以自由選擇編程風(fēng)格,但是好的編程風(fēng)格有助于寫出質(zhì)量更高、錯(cuò)誤更少、更易于維護(hù)的程序。

所以,”編程風(fēng)格”的選擇不應(yīng)該基于個(gè)人愛好、熟悉程度、打字量等因素,而要考慮如何盡量使代碼清晰易讀、減少出錯(cuò)。你選擇的,不是你喜歡的風(fēng)格,而是一種能夠清晰表達(dá)你的意圖的風(fēng)格。這一點(diǎn),對(duì)于JavaScript這種語法自由度很高的語言尤其重要。

必須牢記的一點(diǎn)是,如果你選定了一種“編程風(fēng)格”,就應(yīng)該堅(jiān)持遵守,切忌多種風(fēng)格混用。如果你加入他人的項(xiàng)目,就應(yīng)該遵守現(xiàn)有的風(fēng)格。

縮進(jìn)

空格和Tab鍵,都可以產(chǎn)生縮進(jìn)效果(indent)。

Tab鍵可以節(jié)省擊鍵次數(shù),但不同的文本編輯器對(duì)Tab的顯示不盡相同,有的顯示四個(gè)空格,有的顯示兩個(gè)空格,所以有人覺得,空格鍵可以使得顯示效果更統(tǒng)一。

無論你選擇哪一種方法,都是可以接受的,要做的就是始終堅(jiān)持這一種選擇。不要一會(huì)使用Tab鍵,一會(huì)使用空格鍵。

區(qū)塊

如果循環(huán)和判斷的代碼體只有一行,JavaScript允許該區(qū)塊(block)省略大括號(hào)。

if (a)
  b();
  c();

上面代碼的原意可能是下面這樣。

if (a) {
  b();
  c();
}

但是,實(shí)際效果卻是下面這樣。

if (a) {
  b();
}
  c();

因此,總是使用大括號(hào)表示區(qū)塊。

另外,區(qū)塊起首的大括號(hào)的位置,有許多不同的寫法。最流行的有兩種。一種是起首的大括號(hào)另起一行:

block
{
  // ...
}

另一種是起首的大括號(hào)跟在關(guān)鍵字的后面。

block {
  // ...
}

一般來說,這兩種寫法都可以接受。但是,JavaScript要使用后一種,因?yàn)镴avaScript會(huì)自動(dòng)添加句末的分號(hào),導(dǎo)致一些難以察覺的錯(cuò)誤。

return
{
  key: value
};

// 相當(dāng)于
return;
{
  key: value
};

上面的代碼的原意,是要返回一個(gè)對(duì)象,但實(shí)際上返回的是undefined,因?yàn)镴avaScript自動(dòng)在return語句后面添加了分號(hào)。為了避免這一類錯(cuò)誤,需要寫成下面這樣。

return {
  key : value
};

因此,表示區(qū)塊起首的大括號(hào),不要另起一行。

圓括號(hào)

圓括號(hào)(parentheses)在JavaScript中有兩種作用,一種表示函數(shù)的調(diào)用,另一種表示表達(dá)式的組合(grouping)。

// 圓括號(hào)表示函數(shù)的調(diào)用
console.log('abc');

// 圓括號(hào)表示表達(dá)式的組合
(1 + 2) * 3

我們可以用空格,區(qū)分這兩種不同的括號(hào)。

  1. 表示函數(shù)調(diào)用時(shí),函數(shù)名與左括號(hào)之間沒有空格。

  2. 表示函數(shù)定義時(shí),函數(shù)名與左括號(hào)之間沒有空格。

  3. 其他情況時(shí),前面位置的語法元素與左括號(hào)之間,都有一個(gè)空格。

按照上面的規(guī)則,下面的寫法都是不規(guī)范的。

foo (bar)
return(a+b);
if(a === 0) {...}
function foo (b) {...}
function(x) {...}

上面代碼的最后一行是一個(gè)匿名函數(shù),function是語法關(guān)鍵字,不是函數(shù)名,所以與左括號(hào)之間應(yīng)該要有一個(gè)空格。

行尾的分號(hào)

分號(hào)表示一條語句的結(jié)束。JavaScript規(guī)定,行尾的分號(hào)可以省略。事實(shí)上,確實(shí)有一些開發(fā)者行尾從來不寫分號(hào)。但是,由于下面要討論的原因,建議還是不要省略這個(gè)分號(hào)。

不使用分號(hào)的情況

有一些語法結(jié)構(gòu)不需要在語句的結(jié)尾添加分號(hào),主要是以下三種情況。

(1)for和while循環(huán)

for ( ; ; ) {
} // 沒有分號(hào)

while (true) {
} // 沒有分號(hào)

需要注意的是do...while循環(huán)是有分號(hào)的。

do {
  a--;
} while(a > 0); // 分號(hào)不能省略

(2)分支語句:if,switch,try

if (true) {
} // 沒有分號(hào)

switch () {
} // 沒有分號(hào)

try {
} catch {
} // 沒有分號(hào)

(3)函數(shù)的聲明語句

function f() {
} // 沒有分號(hào)

但是函數(shù)表達(dá)式仍然要使用分號(hào)。

var f = function f() {
};

以上三種情況,如果使用了分號(hào),并不會(huì)出錯(cuò)。因?yàn)?,解釋引擎?huì)把這個(gè)分號(hào)解釋為空語句。

分號(hào)的自動(dòng)添加

除了上一節(jié)的三種情況,所有語句都應(yīng)該使用分號(hào)。但是,如果沒有使用分號(hào),大多數(shù)情況下,JavaScript會(huì)自動(dòng)添加。

var a = 1
// 等同于
var a = 1;

這種語法特性被稱為“分號(hào)的自動(dòng)添加”(Automatic Semicolon Insertion,簡稱ASI)。

因此,有人提倡省略句尾的分號(hào)。麻煩的是,如果下一行的開始可以與本行的結(jié)尾連在一起解釋,JavaScript就不會(huì)自動(dòng)添加分號(hào)。

// 等同于 var a = 3
var
a
=
3

// 等同于 'abc'.length
'abc'
.length

// 等同于 return a + b;
return a +
b;

// 等同于 obj.foo(arg1, arg2);
obj.foo(arg1,
arg2);

// 等同于 3 * 2 + 10 * (27 / 6)
3 * 2
+
10 * (27 / 6)

上面代碼都會(huì)多行放在一起解釋,不會(huì)每一行自動(dòng)添加分號(hào)。這些例子還是比較容易看出來的,但是下面這個(gè)例子就不那么容易看出來了。

x = y
(function () {
  // ...
})();

// 等同于
x = y(function () {...})();

下面是更多不會(huì)自動(dòng)添加分號(hào)的例子。

// 解釋為 c(d+e)
var a = b + c
(d+e).toString();

// 解釋為 a = b/hi/g.exec(c).map(d)
// 正則表達(dá)式的斜杠,會(huì)當(dāng)作除法運(yùn)算符
a = b
/hi/g.exec(c).map(d);

// 解釋為'b'['red', 'green'],
// 即把字符串當(dāng)作一個(gè)數(shù)組,按索引取值
var a = 'b'
['red', 'green'].forEach(function (c) {
  console.log(c);
})

// 解釋為 function(x) { return x }(a++)
// 即調(diào)用匿名函數(shù),結(jié)果f等于0
var a = 0;
var f = function(x) { return x }
(a++)

只有下一行的開始與本行的結(jié)尾,無法放在一起解釋,JavaScript引擎才會(huì)自動(dòng)添加分號(hào)。

if (a < 0) a = 0
console.log(a)

// 等同于下面的代碼,
// 因?yàn)?console沒有意義

if (a < 0) a = 0;
console.log(a)

另外,如果一行的起首是“自增”(++)或“自減”(--)運(yùn)算符,則它們的前面會(huì)自動(dòng)添加分號(hào)。

a = b = c = 1

a
++
b
--
c

console.log(a, b, c)
// 1 2 0

上面代碼之所以會(huì)得到“1 2 0”的結(jié)果,原因是自增和自減運(yùn)算符前,自動(dòng)加上了分號(hào)。上面的代碼實(shí)際上等同于下面的形式。

a = b = c = 1;
a;
++b;
--c;

如果continue、break、returnthrow這四個(gè)語句后面,直接跟換行符,則會(huì)自動(dòng)添加分號(hào)。這意味著,如果return語句返回的是一個(gè)對(duì)象的字面量,起首的大括號(hào)一定要寫在同一行,否則得不到預(yù)期結(jié)果。

return
{ first: 'Jane' };

// 解釋成
return;
{ first: 'Jane' };

由于解釋引擎自動(dòng)添加分號(hào)的行為難以預(yù)測,因此編寫代碼的時(shí)候不應(yīng)該省略行尾的分號(hào)。

不應(yīng)該省略結(jié)尾的分號(hào),還有一個(gè)原因。有些JavaScript代碼壓縮器不會(huì)自動(dòng)添加分號(hào),因此遇到?jīng)]有分號(hào)的結(jié)尾,就會(huì)讓代碼保持原狀,而不是壓縮成一行,使得壓縮無法得到最優(yōu)的結(jié)果。

另外,不寫結(jié)尾的分號(hào),可能會(huì)導(dǎo)致腳本合并出錯(cuò)。所以,有的代碼庫在第一行語句開始前,會(huì)加上一個(gè)分號(hào)。

;var a = 1;
// ...

上面這種寫法就可以避免與其他腳本合并時(shí),排在前面的腳本最后一行語句沒有分號(hào),導(dǎo)致運(yùn)行出錯(cuò)的問題。

全局變量

JavaScript最大的語法缺點(diǎn),可能就是全局變量對(duì)于任何一個(gè)代碼塊,都是可讀可寫。這對(duì)代碼的模塊化和重復(fù)使用,非常不利。

因此,避免使用全局變量。如果不得不使用,用大寫字母表示變量名,比如UPPER_CASE。

變量聲明

JavaScript會(huì)自動(dòng)將變量聲明”提升”(hoist)到代碼塊(block)的頭部。

if (!o) {
  var o = {};
}

// 等同于

var o;
if (!o) {
  o = {};
}

為了避免可能出現(xiàn)的問題,最好把變量聲明都放在代碼塊的頭部。

for (var i = 0; i < 10; i++) {
  // ...
}

// 寫成

var i;
for (i = 0; i < 10; i++) {
  // ...
}

另外,所有函數(shù)都應(yīng)該在使用之前定義,函數(shù)內(nèi)部的變量聲明,都應(yīng)該放在函數(shù)的頭部。

new命令

JavaScript使用new命令,從構(gòu)造函數(shù)生成一個(gè)新對(duì)象。

var o = new myObject();

上面這種做法的問題是,一旦你忘了加上new,myObject()內(nèi)部的this關(guān)鍵字就會(huì)指向全局對(duì)象,導(dǎo)致所有綁定在this上面的變量,都變成全局變量。

因此,建議使用Object.create()命令,替代new命令。如果不得不使用new,為了防止出錯(cuò),最好在視覺上把構(gòu)造函數(shù)與其他函數(shù)區(qū)分開來。比如,構(gòu)造函數(shù)的函數(shù)名,采用首字母大寫(InitialCap),其他函數(shù)名一律首字母小寫。

with語句

with可以減少代碼的書寫,但是會(huì)造成混淆。

with (o) {
 foo = bar;
}

上面的代碼,可以有四種運(yùn)行結(jié)果:

o.foo = bar;
o.foo = o.bar;
foo = bar;
foo = o.bar;

這四種結(jié)果都可能發(fā)生,取決于不同的變量是否有定義。因此,不要使用with語句。

相等和嚴(yán)格相等

JavaScript有兩個(gè)表示”相等”的運(yùn)算符:”相等”(==)和”嚴(yán)格相等”(===)。

因?yàn)椤毕嗟取边\(yùn)算符會(huì)自動(dòng)轉(zhuǎn)換變量類型,造成很多意想不到的情況:

0 == ''// true
1 == true // true
2 == true // false
0 == '0' // true
false == 'false' // false
false == '0' // true
' \t\r\n ' == 0 // true

因此,不要使用“相等”(==)運(yùn)算符,只使用“嚴(yán)格相等”(===)運(yùn)算符。

語句的合并

有些程序員追求簡潔,喜歡合并不同目的的語句。比如,原來的語句是

a = b;
if (a) {
  // ...
}

他喜歡寫成下面這樣。

if (a = b) {
  // ...
}

雖然語句少了一行,但是可讀性大打折扣,而且會(huì)造成誤讀,讓別人誤解這行代碼的意思是下面這樣。

if a === b{
  // ...
}

建議不要將不同目的的語句,合并成一行。

自增和自減運(yùn)算符

自增(++)和自減(--)運(yùn)算符,放在變量的前面或后面,返回的值不一樣,很容易發(fā)生錯(cuò)誤。事實(shí)上,所有的++運(yùn)算符都可以用+= 1代替。

++x
// 等同于
x += 1;

改用+= 1,代碼變得更清晰了。有一個(gè)很可笑的例子,某個(gè)JavaScript函數(shù)庫的源代碼中出現(xiàn)了下面的片段:

++x;
++x;

這個(gè)程序員忘了,還有更簡單、更合理的寫法。

x += 2;

建議自增(++)和自減(--)運(yùn)算符盡量使用+=-=代替。

switch…case結(jié)構(gòu)

switch...case結(jié)構(gòu)要求,在每一個(gè)case的最后一行必須是break語句,否則會(huì)接著運(yùn)行下一個(gè)case。這樣不僅容易忘記,還會(huì)造成代碼的冗長。

而且,switch...case不使用大括號(hào),不利于代碼形式的統(tǒng)一。此外,這種結(jié)構(gòu)類似于goto語句,容易造成程序流程的混亂,使得代碼結(jié)構(gòu)混亂不堪,不符合面向?qū)ο缶幊痰脑瓌t。

function doAction(action) {
  switch (action) {
    case 'hack':
      return 'hack';
      break;
    case 'slash':
      return 'slash';
      break;
    case 'run':
      return 'run';
      break;
    default:
      throw new Error('Invalid action.');
  }
}

上面的代碼建議改寫成對(duì)象結(jié)構(gòu)。

function doAction(action) {
  var actions = {
    'hack': function () {
      return 'hack';
    },
    'slash': function () {
      return 'slash';
    },
    'run': function () {
      return 'run';
    }
  };

  if (typeof actions[action] !== 'function') {
    throw new Error('Invalid action.');
  }

  return actions[action]();
}

建議避免使用switch...case結(jié)構(gòu),用對(duì)象結(jié)構(gòu)代替。

參考鏈接

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)