JavaScript 編程風格

2021-09-15 15:12 更新

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

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

所以,"編程風格"的選擇不應該基于個人愛好、熟悉程度、打字量等因素,而要考慮如何盡量使代碼清晰易讀、減少出錯。你選擇的,不是你喜歡的風格,而是一種能夠清晰表達你的意圖的風格。這一點,對于JavaScript這種語法自由度很高的語言尤其重要。

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

語法標記的風格

大括號的位置

絕大多數(shù)的編程語言,都用大括號({})表示區(qū)塊(block)。起首的大括號的位置,有許多不同的寫法。

最流行的有兩種。一種是起首的大括號另起一行:

block
{
  ...
}

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

block {
  ...
}

一般來說,這兩種寫法都可以接受。但是,JavaScript要使用后一種,因為JavaScript會自動添加句末的分號,導致一些難以察覺的錯誤。

return
{
  key:value;
};

上面的代碼的原意,是要返回一個對象,但實際上返回的是undefined,因為JavaScript自動在return語句后面添加了分號。為了避免這一類錯誤,需要寫成下面這樣:

return {
  key : value;
};

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

圓括號

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

console.log("abc") // 圓括號表示函數(shù)的調(diào)用
(1+2) * 3 // 圓括號表示表達式的組合

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

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

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

  3. 其他情況時,前面位置的語法元素與左括號之間,都有一個空格。

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

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

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

縮進

空格和Tab鍵,都可以產(chǎn)生縮進效果(intent)。Tab鍵可以節(jié)省擊鍵次數(shù),但不同的文本編輯器對Tab的顯示不盡相同,有的顯示四個空格,有的顯示兩個空格,所以有人覺得,空格鍵可以使得顯示效果更統(tǒng)一。

無論你選擇哪一種方法,都是可以接受的,要做的就是始終堅持這一種選擇。不要一會使用tab鍵,一會使用空格鍵。

行尾的分號

分號表示語句的結(jié)束。大多數(shù)情況下,如果你省略了句尾的分號,JavaScript會自動添加。

var a = 1

等同于

var a = 1;

因此,有人提倡省略句尾的分號。但麻煩的是,如果句尾的最后一個字符與下一行的第一個字符,可以連在一起解釋,JavaScript就不會自動添加分號,這會產(chǎn)生一些難以察覺的錯誤。

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

上面的代碼等同于

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

因此,不要省略句末的分號。

區(qū)塊

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

下面的代碼

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

原意可能是

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

但是,實際效果是

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

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

語法命令的風格

全局變量

JavaScript最大的語法缺點,可能就是全局變量對于任何一個代碼塊,都是可讀可寫。這對代碼的模塊化和重復使用,非常不利。

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

new命令

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

var o = new myObject();

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

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

變量聲明

JavaScript會自動將變量聲明"提升"(hoist)到代碼塊(block)的頭部。

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

等同于

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

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

for (var i ...) {...}

最好寫成:

var i;

for (i ...) {...}

因此,所有變量聲明都放在函數(shù)的頭部,所有函數(shù)都在使用之前定義。

with語句

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

with (o) {
  foo = bar;
}

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

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

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

相等和嚴格相等

JavaScript有兩個表示"相等"的運算符:"相等"(==)和"嚴格相等"(===)。

因為"相等"運算符會自動轉(zhuǎn)換變量類型,造成很多意想不到的情況:

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

因此,不要使用"相等"(==)運算符,只使用"嚴格相等"(===)運算符。

語句的合并

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

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

他喜歡寫成下面這樣:

if (a = b) {...}

雖然語句少了一行,但是可讀性大打折扣,而且會造成誤讀,讓別人誤以為這行代碼的意思是:

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

另外一種情況是,有些程序員喜歡在同一行中賦值多個變量:

var a = b = 0;

他以為,這行代碼等同于

var a = 0, b = 0;

實際上不是,它的真正效果是下面這樣:

b = 0;

var a = b;

因此,不要將不同目的的語句,合并成一行。

自增和自減運算符

自增(++)和自減(--)運算符,放在變量的前面或后面,返回的值不一樣,很容易發(fā)生錯誤。

事實上,所有的++運算符都可以用"+= 1"代替。

++x

等同于

x += 1;

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

++x;
++x;

這個程序員忘了,還有更簡單、更合理的寫法:

x += 2;

因此,不要使用自增(++)和自減(--)運算符,用+=和-=代替。

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

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

而且,switch...case不使用大括號,不利于代碼形式的統(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.');
    break;
  }
}

上面的代碼建議改寫成對象結(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),用對象結(jié)構(gòu)代替。

eval函數(shù)

eval函數(shù)的作用是將一段字符串當作語句執(zhí)行。問題是eval不提供單獨的作用域,而是直接在當前作用域運行。這會造成在不知不覺中,eval中的語句在當前作用域創(chuàng)建新變量、修改已有的變量,使得惡意代碼有機可乘。下面就是一個例子。

eval('var x = 10');

因此,避免使用eval函數(shù)。

參考鏈接

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號