所謂"編程風格"(programming style),指的是編寫代碼的樣式規則。不同的程序員,往往有不同的編程風格。
有人說,編譯器的規范叫做"語法規則"(grammar),這是程序員必須遵守的;而編譯器忽略的部分,就叫"編程風格"(programming style),這是程序員可以自由選擇的。這種說法不完全正確,程序員固然可以自由選擇編程風格,但是好的編程風格有助于寫出質量更高、錯誤更少、更易于維護的程序。
所以,"編程風格"的選擇不應該基于個人愛好、熟悉程度、打字量等因素,而要考慮如何盡量使代碼清晰易讀、減少出錯。你選擇的,不是你喜歡的風格,而是一種能夠清晰表達你的意圖的風格。這一點,對于JavaScript這種語法自由度很高的語言尤其重要。
必須牢記的一點是,如果你選定了一種“編程風格”,就應該堅持遵守,切忌多種風格混用。如果你加入他人的項目,就應該遵守現有的風格。
[TOC]
## 語法標記的風格
### 大括號的位置
絕大多數的編程語言,都用大括號({})表示區塊(block)。起首的大括號的位置,有許多不同的寫法。
最流行的有兩種。一種是起首的大括號另起一行:
~~~
block
{
...
}
~~~
另一種是起首的大括號跟在關鍵字的后面:
~~~
block {
...
}
~~~
一般來說,這兩種寫法都可以接受。但是,JavaScript要使用后一種,因為JavaScript會自動添加句末的分號,導致一些難以察覺的錯誤。
~~~
return
{
key:value;
};
~~~
上面的代碼的原意,是要返回一個對象,但實際上返回的是undefined,因為JavaScript自動在return語句后面添加了分號。為了避免這一類錯誤,需要寫成下面這樣:
~~~
return {
key : value;
};
~~~
因此,表示區塊起首的大括號,不要另起一行。
### 圓括號
圓括號(parentheses)在JavaScript中有兩種作用,一種表示函數的調用,另一種表示表達式的組合(grouping)。
~~~
console.log("abc") // 圓括號表示函數的調用
(1+2) * 3 // 圓括號表示表達式的組合
~~~
我們可以用空格,區分這兩種不同的括號。
1. 表示函數調用時,函數名與左括號之間沒有空格。
2. 表示函數定義時,函數名與左括號之間沒有空格。
3. 其他情況時,前面位置的語法元素與左括號之間,都有一個空格。
按照上面的規則,下面的寫法都是不規范的:
~~~
foo (bar)
return(a+b);
if(a === 0) {...}
function foo (b) {...}
function(x) {...}
~~~
上面代碼的最后一行是一個匿名函數,function是語法關鍵字,不是函數名,所以與左括號之間應該要有一個空格。
### 縮進
空格和Tab鍵,都可以產生縮進效果(intent)。Tab鍵可以節省擊鍵次數,但不同的文本編輯器對Tab的顯示不盡相同,有的顯示四個空格,有的顯示兩個空格,所以有人覺得,空格鍵可以使得顯示效果更統一。
無論你選擇哪一種方法,都是可以接受的,要做的就是始終堅持這一種選擇。不要一會使用tab鍵,一會使用空格鍵。
### 行尾的分號
分號表示語句的結束。大多數情況下,如果你省略了句尾的分號,JavaScript會自動添加。
~~~
var a = 1
~~~
等同于
~~~
var a = 1;
~~~
因此,有人提倡省略句尾的分號。但麻煩的是,如果句尾的最后一個字符與下一行的第一個字符,可以連在一起解釋,JavaScript就不會自動添加分號,這會產生一些難以察覺的錯誤。
~~~
x = y
(function (){
...
})();
~~~
上面的代碼等同于
~~~
x = y(function (){...})();
~~~
因此,不要省略句末的分號。
### 區塊
如果循環和判斷的代碼體只有一行,JavaScript允許該區塊(block)省略大括號。
下面的代碼
~~~
if (a) b(); c();
~~~
原意可能是
~~~
if (a) { b(); c();}
~~~
但是,實際效果是
~~~
if (a) { b();} c();
~~~
因此,總是使用大括號表示區塊。
## 語法命令的風格
### 全局變量
JavaScript最大的語法缺點,可能就是全局變量對于任何一個代碼塊,都是可讀可寫。這對代碼的模塊化和重復使用,非常不利。
因此,避免使用全局變量;如果不得不使用,用大寫字母表示變量名,比如UPPER_CASE。
### new命令
JavaScript使用new命令,從構造函數生成一個新對象。
~~~
var o = new myObject();
~~~
這種做法的問題是,一旦你忘了加上new,myObject()內部的this關鍵字就會指向全局對象,導致所有綁定在this上面的變量,都變成全局變量。
因此,盡量使用Object.create()命令,替代new命令。如果不得不使用new,為了防止出錯,最好在視覺上把構造函數與其他函數區分開來。比如,構造函數的函數名,采用首字母大寫(InitialCap);其他函數名,一律首字母小寫。
### 變量聲明
JavaScript會自動將變量聲明"提升"(hoist)到代碼塊(block)的頭部。
~~~
if (!o) {
var o = {};
}
~~~
等同于
~~~
var o;
if (!o) {
o = {};
}
~~~
為了避免可能出現的問題,不如把變量聲明都放在代碼塊的頭部。
~~~
for (var i ...) {...}
~~~
最好寫成:
~~~
var i;
for (i ...) {...}
~~~
因此,所有變量聲明都放在函數的頭部,所有函數都在使用之前定義。
### with語句
with可以減少代碼的書寫,但是會造成混淆。
~~~
with (o) {
foo = bar;
}
~~~
上面的代碼,可以有四種運行結果:
~~~
o.foo = bar;
o.foo = o.bar;
foo = bar;
foo = o.bar;
~~~
這四種結果都可能發生,取決于不同的變量是否有定義。因此,不要使用with語句。
### 相等和嚴格相等
JavaScript有兩個表示"相等"的運算符:"相等"(==)和"嚴格相等"(===)。
因為"相等"運算符會自動轉換變量類型,造成很多意想不到的情況:
~~~
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;
~~~
因此,不要將不同目的的語句,合并成一行。
### 自增和自減運算符
自增(++)和自減(--)運算符,放在變量的前面或后面,返回的值不一樣,很容易發生錯誤。
事實上,所有的++運算符都可以用"+= 1"代替。
~~~
++x
~~~
等同于
~~~
x += 1;
~~~
代碼變得更清晰了。有一個很可笑的例子,某個JavaScript函數庫的源代碼中出現了下面的片段:
~~~
++x;
++x;
~~~
這個程序員忘了,還有更簡單、更合理的寫法:
~~~
x += 2;
~~~
因此,不要使用自增(++)和自減(--)運算符,用+=和-=代替。
### switch...case結構
switch...case結構要求,在每一個case的最后一行必須是break語句,否則會接著運行下一個case。這樣不僅容易忘記,還會造成代碼的冗長。
而且,switch...case不使用大括號,不利于代碼形式的統一。此外,這種結構類似于goto語句,容易造成程序流程的混亂,使得代碼結構混亂不堪,不符合面向對象編程的原則。
~~~
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;
}
}
~~~
上面的代碼建議改寫成對象結構。
~~~
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結構,用對象結構代替。
### eval函數
eval函數的作用是將一段字符串當作語句執行。問題是eval不提供單獨的作用域,而是直接在當前作用域運行。這會造成在不知不覺中,eval中的語句在當前作用域創建新變量、修改已有的變量,使得惡意代碼有機可乘。下面就是一個例子。
~~~
eval('var x = 10');
~~~
因此,避免使用eval函數。
## 參考鏈接
* Eric Elliott, Programming JavaScript Applications,?[Chapter 2\. JavaScript Style Guide](http://chimera.labs.oreilly.com/books/1234000000262/ch02.html), O'reilly, 2013.
* Axel Rauschmayer,?[A meta style guide for JavaScript](http://www.2ality.com/2013/07/meta-style-guide.html)
- 第一章 導論
- 1.1 前言
- 1.2 為什么學習JavaScript?
- 1.3 JavaScript的歷史
- 第二章 基本語法
- 2.1 語法概述
- 2.2 數值
- 2.3 字符串
- 2.4 對象
- 2.5 數組
- 2.6 函數
- 2.7 運算符
- 2.8 數據類型轉換
- 2.9 錯誤處理機制
- 2.10 JavaScript 編程風格
- 第三章 標準庫
- 3.1 Object對象
- 3.2 Array 對象
- 3.3 包裝對象和Boolean對象
- 3.4 Number對象
- 3.5 String對象
- 3.6 Math對象
- 3.7 Date對象
- 3.8 RegExp對象
- 3.9 JSON對象
- 3.10 ArrayBuffer:類型化數組
- 第四章 面向對象編程
- 4.1 概述
- 4.2 封裝
- 4.3 繼承
- 4.4 模塊化編程
- 第五章 DOM
- 5.1 Node節點
- 5.2 document節點
- 5.3 Element對象
- 5.4 Text節點和DocumentFragment節點
- 5.5 Event對象
- 5.6 CSS操作
- 5.7 Mutation Observer
- 第六章 瀏覽器對象
- 6.1 瀏覽器的JavaScript引擎
- 6.2 定時器
- 6.3 window對象
- 6.4 history對象
- 6.5 Ajax
- 6.6 同域限制和window.postMessage方法
- 6.7 Web Storage:瀏覽器端數據儲存機制
- 6.8 IndexedDB:瀏覽器端數據庫
- 6.9 Web Notifications API
- 6.10 Performance API
- 6.11 移動設備API
- 第七章 HTML網頁的API
- 7.1 HTML網頁元素
- 7.2 Canvas API
- 7.3 SVG 圖像
- 7.4 表單
- 7.5 文件和二進制數據的操作
- 7.6 Web Worker
- 7.7 SSE:服務器發送事件
- 7.8 Page Visibility API
- 7.9 Fullscreen API:全屏操作
- 7.10 Web Speech
- 7.11 requestAnimationFrame
- 7.12 WebSocket
- 7.13 WebRTC
- 7.14 Web Components
- 第八章 開發工具
- 8.1 console對象
- 8.2 PhantomJS
- 8.3 Bower:客戶端庫管理工具
- 8.4 Grunt:任務自動管理工具
- 8.5 Gulp:任務自動管理工具
- 8.6 Browserify:瀏覽器加載Node.js模塊
- 8.7 RequireJS和AMD規范
- 8.8 Source Map
- 8.9 JavaScript 程序測試
- 第九章 JavaScript高級語法
- 9.1 Promise對象
- 9.2 有限狀態機
- 9.3 MVC框架與Backbone.js
- 9.4 嚴格模式
- 9.5 ECMAScript 6 介紹
- 附錄
- 10.1 JavaScript API列表
- 草稿一:函數庫
- 11.1 Underscore.js
- 11.2 Modernizr
- 11.3 Datejs
- 11.4 D3.js
- 11.5 設計模式
- 11.6 排序算法
- 草稿二:jQuery
- 12.1 jQuery概述
- 12.2 jQuery工具方法
- 12.3 jQuery插件開發
- 12.4 jQuery.Deferred對象
- 12.5 如何做到 jQuery-free?
- 草稿三:Node.js
- 13.1 Node.js 概述
- 13.2 CommonJS規范
- 13.3 package.json文件
- 13.4 npm模塊管理器
- 13.5 fs 模塊
- 13.6 Path模塊
- 13.7 process對象
- 13.8 Buffer對象
- 13.9 Events模塊
- 13.10 stream接口
- 13.11 Child Process模塊
- 13.12 Http模塊
- 13.13 assert 模塊
- 13.14 Cluster模塊
- 13.15 os模塊
- 13.16 Net模塊和DNS模塊
- 13.17 Express框架
- 13.18 Koa 框架