翻譯編輯:張鑫旭javascript
才華橫溢的Stoyan Stefanov,在他寫的由O’Reilly第一版的新書《JavaScript Patterns》(JavaScript模式)中,我想要是爲咱們的讀者貢獻其摘要,那會是件很美妙的事情。具體一點就是編寫高質量JavaScript的一些要素,例如避免全局變量,使用單變量聲明,在循環中預緩存length(長度),遵循代碼閱讀,以及更多。php
此摘要也包括一些與代碼不太相關的習慣,但對總體代碼的建立息息相關,包括撰寫API文檔、執行同行評審以及運行JSLint。這些習慣和最佳作法能夠幫助你寫出更好的,更易於理解和維護的代碼,這些代碼在幾個月或是幾年以後再回過頭看看也是會以爲很自豪的。java
軟件bug的修復是昂貴的,而且隨着時間的推移,這些bug的成本也會增長,尤爲當這些bug潛伏並慢慢出如今已經發布的軟件中時。當你發現bug 的時候就當即修復它是最好的,此時你代碼要解決的問題在你腦中仍是很清晰的。不然,你轉移到其餘任務,忘了那個特定的代碼,一段時間後再去查看這些代碼就 須要:web
還有問題,特別對於大的項目或是公司,修復bug的這位夥計不是寫代碼的那我的(且發現bug和修復bug的不是同一我的)。所以,必須下降理解代 碼花費的時間,不管是一段時間前你本身寫的代碼仍是團隊中的其餘成員寫的代碼。這關係到底線(營業收入)和開發人員的幸福,由於咱們更應該去開發新的激動 人心的事物而不是花幾小時幾天的時間去維護遺留代碼。ajax
另外一個相關軟件開發生命的事實是,讀代碼花費的時間要比寫來得多。有時候,當你專一併深刻思考某個問題的時候,你能夠坐下來,一個下午寫大量的代碼。正則表達式
你的代碼很能很快就工做了,可是,隨着應用的成熟,還會有不少其餘的事情發生,這就要求你的進行進行審查,修改,和調整。例如:api
因爲這些變化,不多人力數小時寫的代碼最終演變成花數週來閱讀這些代碼。這就是爲何建立可維護的代碼對應用程序的成功相當重要。數組
可維護的代碼意味着:瀏覽器
JavaScript經過函數管理做用域。在函數內部聲明的變量只在這個函數內部,函數外面不可用。另外一方面,全局變量就是在任何函數外面聲明的或是未聲明直接簡單使用的。緩存
每一個JavaScript環境有一個全局對象,當你在任意的函數外面使用this的時候能夠訪問到。你建立的每個所有變量都成了這個全局對象的屬 性。在瀏覽器中,方便起見,該全局對象有個附加屬性叫作window,此window(一般)指向該全局對象自己。下面的代碼片斷顯示瞭如何在瀏覽器環境 中建立和訪問的全局變量:
myglobal = "hello"; // 不推薦寫法 console.log(myglobal); // "hello" console.log(window.myglobal); // "hello" console.log(window["myglobal"]); // "hello" console.log(this.myglobal); // "hello"
全局變量的問題在於,你的JavaScript應用程序和web頁面上的全部代碼都共享了這些全局變量,他們住在同一個全局命名空間,因此當程序的兩個不一樣部分定義同名但不一樣做用的全局變量的時候,命名衝突在所不免。
web頁面包含不是該頁面開發者所寫的代碼也是比較常見的,例如:
比方說,該第三方腳本定義了一個全局變量,叫作result;接着,在你的函數中也定義一個名爲result的全局變量。其結果就是後面的變量覆蓋前面的,第三方腳本就一會兒嗝屁啦!
所以,要想和其餘腳本成爲好鄰居的話,儘量少的使用全局變量是很重要的。在書中後面提到的一些減小全局變量的策略,例如命名空間模式或是函數當即自動執行,可是要想讓全局變量少最重要的仍是始終使用var來聲明變量。
因爲JavaScript的兩個特徵,不自覺地建立出全局變量是出乎意料的容易。首先,你能夠甚至不須要聲明就可使用變量;第二,JavaScript有隱含的全局概念,意味着你不聲明的任何變量都會成爲一個全局對象屬性。參考下面的代碼:
function sum(x, y) {
// 不推薦寫法: 隱式全局變量 result = x + y;
return result;
}
此段代碼中的result
沒有聲明。代碼照樣運做正常,但在調用函數後你最後的結果就多一個全局命名空間,這能夠是一個問題的根源。
經驗法則是始終使用var聲明變量,正如改進版的sum()函數所演示的:
function sum(x, y) { var result = x + y; return result; }
另外一個建立隱式全局變量的反例就是使用任務鏈進行部分var聲明。下面的片斷中,a
是本地變量可是b
確實全局變量,這可能不是你但願發生的:
// 反例,勿使用 function foo() { var a = b = 0; // ... }
此現象發生的緣由在於這個從右到左的賦值,首先,是賦值表達式b = 0
,此狀況下b是未聲明的。這個表達式的返回值是0,而後這個0就分配給了經過var定義的這個局部變量a。換句話說,就比如你輸入了:
var a = (b = 0);
若是你已經準備好聲明變量,使用鏈分配是比較好的作法,不會產生任何意料以外的全局變量,如:
function foo() {
var a, b;
// ... a = b = 0; // 兩個均局部變量 }
然而,另一個避免全局變量的緣由是可移植性。若是你想你的代碼在不一樣的環境下(主機下)運行,使用全局變量如履薄冰,由於你會無心中覆蓋你最初環境下不存在的主機對象(因此你原覺得名稱能夠放心大膽地使用,實際上對於有些狀況並不適用)。
隱式全局變量和明肯定義的全局變量間有些小的差別,就是經過delete
操做符讓變量未定義的能力。
這代表,在技術上,隱式全局變量並非真正的全局變量,但它們是全局對象的屬性。屬性是能夠經過delete
操做符刪除的,而變量是不能的:
// 定義三個全局變量 var global_var = 1; global_novar = 2; // 反面教材 (function () { global_fromfunc = 3; // 反面教材 }());// 試圖刪除 delete global_var; // false delete global_novar; // true delete global_fromfunc; // true // 測試該刪除 typeof global_var; // "number" typeof global_novar; // "undefined" typeof global_fromfunc; // "undefined"
在ES5嚴格模式下,未聲明的變量(如在前面的代碼片斷中的兩個反面教材)工做時會拋出一個錯誤。
在瀏覽器中,全局對象能夠經過window
屬性在代碼的任何位置訪問(除非你作了些比較出格的事情,像是聲明瞭一個名爲window的局部變量)。可是在其餘環境下,這個方便的屬性可能被叫作其餘什麼東西(甚至在程序中不可用)。若是你須要在沒有硬編碼的window
標識符下訪問全局對象,你能夠在任何層級的函數做用域中作以下操做:
var global = (function () { return this; }());
這種方法能夠隨時得到全局對象,由於其在函數中被當作函數調用了(不是經過new
構造),this
總 是指向全局對象。實際上這個病不適用於ECMAScript 5嚴格模式,因此,在嚴格模式下時,你必須採起不一樣的形式。例如,你正在開發一個JavaScript庫,你能夠將你的代碼包裹在一個即時函數中,而後從 全局做用域中,傳遞一個引用指向this做爲你即時函數的參數。
在函數頂部使用單var語句是比較有用的一種形式,其好處在於:
單var形式長得就像下面這個樣子:
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i,
j;
// function body... }
您可使用一個var語句聲明多個變量,並以逗號分隔。像這種初始化變量同時初始化值的作法是很好的。這樣子能夠防止邏輯錯誤(全部未初始化但聲明的變量的初始值是undefined
)和增長代碼的可讀性。在你看到代碼後,你能夠根據初始化的值知道這些變量大體的用途,例如是要看成對象呢仍是看成整數來使。
你也能夠在聲明的時候作一些實際的工做,例如前面代碼中的sum = a + b
這個狀況,另一個例子就是當你使用DOM(文檔對象模型)引用時,你可使用單一的var把DOM引用一塊兒指定爲局部變量,就以下面代碼所示的:
function updateElement() {
var el = document.getElementById("result"),
style = el.style;
// 使用el和style乾點其餘什麼事... }
JavaScript中,你能夠在函數的任何位置聲明多個var語句,而且它們就好像是在函數頂部聲明同樣發揮做用,這種行爲稱爲 hoisting(懸置/置頂解析/預解析)。當你使用了一個變量,而後不久在函數中又從新聲明的話,就可能產生邏輯錯誤。對於JavaScript,只 要你的變量是在同一個做用域中(同一函數),它都被當作是聲明的,即便是它在var聲明前使用的時候。看下面這個例子:
// 反例 myname = "global"; // 全局變量 function func() { alert(myname); // "undefined" var myname = "local"; alert(myname); // "local" } func();
在這個例子中,你可能會覺得第一個alert彈出的是」global」,第二個彈出」loacl」。這種期許是能夠理解的,由於在第一個alert 的時候,myname未聲明,此時函數確定很天然而然地看全局變量myname,可是,實際上並非這麼工做的。第一個alert會彈 出」undefined」是由於myname被當作了函數的局部變量(儘管是以後聲明的),全部的變量聲明當被懸置到函數的頂部了。所以,爲了不這種混 亂,最好是預先聲明你想使用的所有變量。
上面的代碼片斷執行的行爲可能就像下面這樣:
myname = "global"; // global variable function func() { var myname; // 等同於 -> var myname = undefined; alert(myname); // "undefined" myname = "local"; alert(myname); // "local"} func();
//zxx:關於JavaScript的置頂解析,我上週專門翻譯了篇文章,您有興趣能夠看看:「翻譯 – 解釋JavaScript的置頂解析」。
爲了完整,咱們再提一提執行層面的稍微複雜點的東西。代碼處理分兩個階段,第一階段是變量,函數聲明,以及正常格式的參數建立,這是一個解析和進入上下文 的階段。第二個階段是代碼執行,函數表達式和不合格的標識符(爲聲明的變量)被建立。可是,出於實用的目的,咱們就採用了」hoisting」這個概念, 這種ECMAScript標準中並未定義,一般用來描述行爲。
在for
循環中,你能夠循環取得數組或是數組相似對象的值,譬如arguments
和HTMLCollection
對象。一般的循環形式以下:
// 次佳的循環 for (var i = 0; i < myarray.length; i++) { // 使用myarray[i]作點什麼 }
這種形式的循環的不足在於每次循環的時候數組的長度都要去獲取下。這回下降你的代碼,尤爲當myarray
不是數組,而是一個HTMLCollection
對象的時候。
HTMLCollections
指的是DOM方法返回的對象,例如:
document.getElementsByName() document.getElementsByClassName() document.getElementsByTagName()
還有其餘一些HTMLCollections
,這些是在DOM標準以前引進而且如今還在使用的。有:
document.images: 頁面上全部的圖片元素 document.links : 全部a標籤元素 document.forms : 全部表單 document.forms[0].elements : 頁面上第一個表單中的全部域
集合的麻煩在於它們實時查詢基本文檔(HTML頁面)。這意味着每次你訪問任何集合的長度,你要實時查詢DOM,而DOM操做通常都是比較昂貴的。
這就是爲何當你循環獲取值時,緩存數組(或集合)的長度是比較好的形式,正以下面代碼顯示的:
for (var i = 0, max = myarray.length; i < max; i++) {
// 使用myarray[i]作點什麼 }
這樣,在這個循環過程當中,你只檢索了一次長度值。
在全部瀏覽器下,循環獲取內容時緩存HTMLCollections
的長度是更快的,2倍(Safari3)到190倍(IE7)之間。//zxx:此數據貌似很老,僅供參考
注意到,當你明確想要修改循環中的集合的時候(例如,添加更多的DOM元素),你可能更喜歡長度更新而不是常量。
伴隨着單var形式,你能夠把變量從循環中提出來,就像下面這樣:
function looper() { var i = 0, max, myarray = []; // ... for (i = 0, max = myarray.length; i < max; i++) { // 使用myarray[i]作點什麼 } }
這種形式具備一致性的好處,由於你堅持了單一var形式。不足在於當重構代碼的時候,複製和粘貼整個循環有點困難。例如,你從一個函數複製了一個循環到另外一個函數,你不得不去肯定你可以把i
和max
引入新的函數(若是在這裏沒有用的話,頗有可能你要從原函數中把它們刪掉)。
最後一個須要對循環進行調整的是使用下面表達式之一來替換i++
。
i = i + 1 i += 1
JSLint提示您這樣作,緣由是++
和–-
促進了「過度棘手(excessive trickiness)」。//zxx:這裏比較難翻譯,我想本意應該是讓代碼變得更加的棘手
若是你直接無視它,JSLint的plusplus
選項會是false
(默認是default)。
還有兩種變化的形式,其又有了些微改進,由於:
第一種變化的形式:
var i, myarray = [];
for (i = myarray.length; i–-;) {
// 使用myarray[i]作點什麼 }
第二種使用while
循環:
var myarray = [],
i = myarray.length;
while (i–-) {
// 使用myarray[i]作點什麼 }
這些小的改進只體如今性能上,此外JSLint會對使用i–-加以抱怨。
for-in
循環應該用在非數組對象的遍歷上,使用for-in
進行循環也被稱爲「枚舉」。
從技術上將,你可使用for-in
循環數組(由於JavaScript中數組也是對象),但這是不推薦的。由於若是數組對象已被自定義的功能加強,就可能發生邏輯錯誤。另外,在for-in中,屬性列表的順序(序列)是不能保證的。因此最好數組使用正常的for循環,對象使用for-in循環。
有個很重要的hasOwnProperty()
方法,當遍歷對象屬性的時候能夠過濾掉從原型鏈上下來的屬性。
思考下面一段代碼:
// 對象 var man = { hands: 2, legs: 2, heads: 1 };// 在代碼的某個地方 // 一個方法添加給了全部對象 if (typeof Object.prototype.clone === "undefined") { Object.prototype.clone = function () {}; }
在這個例子中,咱們有一個使用對象字面量定義的名叫man的對象。在man定義完成後的某個地方,在對象原型上增長了一個頗有用的名叫 clone()的方法。此原型鏈是實時的,這就意味着全部的對象自動能夠訪問新的方法。爲了不枚舉man的時候出現clone()方法,你須要應用hasOwnProperty()
方法過濾原型屬性。若是不作過濾,會致使clone()函數顯示出來,在大多數狀況下這是不但願出現的。
// 1. // for-in 循環 for (var i in man) { if (man.hasOwnProperty(i)) { // 過濾 console.log(i, ":", man[i]); } }/* 控制檯顯示結果 hands : 2 legs : 2 heads : 1 */ // 2. // 反面例子: // for-in loop without checking hasOwnProperty() for (var i in man) { console.log(i, ":", man[i]); }/* 控制檯顯示結果 hands : 2 legs : 2 heads : 1 clone: function() */
另一種使用hasOwnProperty()
的形式是取消Object.prototype上的方法。像是:
for (var i in man) {
if (Object.prototype.hasOwnProperty.call(man, i)) { // 過濾 console.log(i, ":", man[i]);
}
}
其好處在於在man對象從新定義hasOwnProperty狀況下避免命名衝突。也避免了長屬性查找對象的全部方法,你可使用局部變量「緩存」它。
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
if (hasOwn.call(man, i)) { // 過濾 console.log(i, ":", man[i]);
}
}
嚴格來講,不使用
hasOwnProperty()
並非一個錯誤。根據任務以及你對代碼的自信程度,你能夠跳過它以提升些許的循環速度。可是當你對當前對象內容(和其原型鏈)不肯定的時候,添加hasOwnProperty()
更加保險些。
格式化的變化(通不過JSLint)會直接忽略掉花括號,把if語句放到同一行上。其優勢在於循環語句讀起來就像一個完整的想法(每一個元素都有一個本身的屬性」X」,使用」X」乾點什麼):
// 警告: 通不過JSLint檢測 var i, hasOwn = Object.prototype.hasOwnProperty; for (i in man) if (hasOwn.call(man, i)) { // 過濾 console.log(i, ":", man[i]); }
擴增構造函數的prototype屬性是個很強大的增長功能的方法,但有時候它太強大了。
增長內置的構造函數原型(如Object(), Array(), 或Function())挺誘人的,可是這嚴重下降了可維護性,由於它讓你的代碼變得難以預測。使用你代碼的其餘開發人員極可能更指望使用內置的 JavaScript方法來持續不斷地工做,而不是你另加的方法。
另外,屬性添加到原型中,可能會致使不使用hasOwnProperty屬性時在循環中顯示出來,這會形成混亂。
所以,不增長內置原型是最好的。你能夠指定一個規則,僅當下面的條件均知足時例外:
若是這三個條件獲得知足,你能夠給原型進行自定義的添加,形式以下:
if (typeof Object.protoype.myMethod !== "function") {
Object.protoype.myMethod = function () {
// 實現... };
}
你能夠經過相似下面形式的switch語句加強可讀性和健壯性:
var inspect_me = 0, result = ''; switch (inspect_me) { case 0: result = "zero"; break; case 1: result = "one"; break; default: result = "unknown"; }
這個簡單的例子中所遵循的風格約定以下:
JavaScript的變量在比較的時候會隱式類型轉換。這就是爲何一些諸如:false == 0 或 「」 == 0 返回的結果是true。爲避免引發混亂的隱含類型轉換,在你比較值和表達式類型的時候始終使用===和!==操做符。
var zero = 0; if (zero === false) { // 不執行,由於zero爲0, 而不是false }// 反面示例 if (zero == false) { // 執行了... }
還有另一種思想觀點認爲==就足夠了===是多餘的。例如,當你使用typeof你就知道它會返回一個字符串,因此沒有使用嚴格相等的理由。然而,JSLint要求嚴格相等,它使代碼看上去更有一致性,能夠下降代碼閱讀時的精力消耗。(「==是故意的仍是一個疏漏?」)
若是你如今的代碼中使用了eval(),記住該咒語「eval()是魔鬼」。此方法接受任意的字符串,並看成JavaScript代碼來處理。當有 問題的代碼是事先知道的(不是運行時肯定的),沒有理由使用eval()。若是代碼是在運行時動態生成,有一個更好的方式不使用eval而達到一樣的目 標。例如,用方括號表示法來訪問動態屬性會更好更簡單:
// 反面示例 var property = "name"; alert(eval("obj." + property));// 更好的 var property = "name"; alert(obj[property]);
使用eval()也帶來了安全隱患,由於被執行的代碼(例如從網絡來)可能已被篡改。這是個很常見的反面教材,當處理Ajax請求獲得的JSON 相應的時候。在這些狀況下,最好使用JavaScript內置方法來解析JSON相應,以確保安全和有效。若瀏覽器不支持JSON.parse(),你可 以使用來自JSON.org的庫。
一樣重要的是要記住,給setInterval(), setTimeout()和Function()構造函數傳遞字符串,大部分狀況下,與使用eval()是相似的,所以要避免。在幕後,JavaScript仍須要評估和執行你給程序傳遞的字符串:
// 反面示例 setTimeout("myFunc()", 1000); setTimeout("myFunc(1, 2, 3)", 1000);// 更好的 setTimeout(myFunc, 1000); setTimeout(function () { myFunc(1, 2, 3); }, 1000);
使用新的Function()構造就相似於eval(),應當心接近。這多是一個強大的構造,但每每被誤用。若是你絕對必須使用eval(),你 能夠考慮使用new Function()代替。有一個小的潛在好處,由於在新Function()中做代碼評估是在局部函數做用域中運行,因此代碼中任何被評估的經過var 定義的變量都不會自動變成全局變量。另外一種方法來阻止自動全局變量是封裝eval()調用到一個即時函數中。
考慮下面這個例子,這裏僅un
做爲全局變量污染了命名空間。
console.log(typeof un); // "undefined" console.log(typeof deux); // "undefined" console.log(typeof trois); // "undefined" var jsstring = "var un = 1; console.log(un);"; eval(jsstring); // logs "1" jsstring = "var deux = 2; console.log(deux);"; new Function(jsstring)(); // logs "2" jsstring = "var trois = 3; console.log(trois);"; (function () { eval(jsstring); }()); // logs "3" console.log(typeof un); // number console.log(typeof deux); // "undefined" console.log(typeof trois); // "undefined"
另外一間eval()和Function構造不一樣的是eval()能夠干擾做用域鏈,而Function()更安分守己些。無論你在哪裏執行 Function(),它只看到全局做用域。因此其能很好的避免本地變量污染。在下面這個例子中,eval()能夠訪問和修改它外部做用域中的變量,這是 Function作不來的(注意到使用Function和new Function是相同的)。
(function () { var local = 1; eval("local = 3; console.log(local)"); // logs "3" console.log(local); // logs "3" }()); (function () { var local = 1; Function("console.log(typeof local);")(); // logs undefined }());
使用parseInt()你能夠從字符串中獲取數值,該方法接受另外一個基數參數,這常常省略,但不該該。當字符串以」0″開頭的時候就有可能會出問 題,例如,部分時間進入表單域,在ECMAScript 3中,開頭爲」0″的字符串被當作8進制處理了,但這已在ECMAScript 5中改變了。爲了不矛盾和意外的結果,老是指定基數參數。
var month = "06", year = "09"; month = parseInt(month, 10); year = parseInt(year, 10);
此例中,若是你忽略了基數參數,如parseInt(year),返回的值將是0,由於「09」被當作8進制(比如執行 parseInt( year, 8 )),而09在8進制中不是個有效數字。
替換方法是將字符串轉換成數字,包括:
+"08" // 結果是 8 Number("08") // 8
這些一般快於parseInt(),由於parseInt()方法,顧名思意,不是簡單地解析與轉換。可是,若是你想輸入例如「08 hello」,parseInt()將返回數字,而其它以NaN了結。
創建和遵循編碼規範是很重要的,這讓你的代碼保持一致性,可預測,更易於閱讀和理解。一個新的開發者加入這個團隊能夠通讀規範,理解其它團隊成員書寫的代碼,更快上手幹活。
許多激烈的爭論發生會議上或是郵件列表上,問題每每針對某些代碼規範的特定方面(例如代碼縮進,是Tab製表符鍵仍是space空格鍵)。若是你是 你組織中建議採用規範的,準備好面對各類反對的或是聽起來不一樣但很強烈的觀點。要記住,創建和堅決不移地遵循規範要比糾結於規範的細節重要的多。
代碼沒有縮進基本上就不能讀了。惟一糟糕的事情就是不一致的縮進,由於它看上去像是遵循了規範,可是可能一路上伴隨着混亂和驚奇。重要的是規範地使用縮進。
一些開發人員更喜歡用tab製表符縮進,由於任何人均可以調整他們的編輯器以本身喜歡的空格數來顯示Tab。有些人喜歡空格——一般四個,這都無所謂,只要團隊每一個人都遵循同一個規範就行了。這本書,例如,使用四個空格縮進,這也是JSLint中默認的縮進。
什麼應該縮進呢?規則很簡單——花括號裏面的東西。這就意味着函數體,循環 (do, while, for, for-in),if,switch,以及對象字面量中的對象屬性。下面的代碼就是使用縮進的示例:
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; }
花括號(亦稱大括號,下同)應總被使用,即便在它們爲可選的時候。技術上將,在in或是for中若是語句僅一條,花括號是不須要的,可是你仍是應該老是使用它們,這會讓代碼更有持續性和易於更新。
想象下你有一個只有一條語句的for循環,你能夠忽略花括號,而沒有解析的錯誤。
// 糟糕的實例 for (var i = 0; i < 10; i += 1)
alert(i);
可是,若是,後來,主體循環部分又增長了行代碼?
// 糟糕的實例 for (var i = 0; i < 10; i += 1)
alert(i);
alert(i + " is " + (i % 2 ? "odd" : "even"));
第二個alert已經在循環以外,縮進可能欺騙了你。爲了長遠打算,最好老是使用花括號,即時值一行代碼:
// 好的實例 for (var i = 0; i < 10; i += 1) {
alert(i);
}
if條件相似:
// 壞 if (true) alert(1); else alert(2);// 好 if (true) { alert(1); } else { alert(2); }
開發人員對於左大括號的位置有着不一樣的偏好——在同一行或是下一行。
if (true) { alert("It's TRUE!"); }
或
if (true) { alert("It's TRUE!"); }
這個實例中,仁者見仁智者見智,但也有個案,括號位置不一樣會有不一樣的行爲表現。這是由於分號插入機制(semicolon insertion mechanism)——JavaScript是不挑剔的,當你選擇不使用分號結束一行代碼時JavaScript會本身幫你補上。這種行爲可能會致使麻 煩,如當你返回對象字面量,而左括號卻在下一行的時候:
// 警告: 意外的返回值 function func() { return // 下面代碼不執行 { name : "Batman" } }
若是你但願函數返回一個含有name屬性的對象,你會驚訝。因爲隱含分號,函數返回undefined。前面的代碼等價於:
// 警告: 意外的返回值 function func() { return undefined; // 下面代碼不執行 { name : "Batman" } }
總之,老是使用花括號,並始終把在與以前的語句放在同一行:
function func() { return { name : "Batman" }; }
關於分號注:就像使用花括號,你應該老是使用分號,即便他們可由JavaScript解析器隱式建立。這不只促進更科學和更嚴格的代碼,並且有助於解決存有疑惑的地方,就如前面的例子顯示。
空格的使用一樣有助於改善代碼的可讀性和一致性。在寫英文句子的時候,在逗號和句號後面會使用間隔。在JavaScript中,你能夠按照一樣的邏輯在列表模樣表達式(至關於逗號)和結束語句(相對於完成了「想法」)後面添加間隔。
適合使用空格的地方包括:
for (var i = 0; i < 10; i += 1) {...}
for (var i = 0, max = 10; i < max; i += 1) {...}
var a = [1, 2, 3];
var o = {a: 1, b: 2};
myFunc(a, b, c)
function myFunc() {}
var myFunc = function () {};
使用空格分開全部的操做符和操做對象是另外一個不錯的使用,這意味着在+, -, *, =, <, >, <=, >=, ===, !==, &&, ||, +=
等先後都須要空格。
// 寬鬆一致的間距 // 使代碼更易讀 // 使得更加「透氣」 var d = 0, a = b + 1; if (a && b && c) { d = a % c; a += d; }// 反面例子 // 缺失或間距不一 // 使代碼變得疑惑 var d = 0, a = b + 1; if (a && b && c) { d = a % c; a += d; }
//zxx:我就琢磨着這正面和反面例子不長得同樣嗎...原文就是如此,我也很差擅自改動。
最後須要注意的一個空格——花括號間距。最好使用空格:
空格使用的一點不足就是增長了文件的大小,可是壓縮無此問題。
有一個常常被忽略的代碼可讀性方面是垂直空格的使用。你可使用空行來分隔代碼單元,就像是文學做品中使用段落分隔同樣。
另外一種方法讓你的代碼更具可預測性和可維護性是採用命名規範。這就意味着你須要用同一種形式給你的變量和函數命名。
下面是建議的一些命名規範,你能夠原樣採用,也能夠根據本身的喜愛做調整。一樣,遵循規範要比規範是什麼更重要。
JavaScript並無類,但有new調用的構造函數:
var adam = new Person();
由於構造函數仍僅僅是函數,僅看函數名就能夠幫助告訴你這應該是一個構造函數仍是一個正常的函數。
命名構造函數時首字母大寫具備暗示做用,使用小寫命名的函數和方法不該該使用new調用:
function MyConstructor() {...} function myFunction() {...}
當你的變量或是函數名有多個單詞的時候,最好單詞的分離遵循統一的規範,有一個常見的作法被稱做「駝峯(Camel)命名法」,就是單詞小寫,每一個單詞的首字母大寫。
對於構造函數,可使用大駝峯式命名法(upper camel case),如MyConstructor()
。對於函數和方法名稱,你可使用小駝峯式命名法(lower camel case),像是myFunction(), calculateArea()
和getFirstName()
。
要是變量不是函數呢?開發者一般使用小駝峯式命名法,但還有另一種作法就是全部單詞小寫如下劃線鏈接:例如,first_name, favorite_bands,
和 old_company_name
,這種標記法幫你直觀地區分函數和其餘標識——原型和對象。
ECMAScript的屬性和方法均使用Camel標記法,儘管多字的屬性名稱是罕見的(正則表達式對象的lastIndex和ignoreCase屬性)。
有時,開發人員使用命名規範來彌補或替代語言特性。
例如,JavaScript中沒有定義常量的方法(儘管有些內置的像Number, MAX_VALUE),因此開發者都採用所有單詞大寫的規範來命名這個程序生命週期中都不會改變的變量,如:
// 珍貴常數,只可遠觀 var PI = 3.14,
MAX_WIDTH = 800;
還有另一個徹底大寫的慣例:全局變量名字所有大寫。所有大寫命名全局變量能夠增強減少全局變量數量的實踐,同時讓它們易於區分。
另一種使用規範來模擬功能的是私有成員。雖然能夠在JavaScript中實現真正的私有,可是開發者發現僅僅使用一個下劃線前綴來表示一個私有屬性或方法會更容易些。考慮下面的例子:
var person = { getName: function () { return this._getFirst() + ' ' + this._getLast(); }, _getFirst: function () { // ... }, _getLast: function () { // ... } };
在此例中,getName()
就表示公共方法,部分穩定的API。而_getFirst()
和_getLast()
則代表了私有。它們仍然是正常的公共方法,可是使用下劃線前綴來警告person對象的使用者這些方法在下一個版本中時不能保證工做的,是不能直接使用的。注意,JSLint有些不鳥下劃線前綴,除非你設置了noman選項爲:false。
下面是一些常見的_private規範:
你必須註釋你的代碼,即便不會有其餘人向你同樣接觸它。一般,當你深刻研究一個問題,你會很清楚的知道這個代碼是幹嗎用的,可是,當你一週以後再回來看的時候,想必也要耗掉很多腦細胞去搞明白到底怎麼工做的。
很顯然,註釋不能走極端:每一個單獨變量或是單獨一行。可是,你一般應該記錄全部的函數,它們的參數和返回值,或是任何不尋常的技術和方法。要想到注 釋能夠給你代碼將來的閱讀者以諸多提示;閱讀者須要的是(不要讀太多的東西)僅註釋和函數屬性名來理解你的代碼。例如,當你有五六行程序執行特定的任務, 若是你提供了一行代碼目的以及爲何在這裏的描述的話,閱讀者就能夠直接跳過這段細節。沒有硬性規定註釋代碼比,代碼的某些部分(如正則表達式)可能註釋 要比代碼多。
最重要的習慣,然而也是最難遵照的,就是保持註釋的及時更新,由於過期的註釋比沒有註釋更加的誤導人。
Stoyan Stefanov是Yahoo!web開發人員,多個O'Reilly書籍的做者、投稿者和技術評審。他常常在會議和他的博客www.phpied.com上發表web開發主題的演講。Stoyan仍是smush.it圖片優化工具的創造者,YUI貢獻者,雅虎性能優化工具YSlow 2.0的架構設計師。
//zxx:以上就是翻譯的所有內容,夠長吧~~
廢話很少說。翻譯水平拙劣,因此文中不免會有翻譯不許確的地方,我爸不是李剛,因此要是翻譯或表述有錯請放心大膽地大力指正,不甚感謝。
原創文章,轉載請註明來自張鑫旭-鑫空間-鑫生活[http://www.zhangxinxu.com]
本文地址:http://www.zhangxinxu.com/wordpress/?p=1173