Skip to content

Airbnb JavaScript Style Guide 正體中文版

Notifications You must be signed in to change notification settings

force416/JavaScript-Style-Guide

Repository files navigation

Airbnb JavaScript Style Guide(){

一份彙整了在 JavasScript 中被普遍使用的風格指南。

Downloads![Gitter](https://badges.gitter.im/Join Chat.svg)

其他風格指南

翻譯自 Airbnb JavaScript Style Guide

目錄

  1. 資料型態
  2. 參考
  3. 物件
  4. 陣列
  5. 解構子
  6. 字串
  7. 函式
  8. 箭頭函式
  9. 建構子
  10. 模組
  11. 迭代器及產生器
  12. 屬性
  13. 變數
  14. 提升
  15. 條件式與等號
  16. 區塊
  17. 註解
  18. 空格
  19. 逗號
  20. 分號
  21. 型別轉換
  22. 命名規則
  23. 存取器
  24. 事件
  25. jQuery
  26. ECMAScript 5 相容性
  27. ECMAScript 6 風格
  28. 測試
  29. 效能
  30. 資源
  31. 誰在使用
  32. 翻譯
  33. JavaScript 風格指南
  34. 和我們討論 Javascript
  35. 貢獻者
  36. 授權許可

資料型態

  • 1.1基本:你可以直接存取基本資料型態。

    • 字串
    • 數字
    • 布林
    • null
    • undefined
    constfoo=1;letbar=foo;bar=9;console.log(foo,bar);// => 1, 9
  • 1.2複合:你需要透過引用的方式存取複合資料型態。

    • 物件
    • 陣列
    • 函式
    constfoo=[1,2];constbar=foo;bar[0]=9;console.log(foo[0],bar[0]);// => 9, 9

⬆ 回到頂端

參考

  • 2.1 對於所有的參考使用 const;避免使用 var。eslint: prefer-const, no-const-assign

    為什麼?因為這能確保你無法對參考重新賦值,也不會讓你的程式碼有錯誤或難以理解。

    // badvara=1;varb=2;// goodconsta=1;constb=2;
  • 2.2 如果你需要可變動的參考,使用 let 代替 var。eslint: no-var jscs: disallowVar

    為什麼?因為 let 的作用域是在區塊內,而不像 var 是在函式內。

    // badvarcount=1;if(true){count+=1;}// good, use the let.letcount=1;if(true){count+=1;}
  • 2.3 請注意,letconst 的作用域都只在區塊內。

    // const 及 let 只存在於他們被定義的區塊內。{leta=1;constb=1;}console.log(a);// ReferenceErrorconsole.log(b);// ReferenceError

⬆ 回到頂端

物件

  • 3.1 使用簡潔的語法建立物件。eslint rules: no-new-object.

    // badconstitem=newObject();// goodconstitem={};
  • 3.2 別使用保留字當作鍵值,他在 IE8 上不會被執行。了解更多。不過在 ES6 模組及伺服器端程式碼中使用是可行的。jscs: disallowIdentifierNames

    // badconstsuperman={default: {clark: 'kent'},private: true,};// goodconstsuperman={defaults: {clark: 'kent'},hidden: true,};
  • 3.3 使用同義詞取代保留字。jscs: disallowIdentifierNames

    // badconstsuperman={class: 'alien',};// badconstsuperman={klass: 'alien',};// goodconstsuperman={type: 'alien',};

  • 3.4 建立具有動態屬性名稱的物件時請使用可被計算的屬性名稱。

    為什麼?因為這樣能夠讓你在同一個地方定義所有的物件屬性。

    functiongetKey(k){return`a key named ${k}`;}// badconstobj={id: 5,name: 'San Francisco',};obj[getKey('enabled')]=true;// goodconstobj={id: 5,name: 'San Francisco',[getKey('enabled')]: true,};

  • 3.5 使用物件方法的簡寫。eslint: object-shorthand jscs: requireEnhancedObjectLiterals

    // badconstatom={value: 1,addValue: function(value){returnatom.value+value;},};// goodconstatom={value: 1,addValue(value){returnatom.value+value;},};

  • 3.6 使用屬性值的簡寫。eslint: object-shorthand jscs: requireEnhancedObjectLiterals

    為什麼?因為寫起來更短且更有描述性。

    constlukeSkywalker='Luke Skywalker';// badconstobj={lukeSkywalker: lukeSkywalker,};// goodconstobj={ lukeSkywalker,};
  • 3.7 請在物件宣告的開頭將簡寫的屬性分組。

    為什麼?因為這樣能夠很簡單的看出哪些屬性是使用簡寫。

    constanakinSkywalker='Anakin Skywalker';constlukeSkywalker='Luke Skywalker';// badconstobj={episodeOne: 1,twoJediWalkIntoACantina: 2, lukeSkywalker,episodeThree: 3,mayTheFourth: 4, anakinSkywalker,};// goodconstobj={ lukeSkywalker, anakinSkywalker,episodeOne: 1,twoJediWalkIntoACantina: 2,episodeThree: 3,mayTheFourth: 4,};
  • 3.8 只在無效的鍵加上引號。eslint: quote-props jscs: disallowQuotedKeysInObjects

為什麼?整體來說,我們認為這在主觀上更容易閱讀。它會改善語法高亮,也能讓多數的 JS 引擎更容易最佳化。

// badconstbad={'foo': 3,'bar': 4,'data-blah': 5,};// goodconstgood={foo: 3,bar: 4,'data-blah': 5,};

⬆ 回到頂端

陣列

  • 4.1 使用簡潔的語法建立陣列。eslint: no-array-constructor

    // badconstitems=newArray();// goodconstitems=[];
  • 4.2 如果你不知道陣列的長度請使用 Array#push。

    constsomeStack=[];// badsomeStack[someStack.length]='abracadabra';// goodsomeStack.push('abracadabra');

  • 4.3 使用陣列的擴展運算子 ... 來複製陣列。

    // badconstlen=items.length;constitemsCopy=[];leti;for(i=0;i<len;i++){itemsCopy[i]=items[i];}// goodconstitemsCopy=[...items];
  • 4.4 如果要轉換一個像陣列的物件至陣列,可以使用 Array#from。

    constfoo=document.querySelectorAll('.foo');constnodes=Array.from(foo);
  • 4.5 在陣列方法的回呼使用 return 宣告。若函式本體是如 8.2 的單一語法,那麼省略 return 是可以的。eslint: array-callback-return

    // good[1,2,3].map((x)=>{consty=x+1;returnx*y;});// good[1,2,3].map(x=>x+1);// badconstflat={};[[0,1],[2,3],[4,5]].reduce((memo,item,index)=>{constflatten=memo.concat(item);flat[index]=memo.concat(item);});// goodconstflat={};[[0,1],[2,3],[4,5]].reduce((memo,item,index)=>{constflatten=memo.concat(item);flat[index]=flatten;returnflatten;});// badinbox.filter((msg)=>{const{ subject, author }=msg;if(subject==='Mockingbird'){returnauthor==='Harper Lee';}else{returnfalse;}});// goodinbox.filter((msg)=>{const{ subject, author }=msg;if(subject==='Mockingbird'){returnauthor==='Harper Lee';}returnfalse;});

⬆ 回到頂端

解構子

  • 5.1 存取或使用多屬性的物件時,請使用物件解構子。jscs: requireObjectDestructuring

    為什麼?因為解構子能夠節省你對這些屬性建立暫時的參考。

    // badfunctiongetFullName(user){constfirstName=user.firstName;constlastName=user.lastName;return`${firstName}${lastName}`;}// goodfunctiongetFullName(user){const{ firstName, lastName }=user;return`${firstName}${lastName}`;}// bestfunctiongetFullName({ firstName, lastName }){return`${firstName}${lastName}`;}
  • 5.2 使用陣列解構子。jscs: requireArrayDestructuring

    constarr=[1,2,3,4];// badconstfirst=arr[0];constsecond=arr[1];// goodconst[first,second]=arr;
  • 5.3 需要回傳多個值時請使用物件解構子,而不是陣列解構子。

    為什麼?因為你可以增加新的屬性或改變排序且不須更動呼叫的位置。

    // badfunctionprocessInput(input){// 這時神奇的事情出現了return[left,right,top,bottom];}// 呼叫時必須考慮回傳資料的順序。const[left,__,top]=processInput(input);// goodfunctionprocessInput(input){// 這時神奇的事情出現了return{ left, right, top, bottom };}// 呼叫時只需選擇需要的資料const{ left, right }=processInput(input);

⬆ 回到頂端

字串

  • 6.1 字串請使用單引號 ''。eslint: quotes jscs: validateQuoteMarks

    // badconstname="Capt. Janeway";// goodconstname='Capt. Janeway';
  • 6.2 如果字串超過 100 個字元,請使用字串連接符號換行。

  • 6.3 注意:過度的長字串連接可能會影響效能。jsPerf討論串

    // badconsterrorMessage='This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';// badconsterrorMessage='This is a super long error that was thrown because \of Batman. When you stop to think about how Batman had anything to do \with this, you would get nowhere \fast.';// goodconsterrorMessage='This is a super long error that was thrown because '+'of Batman. When you stop to think about how Batman had anything to do '+'with this, you would get nowhere fast.';

  • 6.4 當以程式方式建構字串時,請使用模板字串而不是字串連接。eslint: prefer-templatetemplate-curly-spacing jscs: requireTemplateStrings

    為什麼?因為模板字串更有可讀性,正確的換行符號及字串插值功能讓語法更簡潔。

    // badfunctionsayHi(name){return'How are you, '+name+'?';}// badfunctionsayHi(name){return['How are you, ',name,'?'].join();}// badfunctionsayHi(name){return`How are you, ${name}?`;}// goodfunctionsayHi(name){return`How are you, ${name}?`;}
  • 6.5 千萬不要在字串中使用 eval(),會造成許多的漏洞。

⬆ 回到頂端

函式

  • 7.1 使用函式宣告而不是函式表達式。jscs: requireFunctionDeclarations

    為什麼?因為函式宣告是可命名的,所以他們在呼叫堆疊中更容易被識別。此外,函式宣告自身都會被提升,而函式表達式則只有參考會被提升。這個規則使得箭頭函式可以完全取代函式表達式。

    // badconstfoo=function(){};// goodfunctionfoo(){}
  • 7.2 立即函式:eslint: wrap-iife jscs: requireParenthesesAroundIIFE

    為什麼?一個立即函式是個獨立的單元-將函式及呼叫函式的括號包起來明確表示這一點。注意在模組世界的任何地方,你都不需要使用立即函式。

    // 立即函式(IIFE)(function(){console.log('Welcome to the Internet. Please follow me.');}());
  • 7.3 絕對不要在非函式的區塊(if、while 等等)宣告函式。你可以將函式賦予至變數解決這個問題。瀏覽器會允許你這麼做,但不同瀏覽器產生的結果可能會不同。eslint: no-loop-func

  • 7.4 **注意:**ECMA-262 將區塊定義為陳述式。函式宣告則不是陳述式。閱讀 ECMA-262 關於這個問題的說明

    // badif(currentUser){functiontest(){console.log('Nope.');}}// goodlettest;if(currentUser){test=()=>{console.log('Yup.');};}
  • 7.5 請勿將參數命名為 arguments,這樣會將覆蓋掉函式作用域傳來的 arguments

    // badfunctionnope(name,options,arguments){// ...stuff...}// goodfunctionyup(name,options,args){// ...stuff...}

  • 7.6 絕對不要使用 arguments,可以選擇使用 rest 語法 ... 替代。prefer-rest-params

    為什麼?使用 ... 能夠明確指出你要將參數傳入哪個變數。再加上 rest 參數是一個真正的陣列,而不像 arguments 似陣列而非陣列。

    // badfunctionconcatenateAll(){constargs=Array.prototype.slice.call(arguments);returnargs.join('');}// goodfunctionconcatenateAll(...args){returnargs.join('');}

  • 7.7 使用預設參數的語法,而不是變動函式的參數。

    // really badfunctionhandleThings(opts){// 不!我們不該變動函式的參數。// Double bad: 如果 opt 是 false ,那們它就會被設定為一個物件,// 或許你想要這麼做,但是這樣可能會造成一些 Bug。opts=opts||{};// ...}// still badfunctionhandleThings(opts){if(opts===void0){opts={};}// ...}// goodfunctionhandleThings(opts={}){// ...}
  • 7.8 使用預設參數時請避免副作用。

    為什麼?因為這樣會讓思緒混淆。

    varb=1;// badfunctioncount(a=b++){console.log(a);}count();// 1count();// 2count(3);// 3count();// 3
  • 7.9 永遠將預設參數放置於最後。

    // badfunctionhandleThings(opts={},name){// ...}// goodfunctionhandleThings(name,opts={}){// ...}
  • 7.10 千萬別使用建構函式去建立一個新的函式。

    為什麼?透過這種方式建立一個函數來計算字串類似於 eval(),會造成許多的漏洞。

    // badvaradd=newFunction('a','b','return a + b');// still badvarsubtract=Function('a','b','return a - b');
  • 7.11 在函式的標示後放置空格。

    為什麼?一致性較好,而且你不應該在新增或刪除名稱時增加或減少空格。

    // badconstf=function(){};constg=function(){};consth=function(){};// goodconstx=function(){};consty=functiona(){};
  • 7.12 切勿變更參數。eslint: no-param-reassign

    為什麼?操作作為參數傳入的物件可能導致變數產生原呼叫者不期望的副作用。

    // badfunctionf1(obj){obj.key=1;};// goodfunctionf2(obj){constkey=Object.prototype.hasOwnProperty.call(obj,'key') ? obj.key : 1;};
  • 7.13 切勿重新賦值給參數。eslint: no-param-reassign

為什麼?將參數重新賦值可能導致意外的行為,尤其在存取 arguments 物件時。它可能會引起最佳化的問題,尤其在 V8。

```javascript // bad function f1(a){a = 1} function f2(a){if (!a){a = 1} } // good function f3(a){const b = a || 1} function f4(a = 1){} ``` 

⬆ 回到頂端

箭頭函式

  • 8.1 當你必須使用函式表達式(或傳遞一個匿名函式)時,請使用箭頭函式的符號。eslint: prefer-arrow-callback, arrow-spacing jscs: requireArrowFunctions

    為什麼?它會在有 this 的內部建立了一個新版本的函式,通常功能都是你所想像的,而且語法更為簡潔。

    為什麼不?如果你已經有一個相當複雜的函式時,或許你該將邏輯都移到一個函式宣告上。

    // bad[1,2,3].map(function(x){consty=x+1;returnx*y;});// good[1,2,3].map((x)=>{consty=x+1;returnx*y;});
  • 8.2 如果函式適合只使用一行,你可以很隨性的省略大括號及使用隱藏的回傳。否則請使用 return 語法。eslint: arrow-parens, arrow-body-style jscs: disallowParenthesesAroundArrowParam, requireShorthandArrowFunctions

    為什麼?因為語法修飾。這樣能夠在多個函式鏈結在一起的時候更易讀。

    為什麼不?如果你打算回傳一個物件。

    // bad[1,2,3].map(number=>{constnextNumber=number+1;`A string containing the ${nextNumber}.`;});// good[1,2,3].map(number=>`A string containing the ${number}.`);// good[1,2,3].map((number)=>{constnextNumber=number+1;return`A string containing the ${nextNumber}.`;});
  • 8.3 如果表達式跨了多行,請將它們包在括號中增加可讀性。

    為什麼?這麼做更清楚的表達函式的開始與結束的位置。

    // bad[1,2,3].map(number=>'As time went by, the string containing the '+`${number} became much longer. So we needed to break it over multiple `+'lines.');// good[1,2,3].map(number=>(`As time went by, the string containing the ${number} became much `+'longer. So we needed to break it over multiple lines.'));
  • 8.4 如果你的函式只使用一個參數,那麼可以很隨意的省略括號。否則請在參數兩側加上括號。eslint: arrow-parens jscs: disallowParenthesesAroundArrowParam

    為什麼?減少視覺上的混亂。

    // bad[1,2,3].map((x)=>x*x);// good[1,2,3].map(x=>x*x);// good[1,2,3].map(number=>(`A long string with the ${number}. It’s so long that we’ve broken it `+'over multiple lines!'));// bad[1,2,3].map(x=>{consty=x+1;returnx*y;});// good[1,2,3].map((x)=>{consty=x+1;returnx*y;});
  • 8.5 避免混淆箭頭函式語法(=>)及比較運算子(<=>=)。eslint: no-confusing-arrow

    // badconstitemHeight=item=>item.height>256 ? item.largeSize : item.smallSize;// badconstitemHeight=(item)=>item.height>256 ? item.largeSize : item.smallSize;// goodconstitemHeight=item=>{returnitem.height>256 ? item.largeSize : item.smallSize;}

⬆ 回到頂端

建構子

  • 9.1 總是使用 class。避免直接操作 prototype

    為什麼?因為 class 語法更簡潔且更易讀。

    // badfunctionQueue(contents=[]){this._queue=[...contents];}Queue.prototype.pop=function(){constvalue=this._queue[0];this._queue.splice(0,1);returnvalue;}// goodclassQueue{constructor(contents=[]){this._queue=[...contents];}pop(){constvalue=this._queue[0];this._queue.splice(0,1);returnvalue;}}
  • 9.2 使用 extends 繼承。

    為什麼?因為他是一個內建繼承原型方法的方式,且不會破壞 instanceof

    // badconstinherits=require('inherits');functionPeekableQueue(contents){Queue.apply(this,contents);}inherits(PeekableQueue,Queue);PeekableQueue.prototype.peek=function(){returnthis._queue[0];}// goodclassPeekableQueueextendsQueue{peek(){returnthis._queue[0];}}
  • 9.3 方法可以回傳 this 幫助方法鏈結。

    // badJedi.prototype.jump=function(){this.jumping=true;returntrue;};Jedi.prototype.setHeight=function(height){this.height=height;};constluke=newJedi();luke.jump();// => trueluke.setHeight(20);// => undefined// goodclassJedi{jump(){this.jumping=true;returnthis;}setHeight(height){this.height=height;returnthis;}}constluke=newJedi();luke.jump().setHeight(20);
  • 9.4 可以寫一個 toString() 的方法,但是請確保它可以正常執行且沒有函式副作用。

    classJedi{constructor(options={}){this.name=options.name||'no name';}getName(){returnthis.name;}toString(){return`Jedi - ${this.getName()}`;}}
  • 9.5 若類別沒有指定建構子,那它會擁有預設的建構子。一個空的建構子函式或只委派給父類別是不必要的。no-useless-constructor

    // badclassJedi{constructor(){}getName(){returnthis.name;}}// badclassReyextendsJedi{constructor(...args){super(...args);}}// goodclassReyextendsJedi{constructor(...args){super(...args);this.name='Rey';}}

⬆ 回到頂端

模組

  • 10.1 總是使用模組(import/export)勝過一個非標準模組的系統。你可以編譯為喜歡的模組系統。

    為什麼?模組就是未來的趨勢,讓我們現在就開始前往未來吧。

    // badconstAirbnbStyleGuide=require('./AirbnbStyleGuide');module.exports=AirbnbStyleGuide.es6;// okimportAirbnbStyleGuidefrom'./AirbnbStyleGuide';exportdefaultAirbnbStyleGuide.es6;// bestimport{es6}from'./AirbnbStyleGuide';exportdefaultes6;
  • 10.2 請別使用萬用字元引入。

    為什麼?這樣能夠確保你只有一個預設導出。

    // badimport*asAirbnbStyleGuidefrom'./AirbnbStyleGuide';// goodimportAirbnbStyleGuidefrom'./AirbnbStyleGuide';
  • 10.3 然後也不要在引入的地方導出。

    為什麼?雖然一行程式相當的簡明,但是讓引入及導出各自有明確的方式能夠讓事情保持一致。

    // bad// filename es6.jsexport{es6asdefault}from'./airbnbStyleGuide';// good// filename es6.jsimport{es6}from'./AirbnbStyleGuide';exportdefaultes6;

⬆ 回到頂端

迭代器及產生器

  • 11.1 不要使用迭代器。更好的做法是使用 JavaScript 的高階函式,像是 map()reduce(),替代如 for-of 的迴圈語法。eslint: no-iterator

    為什麼?這加強了我們不變的規則。處理純函式的回傳值讓程式碼更易讀,勝過它所造成的函式副作用。

    eslint rules: no-iterator.

    constnumbers=[1,2,3,4,5];// badletsum=0;for(letnumofnumbers){sum+=num;}sum===15;// goodletsum=0;numbers.forEach(num=>sum+=num);sum===15;// best (使用 javascript 的高階函式)constsum=numbers.reduce((total,num)=>total+num,0);sum===15;
  • 11.2 現在還不要使用產生器。

    為什麼?因為它現在編譯至 ES5 還沒有編譯得非常好。

⬆ 回到頂端

屬性

  • 12.1 使用點 . 來存取屬性。eslint: dot-notation jscs: requireDotNotation

    constluke={jedi: true,age: 28,};// badconstisJedi=luke['jedi'];// goodconstisJedi=luke.jedi;
  • 12.2 需要帶參數存取屬性時請使用中括號 []

    constluke={jedi: true,age: 28,};functiongetProp(prop){returnluke[prop];}constisJedi=getProp('jedi');

⬆ 回到頂端

變數

  • 13.1 為了避免污染全域的命名空間,請使用 const 來宣告變數,如果不這麼做將會產生全域變數。Captain Planet warned us of that.

    // badsuperPower=newSuperPower();// goodconstsuperPower=newSuperPower();
  • 13.2 每個變數只使用一個 const 來宣告。eslint: one-var jscs: disallowMultipleVarDecl

    為什麼?因為這樣更容易增加新的變數宣告,而且你也不用擔心替換 ;, 及加入的標點符號不同的問題。

    // badconstitems=getItems(),goSportsTeam=true,dragonball='z';// bad// (比較上述例子找出錯誤)constitems=getItems(),goSportsTeam=true;dragonball='z';// goodconstitems=getItems();constgoSportsTeam=true;constdragonball='z';
  • 13.3 將所有的 constlet 分組。

    為什麼?當你需要根據之前已賦值的變數來賦值給未賦值變數時相當有幫助。

    // badleti,len,dragonball,items=getItems(),goSportsTeam=true;// badleti;constitems=getItems();letdragonball;constgoSportsTeam=true;letlen;// goodconstgoSportsTeam=true;constitems=getItems();letdragonball;leti;letlength;
  • 13.4 在你需要的地方賦值給變數,但是請把它們放在合理的位置。

    為什麼?因為 letconst 是在區塊作用域內,而不是函式作用域。

    // bad - unnecessary function callfunctioncheckName(hasName){constname=getName();if(hasName==='test'){returnfalse;}if(name==='test'){this.setName('');returnfalse;}returnname;}// goodfunctioncheckName(hasName){if(hasName==='test'){returnfalse;}constname=getName();if(name==='test'){this.setName('');returnfalse;}returnname;}

⬆ 回到頂端

提升

  • 14.1var 宣告可以被提升至該作用域的最頂層,但賦予的值並不會。constlet 的宣告被賦予了新的概念,稱為暫時性死區(Temporal Dead Zones, TDZ)。這對於瞭解為什麼 typeof 不再那麼安全是相當重要的。

    // 我們知道這樣是行不通的// (假設沒有名為 notDefined 的全域變數)functionexample(){console.log(notDefined);// => throws a ReferenceError}// 由於變數提升的關係,// 你在引用變數後再宣告變數是行得通的。// 注:賦予給變數的 `true` 並不會被提升。functionexample(){console.log(declaredButNotAssigned);// => undefinedvardeclaredButNotAssigned=true;}// 直譯器會將宣告的變數提升至作用域的最頂層,// 表示我們可以將這個例子改寫成以下:functionexample(){letdeclaredButNotAssigned;console.log(declaredButNotAssigned);// => undefineddeclaredButNotAssigned=true;}// 使用 const 及 letfunctionexample(){console.log(declaredButNotAssigned);// => throws a ReferenceErrorconsole.log(typeofdeclaredButNotAssigned);// => throws a ReferenceErrorconstdeclaredButNotAssigned=true;}
  • 14.2 賦予匿名函式的變數會被提升,但函式內容並不會。

    functionexample(){console.log(anonymous);// => undefinedanonymous();// => TypeError anonymous is not a functionvaranonymous=function(){console.log('anonymous function expression');};}
  • 14.3 賦予命名函式的變數會被提升,但函式內容及函式名稱並不會。

    functionexample(){console.log(named);// => undefinednamed();// => TypeError named is not a functionsuperPower();// => ReferenceError superPower is not definedvarnamed=functionsuperPower(){console.log('Flying');};}// 當函式名稱和變數名稱相同時也是如此。functionexample(){console.log(named);// => undefinednamed();// => TypeError named is not a functionvarnamed=functionnamed(){console.log('named');}}
  • 14.4 宣告函式的名稱及函式內容都會被提升。

    functionexample(){superPower();// => FlyingfunctionsuperPower(){console.log('Flying');}}
  • 想瞭解更多訊息,請參考 Ben CherryJavaScript Scoping & Hoisting

⬆ 回到頂端

條件式與等號

Comparison Operators & Equality

  • 15.1 請使用 ===!== ,別使用 ==!= 。eslint: eqeqeq

  • 15.2 像是 if 的條件語法內會使用 ToBoolean 的抽象方法強轉類型,並遵循以下規範:

    • 物件 轉換為 true
    • Undefined 轉換為 false
    • Null 轉換為 false
    • 布林 轉換為 該布林值
    • 數字 如果是 +0, -0, 或 NaN 則轉換為 false,其他的皆為 true
    • 字串 如果是空字串 '' 則轉換為 false,其他的皆為 true
    if([0]&&[]){// true// 陣列(即使為空)為一個物件,所以轉換為 true}
  • 15.3 使用簡短的方式。

    // badif(name!==''){// ...stuff...}// goodif(name){// ...stuff...}// badif(collection.length>0){// ...stuff...}// goodif(collection.length){// ...stuff...}
  • 15.4 想瞭解更多訊息請參考 Angus Croll 的 Truth Equality and JavaScript

  • 15.5 Use braces to create blocks in case and default clauses that contain lexical declarations (e.g. let, const, function, and class).

  • 15.5casedefault 包含了宣告語法(例如:letconstfunctionclass)時使用大括號來建立區塊。

Why? Lexical declarations are visible in the entire switch block but only get initialized when assigned, which only happens when its case is reached. This causes problems when multiple case clauses attempt to define the same thing. 為什麼?宣告語法可以在整個 switch 區塊中可見,但是只在進入該 case 時初始化。當多個 case 語法時會導致嘗試定義相同事情的問題。

eslint rules: no-case-declarations.

```javascript // bad switch (foo){case 1: let x = 1; break; case 2: const y = 2; break; case 3: function f(){} break; default: class C{} } // good switch (foo){case 1:{let x = 1; break} case 2:{const y = 2; break} case 3:{function f(){} break} case 4: bar(); break; default:{class C{} } } ``` 
  • 15.6 不應該使用巢狀的三元運算子,且通常應該使用單行來表示。

    eslint rules: no-nested-ternary.

    // badconstfoo=maybe1>maybe2 ? "bar" : value1>value2 ? "baz" : null;// betterconstmaybeNull=value1>value2 ? 'baz' : null;constfoo=maybe1>maybe2 ? 'bar' : maybeNull;// bestconstmaybeNull=value1>value2 ? 'baz' : null;constfoo=maybe1>maybe2 ? 'bar' : maybeNull;
  • 15.7 避免不必要的三元運算子語法。

    eslint rules: no-unneeded-ternary.

    // badconstfoo=a ? a : b;constbar=c ? true : false;constbaz=c ? false : true;// goodconstfoo=a||b;constbar=!!c;constbaz=!c;

⬆ 回到頂端

區塊

  • 16.1 多行區塊請使用大括號刮起來。

    // badif(test)returnfalse;// goodif(test)returnfalse;// goodif(test){returnfalse;}// badfunctionfoo(){returnfalse;}// goodfunctionbar(){returnfalse;}
  • 16.2 如果你使用 ifelse 的多行區塊,請將 else 放在 if 區塊的結尾大括號後。eslint: brace-style jscs: disallowNewlineBeforeBlockStatements

    // badif(test){thing1();thing2();}else{thing3();}// goodif(test){thing1();thing2();}else{thing3();}

⬆ 回到頂端

註解

  • 17.1 多行註解請使用 /** ... */ ,包含描述,指定類型以及參數值還有回傳值。

    // bad// make() 根據傳入的 tag 名稱回傳一個新的元件//// @param{String} tag// @return{Element} elementfunctionmake(tag){// ...stuff...returnelement;}// good/** * make() 根據傳入的 tag 名稱回傳一個新的元件 * * @param{String} tag * @return{Element} element */functionmake(tag){// ...stuff...returnelement;}
  • 17.2 單行註解請使用 //。在欲註解的上方新增一行進行註解。在註解的上方空一行,除非他在區塊的第一行。

    // badconstactive=true;// 當目前分頁// good// is current tabconstactive=true;// badfunctiongetType(){console.log('fetching type...');// 設定預設的類型為 'no type'consttype=this._type||'no type';returntype;}// goodfunctiongetType(){console.log('fetching type...');// 設定預設的類型為 'no type'consttype=this._type||'no type';returntype;}// also goodfunctiongetType(){// set the default type to 'no type'consttype=this._type||'no type';returntype;}
  • 17.3 在註解前方加上 FIXMETODO 可以幫助其他開發人員快速瞭解這是一個需要重新討論的問題,或是一個等待解決的問題。和一般的註解不同,他們是可被執行的。對應的動作為 FIXME -- 重新討論並解決TODO -- 必須執行

  • 17.4 使用 // FIXME: 標注問題。

    classCalculatorextendsAbacus{constructor(){super();// FIXME: 不該在這使用全域變數total=0;}}
  • 17.5 使用 // TODO: 標注問題的解決方式。

    classCalculatorextendsAbacus{constructor(){super();// TODO: total 應該可被傳入的參數所修改this.total=0;}}

⬆ 回到頂端

空格

  • 18.1 將 Tab 設定為兩個空格。eslint: indent jscs: validateIndentation

    // badfunctionfoo(){∙∙∙∙constname;}// badfunctionbar(){∙constname;}// goodfunctionbaz(){∙∙constname;}
  • 18.2 在大括號前加一個空格。eslint: space-before-blocks jscs: requireSpaceBeforeBlockStatements

    // badfunctiontest(){console.log('test');}// goodfunctiontest(){console.log('test');}// baddog.set('attr',{age: '1 year',breed: 'Bernese Mountain Dog',});// gooddog.set('attr',{age: '1 year',breed: 'Bernese Mountain Dog',});
  • 18.3 在控制流程的語句(if, while 等等。)的左括號前加上一個空格。宣告的函式和傳入的變數間則沒有空格。eslint: space-after-keywords, space-before-keywords jscs: requireSpaceAfterKeywords

    // badif(isJedi){fight();}// goodif(isJedi){fight();}// badfunctionfight(){console.log('Swooosh!');}// goodfunctionfight(){console.log('Swooosh!');}
  • 18.4 將運算元用空格隔開。eslint: space-infix-ops jscs: requireSpaceBeforeBinaryOperators, requireSpaceAfterBinaryOperators

    // badconstx=y+5;// goodconstx=y+5;
  • 18.5 在檔案的最尾端加上一行空白行。

    // bad(function(global){// ...stuff...})(this);
    // bad(function(global){// ...stuff...})(this);
    // good(function(global){// ...stuff...})(this);
  • 18.6 當多個方法鏈結(大於兩個方法鏈結)時請換行縮排。利用前面的 . 強調該行是呼叫方法,而不是一個新的宣告。eslint: newline-per-chained-callno-whitespace-before-property

    // bad$('#items').find('.selected').highlight().end().find('.open').updateCount();// bad$('#items').find('.selected').highlight().end().find('.open').updateCount();// good$('#items').find('.selected').highlight().end().find('.open').updateCount();// badconstleds=stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led',true).attr('width',(radius+margin)*2).append('svg:g').attr('transform','translate('+(radius+margin)+','+(radius+margin)+')').call(tron.led);// goodconstleds=stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led',true).attr('width',(radius+margin)*2).append('svg:g').attr('transform','translate('+(radius+margin)+','+(radius+margin)+')').call(tron.led);// goodconstleds=stage.selectAll('.led').data(data);
  • 18.7 在區塊的結束及下個語法間加上空行。jscs: requirePaddingNewLinesAfterBlocks

    // badif(foo){returnbar;}returnbaz;// goodif(foo){returnbar;}returnbaz;// badconstobj={foo(){},bar(){},};returnobj;// goodconstobj={foo(){},bar(){},};returnobj;// badconstarr=[functionfoo(){},functionbar(){},];returnarr;// goodconstarr=[functionfoo(){},functionbar(){},];returnarr;
  • 18.8 別在區塊中置放空行。eslint: padded-blocks jscs: disallowPaddingNewlinesInBlocks

    // badfunctionbar(){console.log(foo);}// also badif(baz){console.log(qux);}else{console.log(foo);}// goodfunctionbar(){console.log(foo);}// goodif(baz){console.log(qux);}else{console.log(foo);}
  • 18.9 不要在括號內的兩側置放空格。eslint: space-in-parens jscs: disallowSpacesInsideParentheses

    // badfunctionbar(foo){returnfoo;}// goodfunctionbar(foo){returnfoo;}// badif(foo){console.log(foo);}// goodif(foo){console.log(foo);}
  • 18.10 不要在中括號內的兩側置放空格。eslint: array-bracket-spacing jscs: disallowSpacesInsideArrayBrackets

    // badconstfoo=[1,2,3];console.log(foo[0]);// goodconstfoo=[1,2,3];console.log(foo[0]);
  • 18.11 在大括號內的兩側置放空格。eslint: object-curly-spacing jscs: [disallowSpacesInsideObjectBrackets](http://jscs.info/rule/

    // badconstfoo={clark: 'kent'};// goodconstfoo={clark: 'kent'};
  • 18.12 避免一行的程式碼超過 100 字元(包含空白)。eslint: max-len jscs: maximumLineLength

    為什麼?這樣確保可讀性及維護性。

    // badconstfoo='Whatever national crop flips the window. The cartoon reverts within the screw. Whatever wizard constrains a helpful ally. The counterpart ascends!';// bad$.ajax({method: 'POST',url: 'https://airbnb.com/',data: {name: 'John'}}).done(()=>console.log('Congratulations!')).fail(()=>console.log('You have failed this city.'));// goodconstfoo='Whatever national crop flips the window. The cartoon reverts within the screw. '+'Whatever wizard constrains a helpful ally. The counterpart ascends!';// good$.ajax({method: 'POST',url: 'https://airbnb.com/',data: {name: 'John'},}).done(()=>console.log('Congratulations!')).fail(()=>console.log('You have failed this city.'));

⬆ 回到頂端

逗號

  • 19.1 不要將逗號放在前方。eslint: comma-style jscs: requireCommaBeforeLineBreak

    // badconststory=[once,upon,aTime];// goodconststory=[once,upon,aTime,];// badconsthero={firstName: 'Ada',lastName: 'Lovelace',birthYear: 1815,superPower: 'computers'};// goodconsthero={firstName: 'Ada',lastName: 'Lovelace',birthYear: 1815,superPower: 'computers',};
  • 19.2 增加結尾的逗號:別懷疑eslint: comma-dangle jscs: requireTrailingComma

    為什麼?這會讓 Git 的差異列表更乾淨。另外,Babel 轉譯器也會刪除結尾多餘的逗號,也就是說你完全不需要擔心在老舊的瀏覽器發生多餘逗號的問題

    // bad - 不含多餘逗號的 git 差異列表consthero={firstName: 'Florence',-lastName: 'Nightingale'+lastName: 'Nightingale',+inventorOf: ['coxcomb graph','modern nursing']};// good - 包含多餘逗號的 git 差異列表consthero={firstName: 'Florence',lastName: 'Nightingale',+inventorOf: ['coxcomb chart','modern nursing'],};// badconsthero={firstName: 'Dana',lastName: 'Scully'};constheroes=['Batman','Superman'];// goodconsthero={firstName: 'Dana',lastName: 'Scully',};constheroes=['Batman','Superman',];

⬆ 回到頂端

分號

  • 20.1 **對啦。**eslint: semi jscs: requireSemicolons

    // bad(function(){constname='Skywalker'returnname})()// good(()=>{constname='Skywalker';returnname;}());// good(防止當兩個檔案含有立即函式需要合併時,函式被當成參數處理);(()=>{constname='Skywalker';returnname;}());

    瞭解更多

⬆ 回到頂端

型別轉換

  • 21.1 在開頭的宣告進行強制型別轉換。

  • 21.2 字串:

    // => this.reviewScore = 9;// badconsttotalScore=this.reviewScore+'';// goodconsttotalScore=String(this.reviewScore);
  • 21.3 數字:使用 Number 做型別轉換,而 parseInt 則始終以基數解析字串。eslint: radix

    constinputValue='4';// badconstval=newNumber(inputValue);// badconstval=+inputValue;// badconstval=inputValue>>0;// badconstval=parseInt(inputValue);// goodconstval=Number(inputValue);// goodconstval=parseInt(inputValue,10);
  • 21.4 如果你因為某個原因在做些瘋狂的事情,但是 parseInt 是你的瓶頸,所以你對於性能方面的原因而必須使用位元右移,請留下評論並解釋為什麼使用,及你做了哪些事情。

    // good/** * 使用 parseInt 導致我的程式變慢,改成使用 * 位元右移強制將字串轉為數字加快了他的速度。 */constval=inputValue>>0;
  • 21.5 **注意:**使用位元轉換時請小心。數字為 64 位元數值,但是使用位元轉換時則會回傳一個 32 位元的整數(來源),這會導致大於 32 位元的數值產生異常 討論串,32 位元的整數最大值為 2,147,483,647:

    2147483647>>0//=> 21474836472147483648>>0//=> -21474836482147483649>>0//=> -2147483647
  • 21.6 布林:

    constage=0;// badconsthasAge=newBoolean(age);// goodconsthasAge=Boolean(age);// goodconsthasAge=!!age;

⬆ 回到頂端

命名規則

  • 22.1 避免使用單一字母的名稱,讓你的名稱有解釋的含義。

    // badfunctionq(){// ...stuff...}// goodfunctionquery(){// ..stuff..}
  • 22.2 使用駝峰式大小寫命名物件,函式及實例。eslint: camelcase jscs: requireCamelCaseOrUpperCaseIdentifiers

    // badconstOBJEcttsssss={};constthis_is_my_object={};functionc(){}// goodconstthisIsMyObject={};functionthisIsMyFunction(){}
  • 22.3 使用帕斯卡命名法來命名建構子或類別。eslint: new-cap jscs: requireCapitalizedConstructors

    // badfunctionuser(options){this.name=options.name;}constbad=newuser({name: 'nope',});// goodclassUser{constructor(options){this.name=options.name;}}constgood=newUser({name: 'yup',});
  • 22.4 命名私有屬性時請在前面加底線 _。eslint: no-underscore-dangle jscs: disallowDanglingUnderscores

    // badthis.__firstName__='Panda';this.firstName_='Panda';// goodthis._firstName='Panda';
  • 22.5 請別儲存 this 為參考。請使用箭頭函式或是 Function#bind。jscs: disallowNodeTypes

    // badfunctionfoo(){constself=this;returnfunction(){console.log(self);};}// badfunctionfoo(){constthat=this;returnfunction(){console.log(that);};}// goodfunctionfoo(){return()=>{console.log(this);};}
  • 22.6 如果你的檔案只有輸出一個類別,你的檔案名稱必須和你的類別名稱相同。

    // 檔案內容classCheckBox{// ...}exportdefaultCheckBox;// 在其他的檔案// badimportCheckBoxfrom'./checkBox';// badimportCheckBoxfrom'./check_box';// goodimportCheckBoxfrom'./CheckBox';
  • 22.7 當你導出為預設的函式時請使用駝峰式大小寫。檔案名稱必須與你的函式名稱一致。

    functionmakeStyleGuide(){}exportdefaultmakeStyleGuide;
  • 22.8 當你導出為單例 / 函式庫 / 空物件時請使用帕斯卡命名法。

    constAirbnbStyleGuide={es6: {}};exportdefaultAirbnbStyleGuide;

⬆ 回到頂端

存取器

  • 23.1 屬性的存取器函式不是必須的。

  • 23.2 別使用 JavaScript 的 getters 或 setters,因為它們會導致意想不到的副作用,而且不易於測試、維護以及進行推測。取而代之,如果你要建立一個存取器函式,請使用 getVal() 及 setVal('hello')。

    // baddragon.age();// gooddragon.getAge();// baddragon.age(25);// gooddragon.setAge(25);
  • 23.3 如果屬性是布林,請使用 isVal()hasVal()

    // badif(!dragon.age()){returnfalse;}// goodif(!dragon.hasAge()){returnfalse;}
  • 23.4 可以建立 get() 及 set() 函式,但請保持一致。

    classJedi{constructor(options={}){constlightsaber=options.lightsaber||'blue';this.set('lightsaber',lightsaber);}set(key,val){this[key]=val;}get(key){returnthis[key];}}

⬆ 回到頂端

事件

  • 24.1 當需要對事件傳入資料時(不論是 DOM 事件或是其他私有事件),請傳入物件替代單一的資料。這樣可以使之後的開發人員直接加入其他的資料到事件裡,而不需更新該事件的處理器。例如,比較不好的做法:

    // bad$(this).trigger('listingUpdated',listing.id); ... $(this).on('listingUpdated',(e,listingId)=>{// do something with listingId});

    更好的做法:

    // good$(this).trigger('listingUpdated',{listingId: listing.id}); ... $(this).on('listingUpdated',(e,data)=>{// do something with data.listingId});

⬆ 回到頂端

jQuery

  • 25.1 jQuery 的物件請使用 $ 當前綴。jscs: requireDollarBeforejQueryAssignment

    // badconstsidebar=$('.sidebar');// goodconst$sidebar=$('.sidebar');// goodconst$sidebarBtn=$('.sidebar-btn');
  • 25.2 快取 jQuery 的查詢。

    // badfunctionsetSidebar(){$('.sidebar').hide();// ...stuff...$('.sidebar').css({'background-color': 'pink'});}// goodfunctionsetSidebar(){const$sidebar=$('.sidebar');$sidebar.hide();// ...stuff...$sidebar.css({'background-color': 'pink'});}
  • 25.3 DOM 的查詢請使用層遞的 $('.sidebar ul') 或 父元素 > 子元素 $('.sidebar > ul')jsPerf

  • 25.4 對作用域內的 jQuery 物件使用 find 做查詢。

    // bad$('ul','.sidebar').hide();// bad$('.sidebar').find('ul').hide();// good$('.sidebar ul').hide();// good$('.sidebar > ul').hide();// good$sidebar.find('ul').hide();

⬆ 回到頂端

ECMAScript 5 相容性

⬆ 回到頂端

ECMAScript 6 風格

  • 27.1 以下是連結到各個 ES6 特性的列表。
  1. 箭頭函式
  2. 類別
  3. 物件簡寫
  4. 簡潔物件
  5. 可計算的物件屬性
  6. 模板字串
  7. 解構子
  8. 預設參數
  9. 剩餘參數(Rest)
  10. 陣列擴展
  11. Let 及 Const
  12. 迭代器及產生器
  13. 模組

⬆ 回到頂端

測試

  • 28.1如題。

    functionfoo(){returntrue;}
  • 28.2無題,不過很重要

  • 不論你用哪個測試框架,你都應該撰寫測試!

  • 力求撰寫許多的純函式,並盡量減少異常發生的機會。

  • 要對 stubs 及 mocks 保持嚴謹——他們可以讓你的測試變得更加脆弱。

  • 我們在 Airbnb 主要使用 mocha。對小型或單獨的模組偶爾使用 tape

  • 努力達到 100% 的測試涵蓋率是個很好的目標,即使實現這件事是不切實際的。

  • 每當你修復完一個 bug,就撰寫回歸測試。一個修復完的 bug 若沒有回歸測試,通常在未來肯定會再次發生損壞。

⬆ 回到頂端

效能

⬆ 回到頂端

資源

學習 ES6

請讀這個

工具

其他的風格指南

其他風格

瞭解更多

書籍

部落格

Podcasts

⬆ 回到頂端

誰在使用

這是正在使用這份風格指南的組織列表。送一個 pull request 後我們會將你增加到列表上。

⬆ 回到頂端

翻譯

This style guide is also available in other languages:

JavaScript 風格指南

與我們討論 JavaScript

貢獻者

License

(The MIT License)

Copyright (c) 2014-2016 Airbnb

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

⬆ 回到頂端

Amendments

We encourage you to fork this guide and change the rules to fit your team's style guide. Below, you may list some amendments to the style guide. This allows you to periodically update your style guide without having to deal with merge conflicts.

};

About

Airbnb JavaScript Style Guide 正體中文版

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript100.0%