本文檔整理大部分公認的、或者少有爭議的JavaScript良好書寫規范(Best Practice)。一些顯而易見的常識就不再論述(比如要用對象支持識別判斷,而不是浏覽器識別判斷;比如不要嵌套太深)。條目順序按重要級粗略的從高到低排列。
把外部JavaScript文件放在HTML底部 我們的目標是相同的:為用戶盡可能快地顯示內容。當載入一個腳本文件的時候,HTML會停止解析,直到腳本載入完畢。因此,用戶可能會長時間對著一個空白的屏幕,看上去什麼都沒有發生。如果你的JavaScript代碼只是增加一些功能(比如按鈕的點擊動作),那麼盡管大膽地把文件引用放在HTML底部吧(就在</body>之前),你會看到明顯的速度提升。如果是用於其他目的的腳本文件,則需要慎重地考慮。但無論如何,這毫無疑問是一個非常值得考慮的地方。
優化循環 循環遍歷一個數組
復制代碼 代碼如下:
//這段糟糕的代碼會在每次進入循環的時候都計算一次數組的長度
var names = ['George','Ringo','Paul','John'];
for(var i=0;i<names.length;i++){
doSomeThingWith(names[i]);
}
復制代碼 代碼如下:
//這樣的話,就只會計算一次了
var names = ['George','Ringo','Paul','John'];
var all = names.length;
for(var i=0;i<all;i++){
doSomeThingWith(names[i]);
}
復制代碼 代碼如下:
//這樣就更加簡短了
var names = ['George','Ringo','Paul','John'];
for(var i=0,j=names.length;i<j;i++){
doSomeThingWith(names[i]);
}
復制代碼 代碼如下:
//這段代碼的糟糕之處在於,它把變量聲明放在循環體內了,每次循環都會創建變量
for(var i = 0; i < someArray.length; i++) {
var container = document.getElementById('container');
container.innerHtml += 'my number: ' + i;
console.log(i);
}
復制代碼 代碼如下:
//在循環體外聲明變量,變量只會創建一次
var container = document.getElementById('container');
for(var i = 0, len = someArray.length; i < len; i++) {
container.innerHtml += 'my number: ' + i;
console.log(i);
}
用盡量簡短的代碼 如果可以增加可讀性的話,那麼使用代碼的簡短格式是有意義的,下面是一份不完全的列表:
復制代碼 代碼如下:
//對於條件判斷只有兩次的,這是一種冗長的寫法
var direction;
if(x > 100){
direction = 1;
} else {
direction = -1;
}
復制代碼 代碼如下:
//更好的代碼
var direction = (x > 100) ? 1 : -1;
復制代碼 代碼如下:
//判斷一個 變量是否定義,如果否,就賦予一個值,糟糕的代碼
if(v){
var x = v;
} else {
var x = 10;
}
復制代碼 代碼如下:
//更好的代碼
var x = v || 10;
復制代碼 代碼如下:
//重復了變量名很多次
var cow = new Object();
cow.colour = 'brown';
cow.commonQuestion = 'What now?';
cow.moo = function(){
console.log('moo');
}
cow.feet = 4;
cow.accordingToLarson = 'will take over the world';
復制代碼 代碼如下:
//更好的寫法是這樣
var cow = {
colour:'brown',
commonQuestion:'What now?',
moo:function(){
console.log('moo);
},
feet:4,
accordingToLarson:'will take over the world'
};
復制代碼 代碼如下:
//重復了很多次數組名
var aweSomeBands = new Array();
aweSomeBands[0] = 'Bad Religion';
aweSomeBands[1] = 'Dropkick Murphys';
aweSomeBands[2] = 'Flogging Molly';
aweSomeBands[3] = 'Red Hot Chili Peppers';
aweSomeBands[4] = 'Pornophonique';
復制代碼 代碼如下:
//更好的代碼
var aweSomeBands = [
'Bad Religion',
'Dropkick Murphys',
'Flogging Molly',
'Red Hot Chili Peppers',
'Pornophonique'
];
單引號和雙引號 為了避免混亂,我們建議在HTML中使用雙引號,在JavaScript中使用單引號。
復制代碼 代碼如下:
//html
<img src="picture.gif" />
復制代碼 代碼如下:
//JavaScript
<script type="text/javascript">
document.write('<p>');
</script>
復制代碼 代碼如下:
//一段混用的jQuery代碼
$('h1').after('<div id="content"><h2>目錄</h2><ol></ol></div>');
避免混入其他技術 CSS:假設我們的頁面上有必須填入的輸入框(擁有class“mandatory”),如果它沒有被輸入數據,周圍就會加上紅色邊框。
復制代碼 代碼如下:
//一段混合了其他技術的代碼會這樣做:
var f = document.getElementById('mainform');
var inputs = f.getElementsByTagName('input');
for(var i=0,j=inputs.length;i<j;i++){
if(inputs[i].className === 'mandatory' &&
inputs[i].value === ''){
inputs[i].style.borderColor = '#f00';
inputs[i].style.borderStyle = 'solid';
inputs[i].style.borderWidth = '1px';
}
}
復制代碼 代碼如下:
//一段良好的代碼會這麼做:將風格化的事情交給CSS吧
var f = document.getElementById('mainform');
var inputs = f.getElementsByTagName('input');
for(var i=0,j=inputs.length;i<j;i++){
if(inputs[i].className === 'mandatory' &&
inputs[i].value === ''){
inputs[i].className += ' error';
}
}
HTML:假設我們有內多HTML內容需要用JavaScript來載入,那麼使用Ajax載入單獨的文件,而不是通過JavaScript處理DOM,後者會讓代碼難以處理,並且出現難以維護的兼容性問題。
驗證JavaScript代碼 浏覽器處理JavaScript代碼可能會非常寬容,但我建議你不要依賴浏覽器的解析能力,因此養成了懶散的編碼習慣。
最簡單的檢測你的代碼質量的方法是通過一個在線JavaScript驗證工具JSLint。
“JSLint takes a JavaScript source and scans it. If it finds a problem, it returns a message describing the problem and an approximate location within the source. The problem is not necessarily a syntax error, although it often is. JSLint looks at some style conventions as well as structural problems. It does not prove that your program is correct. It just provides another set of eyes to help spot problems.”
– JSLint Documentation
使用更簡單的格式來寫innerscript
復制代碼 代碼如下:
//早期的代碼可能是這樣的
<script type="text/javascript" language="javascript">
...
</script>
復制代碼 代碼如下:
//現在不用language屬性了
<script type="text/javascript">
...
</script>
總是檢查數據 要檢查你的方法輸入的所有數據,一方面是為了安全性,另一方面也是為了可用性。用戶隨時隨地都會輸入錯誤的數據。這不是因為他們蠢,而是因為他們很忙,並且思考的方式跟你不同。用typeof方法來檢測你的function接受的輸入是否合法。
復制代碼 代碼如下:
//如果members的類型不是數組,那麼下面的代碼會拋出一個錯誤
function buildMemberList(members){
var all = members.length;
var ul = document.createElement('ul');
for(var i=0;i
復制代碼 代碼如下:
//良好的習慣是檢查參數是否是一個數組
function buildMemberList(members){
//數組的typeof是object,所以如果要判斷是否為數組的話,可以測試它是否擁有數組才有的function:slice
if(typeof members === 'object' &&
typeof members.slice === 'function'){
var all = members.length;
var ul = document.createElement('ul');
for(var i=0;i
另一個安全隱患是直接從DOM中取出數據使用。比如說你的function從用戶名輸入框中取得用戶名做某項操作,但用戶名中的單引號或者雙引號可能會導致你的代碼崩潰。
避免全局變量 全局變量和全局函數是非常糟糕的。因為在一個頁面中包含的所有JavaScript都在同一個域中運行。所以如果你的代碼中聲明了全局變量或者全局函數的話,後面的代碼中載入的腳本文件中的同名變量和函數會覆蓋掉(overwrite)你的。
復制代碼 代碼如下:
//糟糕的全局變量和全局函數
var current = null;
function init(){...}
function change(){...}
function verify(){...}
解決辦法有很多,Christian Heilmann建議的方法是:
復制代碼 代碼如下:
//如果變量和函數不需要在“外面”引用,那麼就可以使用一個沒有名字的方法將他們全都包起來。
(function(){
var current = null;
function init(){...}
function change(){...}
function verify(){...}
})();
復制代碼 代碼如下:
//如果變量和函數需要在“外面”引用,需要把你的變量和函數放在一個“命名空間”中
//我們這裡用一個function做命名空間而不是一個var,因為在前者中聲明function更簡單,而且能保護隱私數據
myNameSpace = function(){
var current = null;
function init(){...}
function change(){...}
function verify(){...}
//所有需要在命名空間外調用的函數和屬性都要寫在return裡面
return{
init:init,
//甚至你可以為函數和屬性命名一個別名
set:change
}
}();
聲明變量的話,總是用var JavaScript中的變量可能是全局域或者局部域,用var聲明的話會更加直觀。
復制代碼 代碼如下:
//在function中不用var引起的迷惑性問題
var i=0; // This is good - creates a global variable
function test() {
for (i=0; i<10; i++) {
alert("Hello World!");
}
}
test();
alert(i); // The global variable i is now 10!
復制代碼 代碼如下:
//解決方法是在function中聲明變量也用var
function test() {
for (var i=0; i<10; i++) {
alert("Hello World!");
}
}
使用前置+號來把字符串轉化為數字 JavaScript中,“+”操作符即被用來作為數字加,也被用來連接字符串。如果需要求表單中幾個值的和,那麼用+可能會出現問題。
復制代碼 代碼如下:
//會出現問題的代碼
<form name="myform" action="[url]">
<input type="text" name="val1" value="1">
<input type="text" name="val2" value="2">
</form>
function total() {
var theform = document.forms["myform"];
var total = theform.elements["val1"].value + theform.elements["val2"].value;
alert(total); // This will alert "12", but what you wanted was 3!
}
復制代碼 代碼如下:
//在字符串前面加上“+”,這是給JavaScript的一個暗示:這是一個數字而不是字符串
function total() {
var theform = document.forms["myform"];
var total = (+theform.elements["val1"].value) + (+theform.elements["val2"].value);
alert(total); // This will alert 3
}
避免使用eval()方法 JavaScript中的eval()方法是在運行時把任何代碼當作對象來計算/運行的方法。實際上由於安全性的緣故,大部分情況下都不應該用eval(),總是有一種更“正確”的方法來完成同樣的工作的。基本原則是,eval is evil,在任何時候都不要用它,除非你是一個老手,並且知道你不得不這樣做。
for in語句 遍歷一個對象中的所有條目的時候,用for in語句是非常方便的。但有時候我們不需要遍歷對象中的方法,如果不需要的話,可以加上一條filter。
復制代碼 代碼如下:
//加上了一個過濾器的for in語句
for(key in object) {
if(object.hasOwnProperty(key) {
...then do something...
}
}
不要偷懶省略”和{}
從技術上說,你可以忽略很多花括號和分號。
復制代碼 代碼如下:
//雖然看上去很不對頭,大部分浏覽器都能正確解析這段代碼
if(someVariableExists)
x = false
復制代碼 代碼如下:
//這個代碼看上去更不對頭了,咋眼一看似乎下面的句都被執行了
//實際上只有x=false在if中
if(someVariableExists)
x = false
anotherFunctionCall();
所以,要記住的原則是:1.永遠不要省略分號;2.不要省略花括號,除非在同一行中。
復制代碼 代碼如下:
//這樣是OK的
if(2 + 2 === 4) return 'nicely done';
獲取對象屬性的時候用方括號而不是點號
在JavaScript中取得某對象的屬性有兩種方法:
復制代碼 代碼如下:
//點號標記
MyObject.property
復制代碼 代碼如下:
//方括號標記
MyObject["property"]
如果是用點號標記取得對象的屬性,屬性名稱是硬編碼,無法在運行時更改;而用方括號的話,JavaScript會求得方括號內值然後通過計算結果來求得屬性名。也就是說用方括號標記的方式,屬性名稱可以是硬編碼的,也可以是變量或者函數返回值。
復制代碼 代碼如下:
//這樣是不行的
MyObject.value+i
復制代碼 代碼如下:
//這樣就沒有問題
MyObject["value"+i]
假設JavaScript會被禁用
我知道這樣的假設會傷害JavaScript開發者的感情,可是在目前數據不明朗的情況下我們為了安全起見應該做這樣的假設。這是漸進增強中很重要的一部分。
使用JavaScript庫 現在有很多非常流行的JavaScript庫,比如YUI和jQuery、Dojo。它們的缺點是需要下載一個額外的文件,優點卻更多:兼容性更強;代碼更簡單易懂。好的庫有很多,但你不應該在一個項目中把它們都用上,因為可能存在兼容性問題。選擇一個自己習慣的就好。
不要忘記的一點是,原生的JavaScript毫無疑問更快,如果是小規模的使用,最好還是用原生的。