Airbnb Javascript Style 閱讀註解

ylone666發表於2018-01-30

提供一種合理的javascript的規範,對原文主要內容進行翻譯,同時對部分內容進行註釋

注意:本文假定你正在使用 Babel,並且要求你使用 babel-preset-airbnb或者其替代品。同時,假定你已經通過airbnb-browser-shims或者其替代品安裝 shims/polyfills 在你的app內。

如果您想閱讀原文?

如果您想在github上檢視?

如果您想了解並使用 babel with airbnb?

Types(資料型別)

  • 簡單的基本資料型別,直接使用其值

    • string

    • number

    • boolean

    • null

    • undefined

    • symbol

    const foo = 1;
    let bar = foo;
    bar = 9;
    console.log(foo, bar); // => 1, 9
    複製程式碼
  • 複雜的基本資料型別,直接使用其值的引用

    • object

    • array

    • function

    const foo = [1, 2];
    const bar = foo;
    bar[0] = 9;
    console.log(foo[0], bar[0]); // => 9, 9
    複製程式碼

➰symbol

  • Symbol

symbol自ES6引入,目的是提供一種機制,保證每個屬性名都是唯一的,從根本上防止屬性名的衝突。在這之前,物件屬性名都是字串。其實看到這裡,stringsymbol型別有點classid的意思

Symbol()的宣告,因為 Symbol()返回值是一個類似於字串的基本型別,不是一個物件,所以不能使用 new 命令

let ylone = Symbol();
typeof(ylone);
?	
"symbol"
  
//為宣告加上描述
let ylone1 = Symbol('hello');
ylone1;
?
Symbol(hello);
複製程式碼

無論是不加描述,還是所加的描述相同, Symbol() 函式的返回值都不相同

Symbol.for('key') 也會返回一個Symbol,但是Symbol.for()採用登記機制(會被登記在全域性環境中供搜尋),如果之前key已經存在,則直接返回該值,否則新建一個值。比如,如果你呼叫 Symbol.for("cat")30 次,每次都會返回同一個Symbol值,但是呼叫Symbol("cat")30 次,會返回 30 個不同的Symbol值。

Symbol本身不能與其他值進行運算,但是可以轉換成字串和布林型別

物件中使用Symbol()。通過對比之前通過 a['string'] 的方式,相當於多了一步轉換,來保證屬性命名的安全。

let mySymbol = Symbol();
// 第一種寫法
let a = {};
a[mySymbol] = 'Hello!';
  
// 第二種寫法
let a = {
	[mySymbol]: 'Hello!'
};
	
// 第三種寫法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
	
a[mySymbol]
?
'hello!'	
複製程式碼

注意,由於 . 運算子後面總是字串,所以Symbol() 不支援點式宣告物件屬性。在物件內部使用 [symbol] 這樣的寫法也是這個道理

References(引用)

  • 宣告建立一個值時用 const 而不用 var,這樣可以保證你宣告的值不會被重定義

    // bad
    var a = 1;
    var b = 2;
    
    // good
    const a = 1;
    const b = 2;
    複製程式碼
  • 如果需要改變宣告所建立的值,用let而不是var,因為 let 是塊級作用域元素, var 是函式作用域元素

    // bad
    var count = 1;
    if (true) {
      count += 1;
    }
    
    // good, use the let.
    let count = 1;
    if (true) {
      count += 1;
    }
    複製程式碼
  • 注意,letconst 都是塊級作用域函式,他們都只存在於他們被定義的塊中

    // const and let only exist in the blocks they are defined in.
    {
      let a = 1;
      const b = 1;
    }
    console.log(a); // ReferenceError
    console.log(b); // ReferenceError
    複製程式碼

➰const,let,block-scoped,function-scoped

  • const

塊級作用域的常量,此宣告建立一個常量,其作用域可以是全域性或本地宣告的。宣告時需要指定其值作為一個常數的初始化器。一般情況下, const 宣告的值不能改變,但是物件元素可以改變其屬性,陣列元素可以向其中新增值,但是不能重新賦值

const a = 100;
a = 10; ? Uncaught TypeError: Assignment to constant variable
	
const a = [];
a.push('a'); ✔
a = ['a']; ? Uncaught TypeError: Assignment to constant variable
	
const obj = {'name':'ylone'};
obj['name'] = 'yh';	✔
obj = {'name':'yh'}; ? Uncaught TypeError: Assignment to constant variable
複製程式碼

注意,chrome30嚴格模式下不能使用,const(Uncaught SyntaxError: Use of const in strict mode. )

  • let

let允許你宣告一個作用域被限制在塊級中的變數、語句或者表示式。let宣告的變數只在其宣告的塊或子塊中可用,這一點,與var相似。二者之間最主要的區別在於var宣告的變數的作用域是整個封閉函式。

var q = 1;
var w = 2;
if(true){
var q = 11;
let w = 22;
console.log(q,w); ?(11,22)
}
console.log(q,w); ?(11,2)
複製程式碼
  • block-scoped

在其他類C語言中,由 {} 封閉的程式碼塊即為 block-scoped,{..block-scoped..}

if(true){
var a = 100;
}
a; ? 100
	
if(true){
let b = 100;
}
b; ? Uncaught ReferenceError: b is not defined
複製程式碼

如果是類C語言中,a 會在if語句執行完畢後銷燬,但是在javascript中,if中的變數宣告會將變臉那個新增到當前的執行環境中,這裡可以看出 var與let的區別var 宣告的變數會自動被新增到最接近的執行環境中,let宣告的變數則只會存在與塊級作用域中

  • function-scoped

函式作用域,每個函式被宣告時的上下文執行環境,fucnction(){..function-scoped..}

Objects(物件)

  • 直接使用 {} 來建立物件,因為這樣更加簡潔,效能上和 new Object() 也沒差

    // bad
    const item = new Object();
    
    // good
    const item = {};
    複製程式碼

建立擁有動態屬性名的物件時,用計算機屬性名來表示,這樣可以在建立物件時,將所有的屬性寫在同一個地方

function getKey(k) {
  return `a key named ${k}`;
}

// bad
const obj = {
  id: 5,
  name: 'San Francisco',
};
obj[getKey('enabled')] = true;

// good
const obj = {
  id: 5,
  name: 'San Francisco',
  [getKey('enabled')]: true,
};
複製程式碼
  • 物件屬性中有函式方法時,使用更簡潔的物件字面值方法

    // bad
    const atom = {
      value: 1,
      addValue: function (value) {
        return atom.value + value;
      },
    };
    
    // good
    const atom = {
      value: 1,
      addValue(value) {
        return atom.value + value;
      },
    };
    複製程式碼
  • 物件屬性和屬性值一致時,使用更簡潔的物件字面值屬性

    const lukeSkywalker = 'Luke Skywalker';
    
    // bad
    const obj = {
      lukeSkywalker: lukeSkywalker,
    };
    
    // good
    const obj = {
      lukeSkywalker,
    };
    複製程式碼
  • 宣告物件時,根據是否使用速記,簡單地對物件的屬性分下類

    const anakinSkywalker = 'Anakin Skywalker';
    const lukeSkywalker = 'Luke Skywalker';
    
    // bad
    const obj = {
      episodeOne: 1,
      twoJediWalkIntoACantina: 2,
      lukeSkywalker,
      episodeThree: 3,
      mayTheFourth: 4,
      anakinSkywalker,
    };
    
    // good
    const obj = {
      lukeSkywalker,
      anakinSkywalker,
      episodeOne: 1,
      twoJediWalkIntoACantina: 2,
      episodeThree: 3,
      mayTheFourth: 4,
    };
    複製程式碼
  • 僅給有特殊符號的識別符號提供引號,實際上物件的屬性預設為字串型別,除非用[]標記為符號型別。這樣做的好處在於,增強程式碼高亮,方便閱讀,並且對js引擎更加友好

    // bad
    const bad = {
      'foo': 3,
      'bar': 4,
      'data-blah': 5,
    };
    
    // good
    const good = {
      foo: 3,
      bar: 4,
      'data-blah': 5,
    };
    複製程式碼
  • 不要直接呼叫Object.prototype下的方法,比如 hasOwnProperty,isPrototypeOf,propertyIsEnumerable等,因為這些方法可能被覆蓋{ hasOwnProperty: false } ,或者物件為空報錯

    // bad
    console.log(object.hasOwnProperty(key));
    
    // good
    console.log(Object.prototype.hasOwnProperty.call(object, key));
    
    // best
    const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
    /* or */
    import has from 'has'; // https://www.npmjs.com/package/has
    // ...
    console.log(has.call(object, key));
    複製程式碼
  • 用物件擴散運算子和物件剩餘運算子,而不是 Object.assign 來進行淺拷貝操作

    // very bad
    const original = { a: 1, b: 2 };
    const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
    delete copy.a; // so does this
    
    // bad
    const original = { a: 1, b: 2 };
    const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
    
    // good
    const original = { a: 1, b: 2 };
    const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
    
     // noA => { b: 2, c: 3 }
    複製程式碼

➰call,assign(),...

  • call()

Function.prototype.call(),呼叫一個函式,其具有指定的 this 值和引數列表。注意,該方法和 apply() 方法類似,區別在於 apply() 傳參為一個包含多個引數的陣列。可以讓call()中的物件呼叫當前物件所擁有的function。

使用 call() 呼叫父建構函式,在一個子建構函式中,你可以通過呼叫父建構函式的 call 方法來實現繼承,類似於Java中的寫法

//父建構函式,寫一些公用的方法和屬性
function a(v1,v2){
	this.name = v1;
	this.cool = v2;
} 
//子建構函式,可以繼承父建構函式的方法和屬性,同時可以有私有的方法和屬性
function b(v1,v2,v3){
	a.call(this,v1,v2);
	this.sex = v3;
}
var v1 = new a('ylone',true);
var v2 = new b('ylone',true,'male');
v1; ? {name: "ylone", cool: true}
v2; ? {name: "ylone", cool: true, sex: "male"}
複製程式碼

使用 call() 呼叫匿名函式,將引數作為指定的 this值,傳進匿名函式。同時也可以傳遞普通引數。

var i = 1;
(function(i){console.log(this,i)}).call(Math.random(),i);
? 0.9604319664333041 1
複製程式碼

使用 call() 呼叫函式並且指定執行環境的this

function a(){
	console.log(this.name + ' is ' + this.cool);
};
var i = {name: 'ylone', cool: 'cool'};
a.call(i); ? ylone is cool
複製程式碼
  • Object.assign()

$.extend()類似,用於物件的合併,將源物件內所有可列舉的屬性拷貝到目標物件,注意如果源資料不是物件,則先會轉換成物件;如果是null或者undefined等不能轉換成物件的型別,則根據其位置進行跳過或者報錯。

Object.assign(null); ? Uncaught TypeError: Cannot convert undefined or null to object
  
Object.assign(1,null); ? Number {1}
複製程式碼

Object.assign()僅支援淺拷貝,也就是說,如果源物件某個屬性的值是物件,那麼目標物件拷貝得到的是這個物件的引用

var v1 = {a:{b:'b'}};
var v2 = Object.assign({},v1);
v1.a.b = 'c';
v2.a.b; ? 'c'
複製程式碼

Object.assign() 處理陣列,會先把陣列轉換成物件,將其視為屬性名為 0、1、2 的物件,因此源陣列的 0 號屬性4覆蓋了目標陣列的 0 號屬性1。

Object.assign([1, 2, 3], [4, 5]);
?
Object.assign({0:1,1:2,2:3},{0:4,1:5});
?
{0:4,1:5,2:3}
?
[4,5,3]
複製程式碼
  • ...

物件擴散運算子和物件剩餘運算子都用 ... 表示,可以理解為“脫衣服”方法

陣列轉換,將陣列轉換成逗號分隔的引數序列,注意,其返回值並不是某個基本型別,所以該方法多用於函式引數設定,代替 apply() 方法。對於很多引數不能接受陣列的方法提供了便利。

...[1,2,3] ? Uncaught SyntaxError: Unexpected number
  
[...[1,2,3]] ? [1, 2, 3]
  
[1,...[2,3],4] ? [1, 2, 3, 4]
  
//Math.max()不支援陣列傳參,之前通過apply()進行轉換
Math.max.apply(null,[1,2,3]) ? 3
//現在可以利用 ... 直接進行轉換
Math.max(...[1,2,3]) ? 3
複製程式碼

Arrays(陣列)

  • 使用 [] 來建立陣列

    // bad
    const items = new Array();
    
    // good
    const items = [];
    複製程式碼
  • 使用 push() 而不是直接給陣列項賦值

    const someStack = [];
    
    // bad
    someStack[someStack.length] = 'abracadabra';
    
    // good
    someStack.push('abracadabra');
    複製程式碼
  • 使用 ... 拷貝陣列

    // bad
    const len = items.length;
    const itemsCopy = [];
    let i;
    for (i = 0; i < len; i += 1) {
      itemsCopy[i] = items[i];
    }
    
    // good
    const itemsCopy = [...items];
    複製程式碼
  • 使用 ... 將陣列物件轉換為陣列

    const foo = document.querySelectorAll('.foo');
    
    // good
    const nodes = Array.from(foo);
    
    // best
    const nodes = [...foo];
    複製程式碼
  • array.from() 而不是 ... 遍歷迭代器,這樣避免產生了中間變數

    // bad
    const baz = [...foo].map(bar);
    
    // good
    const baz = Array.from(foo, bar);
    複製程式碼
  • 陣列方法的回撥中使用return語句,如果函式體由單語句組成,返回值沒有副作用,return也可以忽略

    // good
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });
    
    // good
    [1, 2, 3].map(x => x + 1);
    
    // bad - no returned value means `memo` becomes undefined after the first iteration
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
      const flatten = memo.concat(item);
      memo[index] = flatten;
    });
    
    // good
    [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => {
      const flatten = memo.concat(item);
      memo[index] = flatten;
      return flatten;
    });
    
    // bad
    inbox.filter((msg) => {
      const { subject, author } = msg;
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
      } else {
        return false;
      }
    });
    
    // good
    inbox.filter((msg) => {
      const { subject, author } = msg;
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
      }
    
      return false;
    });
    複製程式碼
  • 如果陣列有多行,在陣列項開始和結束時使用換行符

    // bad
    const arr = [
      [0, 1], [2, 3], [4, 5],
    ];
    
    const objectInArray = [{
      id: 1,
    }, {
      id: 2,
    }];
    
    const numberInArray = [
      1, 2,
    ];
    
    // good
    const arr = [[0, 1], [2, 3], [4, 5]];
    
    const objectInArray = [
      {
        id: 1,
      },
      {
        id: 2,
      },
    ];
    
    const numberInArray = [
      1,
      2,
    ];
    複製程式碼

➰Array.from()

  • Array.from()

Array.from() 方法從一個類似陣列(一個物件必須有length屬性)或可迭代物件中建立一個新的陣列例項,比如 array,map,set,string

 //陣列
 const arr = ['1','2','3'];
 Array.from(arr); ? ["1", "2", "3"]

 //字串
 const str = 'ylone';
 Array.from(str); ? ["y", "l", "o", "n", "e"]

 //map物件
 const m1 = new Map();
 m1.set('v1',1);
 m2.set('v2',2);
 m2; ? {"v1" => 1, "v2" => 2} 
 Array.from(m2); ? [['v1',1],['v2',2]]

 //json物件
 const j = {'v1':1,'v2':2};
 j.length; ? undefined
 Array.from(j); ? []  
複製程式碼
  • Array.from(arrayLike, mapFn, thisArg)

    • arrayLike表示想要轉換成陣列的偽陣列物件或可迭代物件

    • mapFn(可選引數)表示新陣列中的每個元素會執行該回撥函式

    • thisArg(可選引數)表示執行回撥函式mapFnthis物件

   Array.from([1,2,3], function(n){return n+1})
   ?
   [2, 3, 4]
複製程式碼

Destructuring(解構)

  • 訪問和使用物件的多個屬性時,使用物件解構。這樣可以避免為這些屬性建立臨時引用,保持程式碼的整潔

    // bad
    function getFullName(user) {
      const firstName = user.firstName;
      const lastName = user.lastName;
    
      return `${firstName} ${lastName}`;
    }
    
    // good
    function getFullName(user) {
      const { firstName, lastName } = user;
      return `${firstName} ${lastName}`;
    }
    
    // best
    function getFullName({ firstName, lastName }) {
      return `${firstName} ${lastName}`;
    }
    複製程式碼
  • 使用陣列解構

    const arr = [1, 2, 3, 4];
    
    // bad
    const first = arr[0];
    const second = arr[1];
    
    // good
    const [first, second] = arr;
    複製程式碼
  • 使用物件解構而不是陣列解構來實現多個返回值。這樣,您可以新增新的屬性或者更改屬性順序

    // bad
    function processInput(input) {
      return [left, right, top, bottom];
    }
    
    // the caller needs to think about the order of return data
    const [left, __, top] = processInput(input);
    
    // good
    function processInput(input) {
      return { left, right, top, bottom };
    }
    
    // the caller selects only the data they need
    const { left, top } = processInput(input);
    複製程式碼

➰Destructuring

  • Destructuring:解構。解構的作用是可以快速取得陣列或物件當中的元素或屬性,而無需使用arr[x]或者obj[key]等傳統方式進行賦值

      //陣列解構
      const arr = [1,[2,3],4];
      const [a,[b,c],d] = arr;
      a,b,c,d; ? 1,2,3,4
      //函式傳參
      var arr = [1, 2, 3];
      function fn1([a, b, c]) {
        return a+b+c;
      }
      fn1(arr); ? 6
    複製程式碼

Strings(字串)

  • 使用單引號 ''

    // bad
    const name = "Capt. Janeway";
    
    // bad - template literals should contain interpolation or newlines
    const name = `Capt. Janeway`;
    
    // good
    const name = 'Capt. Janeway';
    複製程式碼
  • 如果字串很長,不要通過字串連線符進行換行,保持原來的字串形式就好。因為破壞字串是一件很不好的事情,同時也減少了程式碼的可讀性

    // bad
    const errorMessage = '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.';
    
    // bad
    const errorMessage = '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.';
    
    // good
    const errorMessage = '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.';
    複製程式碼
  • 當字串中有變數時,使用模板字串而不是連字元。這樣程式碼更加簡潔可讀

    // bad
    function sayHi(name) {
      return 'How are you, ' + name + '?';
    }
    
    // bad
    function sayHi(name) {
      return ['How are you, ', name, '?'].join();
    }
    
    // bad
    function sayHi(name) {
      return `How are you, ${ name }?`;
    }
    
    // good
    function sayHi(name) {
      return `How are you, ${name}?`;
    }
    複製程式碼
  • 不要使用eval()方法,因為它有潛在的危險,在不受信任的程式碼上使用可以開啟一個程式多達幾種不同的注入攻擊

  • 在字串中不要隨意使用 \,因為它影響可讀性,同時可能與轉義符產生影響

    // bad
    const foo = '\'this\' \i\s \"quoted\"';
    
    // good
    const foo = '\'this\' is "quoted"';
    const foo = `my name is '${name}'`;
    複製程式碼

Functions(函式)

  • 使用命名函式表示式而不是函式宣告。因為如果一個函式宣告被掛起之後,很容易在它被定義之前就去引用,這就很影響程式碼的可讀性和可維護性。同時,如果一個函式的功能比較複雜,需要用函式名來對其進行一定的描述

    // bad
    function foo() {
      // ...
    }
    
    // bad
    const foo = function () {
      // ...
    };
    
    // good
    // lexical name distinguished from the variable-referenced invocation(s)
    const short = function longUniqueMoreDescriptiveLexicalFoo() {
      // ...
    };
    複製程式碼
  • () 建立的函式需要立即呼叫,自呼叫函式相當於一個獨立模組。事實上,IIFE很少在專案中使用

    // immediately-invoked function expression (IIFE)
    (function () {
      console.log('Welcome to the Internet. Please follow me.');
    }());
    複製程式碼
  • 不要在非功能模組(if,while等)裡面宣告一個函式。將函式分配給一個變數來替代它。因為雖然瀏覽器支援這種做法,但是他們各自的解析方式並不一樣

  • ECMA-262 定義 ‘塊’ 表示一個語句列表,函式宣告並不是一個語句,跟上一點類似

    // bad
    if (currentUser) {
      function test() {
        console.log('Nope.');
      }
    }
    
    // good
    let test;
    if (currentUser) {
      test = () => {
        console.log('Yup.');
      };
    }
    複製程式碼
  • 永遠不要給引數命名為 arguments,這將導致每個函式作用域的 arguments物件被優先替換

    // bad
    function foo(name, options, arguments) {
      // ...
    }
    
    // good
    function foo(name, options, args) {
      // ...
    }
    複製程式碼
  • 永遠不要使用 arguments,而使用 ...,因為 arguments 只是類似陣列

    // bad
    function concatenateAll() {
      const args = Array.prototype.slice.call(arguments);
      return args.join('');
    }
    
    // good
    function concatenateAll(...args) {
      return args.join('');
    }
    複製程式碼
  • 使用函式預設引數語法而不是改變函式的引數

    // really bad
    function handleThings(opts) {
      // No! We shouldn’t mutate function arguments.
      // Double bad: if opts is falsy it'll be set to an object which may
      // be what you want but it can introduce subtle bugs.
      opts = opts || {};
      // ...
    }
    
    // still bad
    function handleThings(opts) {
      if (opts === void 0) {
        opts = {};
      }
      // ...
    }
    
    // good
    function handleThings(opts = {}) {
      // ...
    }
    複製程式碼
  • 避免函式預設引數使用不當,使用時要考慮場景

    var b = 1;
    // bad
    function count(a = b++) {
      console.log(a);
    }
    count();  // 1
    count();  // 2
    count(3); // 3
    count();  // 3
    複製程式碼
  • 總是將函式預設引數放在傳參的最後

    // bad
    function handleThings(opts = {}, name) {
      // ...
    }
    
    // good
    function handleThings(name, opts = {}) {
      // ...
    }
    複製程式碼
  • 永遠不要使用 Function 建構函式來建立一個新的函式,因為它和 eval() 沆瀣一氣

    // bad
    var add = new Function('a', 'b', 'return a + b');
    
    // still bad
    var subtract = Function('a', 'b', 'return a - b');
    複製程式碼
  • 函式簽名的間距,新增或刪除名稱時不需要新增或刪除空格,保持一致性

    // bad
    const f = function(){};
    const g = function (){};
    const h = function() {};
    
    // good
    const x = function () {};
    const y = function a() {};
    複製程式碼
  • 不要改變引數,因為操作最為引數傳入的物件可能會改變原物件從而對其他呼叫產生影響

    // bad
    function f1(obj) {
      obj.key = 1;
    }
    
    // good
    function f2(obj) {
      const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
    }
    複製程式碼
  • 不要重新分配引數,特別是在訪問arguments物件時

    // 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) {
      // ...
    }
    複製程式碼
  • 優先使用 ... 來呼叫可變引數函式,因為 ... 很乾淨,不需要提供上下文環境,並且你不能輕易地使用 apply()new方法

    // bad
    const x = [1, 2, 3, 4, 5];
    console.log.apply(console, x);
    
    // good
    const x = [1, 2, 3, 4, 5];
    console.log(...x);
    
    // bad
    new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5]));
    
    // good
    new Date(...[2016, 8, 5]);
    複製程式碼
  • 使用函式如果有多行簽名或者呼叫,應該每個 item 單獨放一行,並在最後一項放置一個尾隨逗號

    // bad
    function foo(bar,
                 baz,
                 quux) {
      // ...
    }
    
    // good
    function foo(
      bar,
      baz,
      quux,
    ) {
      // ...
    }
    // bad
    console.log(foo,
      bar,
      baz);
       
    // good
    console.log(
      foo,
      bar,
      baz,
    );
    複製程式碼

➰Default Function Parameter

  • 函式預設引數,允許在沒有值或undefined被傳入時使用預設形參

  • 函式形式:function(name){param1 = defaultValue1,...,paramN = defaultValueN}

  • JavaScript中函式的引數預設是 undefined

    const a = function test(v1,v2=1){
        return v1*v2;
    }
    a(5,5); ? 25
    a(5); ? 5
    a(void 0,5); ? NaN  
    複製程式碼
  • 可以看出,當設定了函式預設引數後,如果傳參為 undefined,則會用預設引數替換,否則為原傳參值

  • 有預設值的解構函式,通過解構賦值為引數賦值

    const b = function test([a,b]=[1,2],{c:c}={c:3}){
      return a+b+c;
    }
    b(); ? 6
    b([2,3],4); ? 9
    b(void 0,4); ? 9
    b([void 0,3],4); ? NaN
    複製程式碼

Arrow Functions(箭頭函式)

  • 當需要使用一個匿名函式時(比如在傳遞內聯回撥時),使用箭頭函式表示

    // bad
    [1, 2, 3].map(function (x) {
      const y = x + 1;
      return x * y;
    });
    
    // good
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });
    複製程式碼

如果一個函式的返回值是一個無副作用的單語句,則省略大括號並且隱式返回,否則保留大括號並且使用return宣告

// bad
[1, 2, 3].map(number => {
  const nextNumber = number + 1;
  `A string containing the ${nextNumber}.`;
});

// good
[1, 2, 3].map(number => `A string containing the ${number}.`);

// good
[1, 2, 3].map((number) => {
  const nextNumber = number + 1;
  return `A string containing the ${nextNumber}.`;
});

// good
[1, 2, 3].map((number, index) => ({
  [index]: number,
}));

// No implicit return with side effects
function foo(callback) {
  const val = callback();
  if (val === true) {
    // Do something if callback returns true
  }
}

let bool = false;

// bad
foo(() => bool = true);

// good
foo(() => {
  bool = true;
});
複製程式碼
  • 如果函式表示式有多行,用括號將內容包裹起來,以便更好地閱讀,因為它清除標記了起始和結束位置

    // bad
    ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call(
        httpMagicObjectWithAVeryLongName,
        httpMethod,
      )
    );
    
    // good
    ['get', 'post', 'put'].map(httpMethod => (
      Object.prototype.hasOwnProperty.call(
        httpMagicObjectWithAVeryLongName,
        httpMethod,
      )
    ));
    複製程式碼
  • 如果函式內始終只有一個引數,則省略括號,否則的話,用括號保護引數

    // 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 don’t want it to take up space on the .map line!`
    ));
    
    // bad
    [1, 2, 3].map(x => {
      const y = x + 1;
      return x * y;
    });
    
    // good
    [1, 2, 3].map((x) => {
      const y = x + 1;
      return x * y;
    });
    複製程式碼
  • 避免將箭頭函式語法(=>)與比較運算子(<=,>=)混淆

    // bad
    const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize;
    
    // bad
    const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize;
    
    // good
    const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize);
    
    // good
    const itemHeight = (item) => {
      const { height, largeSize, smallSize } = item;
      return height > 256 ? largeSize : smallSize;
    };
    複製程式碼

➰arrow Function

  • 箭頭函式表示式的語法比函式表示式更短,並且不繫結自己的this,arguments,super或 new.target。這些函式表示式最適合用於非方法函式,並且它們不能用作建構函式

  • const 函式名 = (引數...) => {函式宣告}||表示式

  • 執行體為函式宣告時需要加上 {},引數的規則參看上文內容

    //支援解構函式
    const f = ([a,b]=[1,2],{c:c}={c:3})=>a+b+c;
    f(); ? 6;
    複製程式碼

Classes & Constructors(類與建構函式)

  • 避免直接使用 prototype , 多用 class。因為 class語法更加簡潔和且閱讀性更棒

    // bad
    function Queue(contents = []) {
      this.queue = [...contents];
    }
    Queue.prototype.pop = function () {
      const value = this.queue[0];
      this.queue.splice(0, 1);
      return value;
    };
    
    // good
    class Queue {
      constructor(contents = []) {
        this.queue = [...contents];
      }
      pop() {
        const value = this.queue[0];
        this.queue.splice(0, 1);
        return value;
      }
    }
    複製程式碼
  • 使用 extends 實現繼承,因為這是繼承原型的內建功能

    // bad
    const inherits = require('inherits');
    function PeekableQueue(contents) {
      Queue.apply(this, contents);
    }
    inherits(PeekableQueue, Queue);
    PeekableQueue.prototype.peek = function () {
      return this.queue[0];
    };
    
    // good
    class PeekableQueue extends Queue {
      peek() {
        return this.queue[0];
      }
    }
    複製程式碼
  • 方法可以通過返回 this 來優化方法鏈

    // bad
    Jedi.prototype.jump = function () {
      this.jumping = true;
      return true;
    };
    
    Jedi.prototype.setHeight = function (height) {
      this.height = height;
    };
    
    const luke = new Jedi();
    luke.jump(); // => true
    luke.setHeight(20); // => undefined
    
    // good
    class Jedi {
      jump() {
        this.jumping = true;
        return this;
      }
    
      setHeight(height) {
        this.height = height;
        return this;
      }
    }
    
    const luke = new Jedi();
    
    luke.jump()
    luke.setHeight(20);
    複製程式碼
  • 寫一個通用的 toString() 方法也沒問題,但是需要保證其能執行且沒有其他影響

    class Jedi {
      constructor(options = {}) {
        this.name = options.name || 'no name';
      }
    
      getName() {
        return this.name;
      }
    
      toString() {
        return `Jedi - ${this.getName()}`;
      }
    }
    複製程式碼
  • 如果沒有指定類,那麼類需要有一個預設的構造方法。一個空的建構函式或者只是委託給父類是沒有必要的

    // bad
    class Jedi {
      constructor() {}
    
      getName() {
        return this.name;
      }
    }
    
    // bad
    class Rey extends Jedi {
      constructor(...args) {
        super(...args);
      }
    }
    
    // good
    class Rey extends Jedi {
      constructor(...args) {
        super(...args);
        this.name = 'Rey';
      }
    }
    複製程式碼
  • 避免出現兩個一樣的類成員,因為前一個成員會被覆蓋從而導致錯誤

    // bad
    class Foo {
      bar() { return 1; }
      bar() { return 2; }
    }
    
    // good
    class Foo {
      bar() { return 1; }
    }
    
    // good
    class Foo {
      bar() { return 2; }
    }
    複製程式碼

Modules(模組)

  • 始終使用模組(import/export)來代替非標準的模組系統。你可以選擇你喜歡的模組系統,因為模組代表未來

    // bad
    const AirbnbStyleGuide = require('./AirbnbStyleGuide');
    module.exports = AirbnbStyleGuide.es6;
    
    // ok
    import AirbnbStyleGuide from './AirbnbStyleGuide';
    export default AirbnbStyleGuide.es6;
    
    // best
    import { es6 } from './AirbnbStyleGuide';
    export default es6;
    複製程式碼
  • 不要使用萬用字元進行匯出,從而保證你輸出一個獨立的匯出

    // bad
    import * as AirbnbStyleGuide from './AirbnbStyleGuide';
    
    // good
    import AirbnbStyleGuide from './AirbnbStyleGuide';
    複製程式碼
  • 不要把匯入和匯出寫在一起,雖然一行簡明扼要,但是我們更需要明確的匯入方式和匯出方式,保持其一致性

    // bad
    // filename es6.js
    export { es6 as default } from './AirbnbStyleGuide';
    
    // good
    // filename es6.js
    import { es6 } from './AirbnbStyleGuide';
    export default es6;
    複製程式碼
  • 一個路徑一次支援一個匯入,因為一個路徑一次支援有多個匯入,會使程式碼變得難以維護

    // bad
    import foo from 'foo';
    // … some other imports … //
    import { named1, named2 } from 'foo';
    
    // good
    import foo, { named1, named2 } from 'foo';
    
    // good
    import foo, {
      named1,
      named2,
    } from 'foo';
    複製程式碼
  • 拒絕匯出可變繫結,這種方式通常應該避免,但是不排除有某些特殊情況需要這麼做,但是應該記住,通常只匯出常量引用

    // bad
    let foo = 3;
    export { foo };
     
    // good
    const foo = 3;
    export { foo };
    複製程式碼
  • 在具有單一匯出的模組中,建議使用預設匯出而不是命名匯出,這樣對於程式碼的可讀性和可維護性更加友好

    // bad
    export function foo() {}
    
    // good
    export default function foo() {}
    複製程式碼
  • 把所有的匯入語句放在一起

    // bad
    import foo from 'foo';
    foo.init();
    
    import bar from 'bar';
    
    // good
    import foo from 'foo';
    import bar from 'bar';
    
    foo.init();
    複製程式碼
  • 多行匯入應該項多行陣列和物件一樣縮排,這樣保持 {} 內容的一致性

    // bad
    import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';
    
    // good
    import {
      longNameA,
      longNameB,
      longNameC,
      longNameD,
      longNameE,
    } from 'path';
    複製程式碼
  • 匯出語句中不允許出現 webpack 載入器語法。因為匯入中使用載入器語法會將程式碼耦合到模組打包器中,,更建議使用 webpack.config.js

    // bad
    import fooSass from 'css!sass!foo.scss';
    import barCss from 'style!css!bar.css';
    
    // good
    import fooSass from 'foo.scss';
    import barCss from 'bar.css';
    複製程式碼

Iterators and Generators(迭代器和發生器)

  • 不要使用迭代器,更推薦使用javascript的高階方法而不是 for-infor-of 這些。使用 map()every()filter()find()findIndex()reduce()some() 等遍歷陣列,以及Object.keys()Object.values()Object.entries()去生成陣列,以便迭代物件。因為處理返回值的純函式更容易定位問題

    const numbers = [1, 2, 3, 4, 5];
    
    // bad
    let sum = 0;
    for (let num of numbers) {
      sum += num;
    }
    sum === 15;
    
    // good
    let sum = 0;
    numbers.forEach((num) => {
      sum += num;
    });
    sum === 15;
    
    // best (use the functional force)
    const sum = numbers.reduce((total, num) => total + num, 0);
    sum === 15;
    
    // bad
    const increasedByOne = [];
    for (let i = 0; i < numbers.length; i++) {
      increasedByOne.push(numbers[i] + 1);
    }
    
    // good
    const increasedByOne = [];
    numbers.forEach((num) => {
      increasedByOne.push(num + 1);
    });
    
    // best (keeping it functional)
    const increasedByOne = numbers.map(num => num + 1);
    複製程式碼
  • 不要使用發生器,因為他們還沒有很好的相容

  • 如果你一定要用發生器,一定要注意關鍵字元的間距,舉個例子,function* 是一個不同於 function 的獨特構造,並且 *是其構造的一部分

    // bad
    function * foo() {
      // ...
    }
    
    // bad
    const bar = function * () {
      // ...
    };
    
    // bad
    const baz = function *() {
      // ...
    };
     
    // bad
    const quux = function*() {
      // ...
    };
    
    // bad
    function*foo() {
      // ...
    }
    
    // bad
    function *foo() {
      // ...
    }
    
    // very bad
    function*
    foo() {
      // ...
    }
      
    // very bad
    const wat = function*
    () {
      // ...
    };
    
    // good
    function* foo() {
      // ...
    }
    
    // good
    const foo = function* () {
      // ...
    };
    複製程式碼

Properties(屬性)

  • 通過常量訪問屬性的時候使用 .

    const luke = {
      jedi: true,
      age: 28,
    };
    
    // bad
    const isJedi = luke['jedi'];
    
    // good
    const isJedi = luke.jedi;
    複製程式碼
  • 通過變數訪問屬性的時候用 []

    const luke = {
      jedi: true,
      age: 28,
    };
    
    function getProp(prop) {
      return luke[prop];
    }
    
    const isJedi = getProp('jedi');
    複製程式碼
  • 使用 ** 進行指數運算

    // bad
    const binary = Math.pow(2, 10);
    
    // good
    const binary = 2 ** 10;
    複製程式碼

Variables(變數)

  • 總是使用 const 或者 let 來宣告變數,這樣做可以避免汙染全域性名稱空間

    // bad
    superPower = new SuperPower();
    
    // good
    const superPower = new SuperPower();
    複製程式碼
  • 每個變數宣告都對應一個 const 或者 let。這樣做,可以獨立的宣告每一個變數,而不需要考慮 ;,的關係,同時也方便對每個宣告進行除錯,而不是跳過所有的宣告

    // bad
    const items = getItems(),
        goSportsTeam = true,
        dragonball = 'z';
      
    // bad
    // (compare to above, and try to spot the mistake)
    const items = getItems(),
        goSportsTeam = true;
        dragonball = 'z';
          
    // good
    const items = getItems();
    const goSportsTeam = true;
    const dragonball = 'z';
    複製程式碼
  • letconst 進行分組,這樣增強程式碼可讀性

    // bad
    let i, len, dragonball,
        items = getItems(),
        goSportsTeam = true;
     
    // bad
    let i;
    const items = getItems();
    let dragonball;
    const goSportsTeam = true;
    let len;
      
    // good
    const goSportsTeam = true;
    const items = getItems();
    let dragonball;
    let i;
    let length;
    複製程式碼
  • 在需要的地方宣告變數,因為 constlet 是塊作用域而不是函式作用域

    // bad - unnecessary function call
    function checkName(hasName) {
      const name = getName();
    
      if (hasName === 'test') {
        return false;
      }
    
      if (name === 'test') {
        this.setName('');
        return false;
      }
    
      return name;
    }
    
    // good
    function checkName(hasName) {
      if (hasName === 'test') {
        return false;
      }
    
      const name = getName();
    
      if (name === 'test') {
        this.setName('');
        return false;
      }
    
      return name;
    }
    複製程式碼
  • 不要進行鏈式宣告變數的操作,這樣可能建立隱式的全域性變數

    // bad
    (function example() {
      // JavaScript interprets this as
      // let a = ( b = ( c = 1 ) );
      // The let keyword only applies to variable a; variables b and c become
      // global variables.
      let a = b = c = 1;
    }());
    
    console.log(a); // throws ReferenceError
    console.log(b); // 1
    console.log(c); // 1
    
    // good
    (function example() {
      let a = 1;
      let b = a;
      let c = a;
    }());
    
    console.log(a); // throws ReferenceError
    console.log(b); // throws ReferenceError
    console.log(c); // throws ReferenceError
      
    // the same applies for `const`
    複製程式碼

不要使用一元遞增和遞減操作符(++,--),因為一元遞增和一元遞減可能受到分號插入的影響,並且可能導致應用中的值遞增或者遞減,並且不會報錯。使用 num += 1 類似的語句也更加有表現力,並且可以避免預先遞增或者遞減從而導致程式發生意外

// bad
const array = [1, 2, 3];
let num = 1;
num++;
--num;
  
let sum = 0;
let truthyCount = 0;
for (let i = 0; i < array.length; i++) {
  let value = array[i];
  sum += value;
  if (value) {
    truthyCount++;
  }
}
  
// good
const array = [1, 2, 3];
let num = 1;
num += 1;
num -= 1;
  
const sum = array.reduce((a, b) => a + b, 0);
const truthyCount = array.filter(Boolean).length;
```                          
複製程式碼

Hoisting(變數提升)

  • var 宣告被置於函式作用域的頂部,但是他們的賦值不是, constlet宣告會被置於一個新概念TDZ內。因此, typeof() 方法不再安全

    // we know this wouldn’t work (assuming there
    // is no notDefined global variable)
    function example() {
      console.log(notDefined); // => throws a ReferenceError
    }
    
    // creating a variable declaration after you
    // reference the variable will work due to
    // variable hoisting. Note: the assignment
    // value of `true` is not hoisted.
    function example() {
      console.log(declaredButNotAssigned); // => undefined
      var declaredButNotAssigned = true;
    }
    
    // the interpreter is hoisting the variable
    // declaration to the top of the scope,
    // which means our example could be rewritten as:
    function example() {
      let declaredButNotAssigned;
      console.log(declaredButNotAssigned); // => undefined
      declaredButNotAssigned = true;
    }
    
    // using const and let
    function example() {
      console.log(declaredButNotAssigned); // => throws a ReferenceError
      console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
      const declaredButNotAssigned = true;
    }
    複製程式碼
  • 匿名函式表示式會提升變數名,而不是函式賦值

    function example() {
      console.log(anonymous); // => undefined
    
      anonymous(); // => TypeError anonymous is not a function
    
      var anonymous = function () {
        console.log('anonymous function expression');
      };
    }
    複製程式碼
  • 命名函式表示式提升變數名,而不是函式名或者函式體

    function example() {
      console.log(named); // => undefined
    
      named(); // => TypeError named is not a function
    
      superPower(); // => ReferenceError superPower is not defined
    
      var named = function superPower() {
        console.log('Flying');
      };
    }
    
    // the same is true when the function name
    // is the same as the variable name.
    function example() {
      console.log(named); // => undefined
    
      named(); // => TypeError named is not a function
    
      var named = function named() {
        console.log('named');
      };
    }
    複製程式碼
  • 函式宣告提升其名字和函式體

    function example() {
      superPower(); // => Flying
    
      function superPower() {
        console.log('Flying');
      }
    }
    複製程式碼

Comparison Operators & Equality(比較操作符和等號)

  • 使用 ===,!== 取代 ==,!=

  • 條件語句比如 if 會強制使用 ToBoolean 抽象方法來進行轉換,並且遵循以下規則:

    • Objects 轉換為 true
    • Undefined 轉換為 false
    • Null 轉換為 false
    • Booleans 轉換為 the value of the boolean
    • Numbers 轉換為 false 如果是 +0, -0, or NaN, 其餘為 true
    • Strings 轉換為 false 如果是空字串 '', 其餘為 true
    if ([0] && []) {
      // true
      // an array (even an empty one) is an object, objects will evaluate to true
    }
    複製程式碼
  • 使用布林值的快捷比較方式,但是顯示比較字串和數字

    // bad
    if (isValid === true) {
      // ...
    }
    
    // good
    if (isValid) {
      // ...
    }
    
    // bad
    if (name) {
      // ...
    }
    
    // good
    if (name !== '') {
      // ...
    }
    
    // bad
    if (collection.length) {
      // ...
    }
    
    // good
    if (collection.length > 0) {
      // ...
    }
    複製程式碼
  • switch 語句中的 casedefault 使用 {} 來建立塊,比如let, const, function, class 也是如此。因為在整個 switch 塊中詞法宣告是隨處可見的,但是隻有在賦值時才會被初始化,且只有 case 值達到時才會發生。但是當多個 case 子句試圖定義相同的東西時,就會發生問題

    // 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 {}
      }
    }
    複製程式碼
  • 三元表示式不應該巢狀,而應該單行表達

    // bad
    const foo = maybe1 > maybe2
      ? "bar"
      : value1 > value2 ? "baz" : null;
    
    // split into 2 separated ternary expressions
    const maybeNull = value1 > value2 ? 'baz' : null;
    
    // better
    const foo = maybe1 > maybe2
      ? 'bar'
      : maybeNull;
    
    // best
    const foo = maybe1 > maybe2 ? 'bar' : maybeNull;
    複製程式碼
  • 沒事不要隨便用三元表示式

    // bad
    const foo = a ? a : b;
    const bar = c ? true : false;
    const baz = c ? false : true;
    
    // good
    const foo = a || b;
    const bar = !!c;
    const baz = !c;
    複製程式碼
  • 當多個運算子混在一個語句中時,將需要的運算子括在括號裡面,並且用括號區分開 **,%+,-,*,/,這樣程式碼更加有可讀性,並且澄清了開發者的意圖

    // bad
    const foo = a && b < 0 || c > 0 || d + 1 === 0;
    
    // bad
    const bar = a ** b - 5 % d;
    
    // bad
    // one may be confused into thinking (a || b) && c
    if (a || b && c) {
      return d;
    }
     
    // good
    const foo = (a && b < 0) || c > 0 || (d + 1 === 0);
    
    // good
    const bar = (a ** b) - (5 % d);
    
    // good
    if (a || (b && c)) {
      return d;
    }
    
    // good
    const bar = a + b / c * d;
    複製程式碼

Blocks(塊)

  • 所有的多行塊都要用 {}

    // bad
    if (test)
      return false;
    
    // good
    if (test) return false;
    
    // good
    if (test) {
      return false;
    }
    
    // bad
    function foo() { return false; }
    
    // good
    function bar() {
      return false;
    }
    複製程式碼
  • 如果使用 if else, else 需要和 if} 在同一行

    // bad
    if (test) {
      thing1();
      thing2();
    }
    else {
      thing3();
    }
    
    // good
    if (test) {
      thing1();
      thing2();
    } else {
      thing3();
    }
    複製程式碼
  • 如果一個 if else 語句內每個程式碼塊都用了 return 語句,那麼 else 語句就沒有必要,分成多個 if 語句就行了

    // bad
    function foo() {
      if (x) {
        return x;
      } else {
        return y;
      }
    }
    
    // bad
    function cats() {
      if (x) {
        return x;
      } else if (y) {
        return y;
      }
    }
     
    // bad
    function dogs() {
      if (x) {
        return x;
      } else {
        if (y) {
          return y;
        }
      }
    }
    
    // good
    function foo() {
      if (x) {
        return x;
      }
    
      return y;
    }
    
    // good
    function cats() {
      if (x) {
        return x;
      }
    
      if (y) {
        return y;
      }
    }
     
    //good
    function dogs(x) {
      if (x) {
        if (z) {
          return y;
        }
      } else {
        return z;
      }
    }
    複製程式碼

Control Statements(控制語句)

  • 如果你的控制語句,比如 if,while等很長,或者超過了行寬,你可以對其中的內容進行換行,但是需要注意,邏輯運算子需要放在行首

    // bad
    if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) {
      thing1();
    }
    
    // bad
    if (foo === 123 &&
      bar === 'abc') {
      thing1();
    }
    
    // bad
    if (foo === 123
      && bar === 'abc') {
      thing1();
    }
    
    // bad
    if (
      foo === 123 &&
      bar === 'abc'
    ) {
      thing1();
    }
    
    // good
    if (
      foo === 123
      && bar === 'abc'
    ) {
      thing1();
    }
    
    // good
    if (
      (foo === 123 || bar === "abc")
      && doesItLookGoodWhenItBecomesThatLong()
      && isThisReallyHappening()
    ) {
      thing1();
    }
    
    // good
    if (foo === 123 && bar === 'abc') {
      thing1();
    }
    複製程式碼

Comments(註釋)

多行註釋使用 /** ... */

// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
    
  // ...
    
  return element;
}
 
// good
/**
 * make() returns a new element
 * based on the passed-in tag name
 */
    function make(tag) {
 
  // ...
 
  return element;
}
複製程式碼
  • 單行註釋用 //,並且在註釋內容的上一行,在註釋語句之前要空一行,當然,如果註釋在檔案的第一行就不需要空行了

    // bad
    const active = true;  // is current tab
    
    // good
    // is current tab
    const active = true;
    
    // bad
    function getType() {
      console.log('fetching type...');
      // set the default type to 'no type'
      const type = this.type || 'no type';
    
      return type;
    }
    
    // good
    function getType() {
      console.log('fetching type...');
    
      // set the default type to 'no type'
      const type = this.type || 'no type';
      return type;
    }
    
    // also good
    function getType() {
      // set the default type to 'no type'
      const type = this.type || 'no type';
      return type;
    }
    複製程式碼

註釋文字以空格作為開始,方便閱讀

```javascript
// bad
//is current tab
const active = true;
  
// good
// is current tab
const active = true;
  
// bad
/**
 *make() returns a new element
 *based on the passed-in tag name
 */
function make(tag) {
  
  // ...
  
  return element;
}
  
// good
/**
 * make() returns a new element
 * based on the passed-in tag name
 */
function make(tag) {
  
  // ...
  
  return element;
}
複製程式碼
  • 為你的提交或者評論加上 FIXME 或者 TODO 的字首,好讓其他開發者迅速明白你的意思。 FIXME表示這個問題需要弄清楚,TODO表示這個問題需要解決

  • 使用 // FIXME 去註釋問題

    class Calculator extends Abacus {
      constructor() {
        super();
    
        // FIXME: shouldn’t use a global here
        total = 0;
      }
    }
    複製程式碼
  • 使用 // TODO 去註釋問題的解決方法

    class Calculator extends Abacus {
      constructor() {
        super();
      
        // TODO: total should be configurable by an options param
        this.total = 0;
      }
    }
    複製程式碼

Whitespace(空格)

  • 使用 tab 去設定兩個空格

    // bad
    function foo() {
    ∙∙∙∙let name;
    }
    
    // bad
    function bar() {
    ∙let name;
    }
    
    // good
    function baz() {
    ∙∙let name;
    }
    複製程式碼
  • 使用 {} 之前空一格

    // bad
    function test(){
      console.log('test');
    }
    
    // good
    function test() {
      console.log('test');
    }
    
    // bad
    dog.set('attr',{
      age: '1 year',
      breed: 'Bernese Mountain Dog',
    });
    
    // good
    dog.set('attr', {
      age: '1 year',
      breed: 'Bernese Mountain Dog',
    });
    複製程式碼
  • 判斷語句(if,while)左括號之前加一個空格,在函式宣告,函式呼叫,引數列表的 () 不需要空格

    // bad
    if(isJedi) {
      fight ();
    }
    
    // good
    if (isJedi) {
      fight();
    }
    
    // bad
    function fight () {
      console.log ('Swooosh!');
    }
    
    // good
    function fight() {
      console.log('Swooosh!');
    }
    複製程式碼
  • 操作符之間要加空格

    // bad
    const x=y+5;
    
    // good
    const x = y + 5;
    複製程式碼
  • 檔案匯出通過換行符結束

    // bad
    import { es6 } from './AirbnbStyleGuide';
      // ...
    export default es6;
    複製程式碼
    // bad
    import { es6 } from './AirbnbStyleGuide';
      // ...
    export default es6;↵
    ↵
    複製程式碼
    // good
    import { es6 } from './AirbnbStyleGuide';
      // ...
    export default es6;↵
    複製程式碼
  • 如果寫一個長的方法鏈(連續使用超過三個方法)時,使用縮排來表示層級關係。使用前導點來表示該行是一個方法呼叫而不是一個新的語句

    // 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();
    
    // bad
    const leds = 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);
    
    // good
    const leds = 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);
    
    // good
    const leds = stage.selectAll('.led').data(data);
    複製程式碼
  • 塊與塊,塊與語句之間需要空一行

    // bad
    if (foo) {
      return bar;
    }
    return baz;
    
    // good
    if (foo) {
      return bar;
    }
    
    return baz;
    
    // bad
    const obj = {
      foo() {
      },
      bar() {
      },
    };
    return obj;
    
    // good
    const obj = {
      foo() {
      },
    
      bar() {
      },
    };
    
    return obj;
    
    // bad
    const arr = [
      function foo() {
      },
      function bar() {
      },
    ];
    return arr;
    
    // good
    const arr = [
      function foo() {
      },
    
      function bar() {
      },
    ];
    
    return arr;
    複製程式碼
  • 塊內不要空行

    // bad
    function bar() {
    
      console.log(foo);
    
    }
    
    // bad
    if (baz) {
    
      console.log(qux);
    } else {
      console.log(foo);
    
    }
    
    // bad  
    class Foo {
    
      constructor(bar) {
        this.bar = bar;
      }
    }
    
    // good
    function bar() {
      console.log(foo);
    }
    
    // good
    if (baz) {
      console.log(qux);
    } else {
      console.log(foo);
    }
    複製程式碼
  • () 裡面不要加空格

    // bad
    function bar( foo ) {
      return foo;
    }
    
    // good
    function bar(foo) {
      return foo;
    }
    
    // bad
    if ( foo ) {
      console.log(foo);
    }
    
    // good
    if (foo) {
      console.log(foo);
    }
    複製程式碼
  • [] 不要隨意加空格

    // bad
    const foo = [ 1, 2, 3 ];
    console.log(foo[ 0 ]);
    
    // good
    const foo = [1, 2, 3];
    console.log(foo[0]);
    複製程式碼
  • {} 裡面要加空格

    // bad
    const foo = {clark: 'kent'};
    
    // good
    const foo = { clark: 'kent' };
    複製程式碼
  • 除了之前提到的長字串,避免出現一行程式碼超過100個字元的情況,這樣確保了可維護性和可讀性

    // bad
    const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy;
    
    // bad
    $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));
    
    // good
    const foo = jsonData
      && jsonData.foo
      && jsonData.foo.bar
      && jsonData.foo.bar.baz
      && jsonData.foo.bar.baz.quux
      && jsonData.foo.bar.baz.quux.xyzzy;
    
    // good
    $.ajax({
      method: 'POST',
      url: 'https://airbnb.com/',
      data: { name: 'John' },
    })
      .done(() => console.log('Congratulations!'))
      .fail(() => console.log('You have failed this city.'));
    複製程式碼

Commas(逗號)

  • 逗號不要放在行首

    // bad
    const story = [
        once
      , upon
      , aTime
    ];
    
    // good
    const story = [
      once,
      upon,
      aTime,
    ];
    
    // bad
    const hero = {
        firstName: 'Ada'
      , lastName: 'Lovelace'
      , birthYear: 1815
      , superPower: 'computers'
    };
    
    // good
    const hero = {
      firstName: 'Ada',
      lastName: 'Lovelace',
      birthYear: 1815,
      superPower: 'computers',
    };
    複製程式碼

有時需要附加的逗號,一是為了在 git 上能保持一致,因為 git 在增減之後都會帶上逗號,二是一些像Babel這樣的轉譯器會自動刪除不必要的逗號,這意味著不必擔心傳統瀏覽器中的逗號尾隨問題

// bad - git diff without trailing comma
const hero = {
     firstName: 'Florence',
-    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing']
};
 
// good - git diff with trailing comma
const hero = {
     firstName: 'Florence',
     lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing'],
};
  
// bad
const hero = {
  firstName: 'Dana',
  lastName: 'Scully'
};
  
const heroes = [
  'Batman',
  'Superman'
];
  
// good
const hero = {
  firstName: 'Dana',
  lastName: 'Scully',
};
  
const heroes = [
  'Batman',
  'Superman',
];
  
// bad
function createHero(
  firstName,
  lastName,
  inventorOf
) {
  // does nothing
}
  
// good
function createHero(
  firstName,
  lastName,
  inventorOf,
) {
  // does nothing
}
  
// good (note that a comma must not appear after a "rest" element)
function createHero(
  firstName,
  lastName,
  inventorOf,
  ...heroArgs
) {
  // does nothing
}
  
// bad
createHero(
  firstName,
  lastName,
  inventorOf
);
  
// good
createHero(
  firstName,
  lastName,
  inventorOf,
);
  
// good (note that a comma must not appear after a "rest" element)
createHero(
  firstName,
  lastName,
  inventorOf,
  ...heroArgs
);
複製程式碼

Semicolons(分號)

  • 在程式碼的結尾一定要用 ; 結尾,防止javascript的自動分號插入機制使整個程式報錯

    // bad - raises exception
    const luke = {}
    const leia = {}
    [luke, leia].forEach(jedi => jedi.father = 'vader')
    
    // bad - raises exception
    const reaction = "No! That's impossible!"
    (async function meanwhileOnTheFalcon(){
      // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
      // ...
    }())
    
    // bad - returns `undefined` instead of the value on the next line - always happens when `return` is on a line by itself because of ASI!
    function foo() {
      return
        'search your feelings, you know it to be foo'
    }
    
    // good
    const luke = {};
    const leia = {};
    [luke, leia].forEach((jedi) => {
      jedi.father = 'vader';
    });
    
    // good
    const reaction = "No! That's impossible!";
    (async function meanwhileOnTheFalcon(){
      // handle `leia`, `lando`, `chewie`, `r2`, `c3p0`
      // ...
    }());
    
    // good
    function foo() {
      return 'search your feelings, you know it to be foo';
    }
    複製程式碼

Type Casting & Coercion(強制型別轉換)

  • 在語句開始進行強制型別轉換

  • String 型別

    // => this.reviewScore = 9;
    
    // bad
    const totalScore = new String(this.reviewScore); // typeof totalScore is "object" not "string"
    
    // bad
    const totalScore = this.reviewScore + ''; // invokes this.reviewScore.valueOf()
    
    // bad
    const totalScore = this.reviewScore.toString(); // isn’t guaranteed to return a string
    
    // good
    const totalScore = String(this.reviewScore);
    複製程式碼
  • Number 型別,用 Number 或者 parseInt 進行強制轉換,通常 parseInt 需要一個基數來解析字串

    const inputValue = '4';
    
    // bad
    const val = new Number(inputValue);
    
    // bad
    const val = +inputValue;
    
    // bad
    const val = inputValue >> 0;
    
    // bad
    const val = parseInt(inputValue);
    
    // good
    const val = Number(inputValue);
    
    // good
    const val = parseInt(inputValue, 10);
    複製程式碼

如果 parseInt 是你程式碼的瓶頸,你不得不使用移位符來進行轉換時,一定要在註釋裡面說明

// good
/**
 * parseInt was the reason my code was slow.
 * Bitshifting the String to coerce it to a
 * Number made it a lot faster.
 */
const val = inputValue >> 0;
複製程式碼
  • 使用移位操作符時需要注意,數字可以表示為64位,但是移位操作符始終返回32位的源,對於大於32位的整數,移位操作可能會導致意外發生。最大的32位支援是 2,147,483,647

    2147483647 >> 0; // => 2147483647
    2147483648 >> 0; // => -2147483648
    2147483649 >> 0; // => -2147483647
    複製程式碼
  • Booleans 型別

    const age = 0;
    
    // bad
    const hasAge = new Boolean(age);
    
    // good
    const hasAge = Boolean(age);
    
    // best
    const hasAge = !!age;
    複製程式碼

Naming Conventions(命名協議)

  • 避免使用單字元命名,注意命名描述

    // bad
    function q() {
      // ...
    }
    
    // good
    function query() {
      // ...
    }
    複製程式碼
  • 命名物件,函式和例項時都使用駝峰命名

    // bad
    const OBJEcttsssss = {};
    const this_is_my_object = {};
    function c() {}
    
    // good
    const thisIsMyObject = {};
    function thisIsMyFunction() {}
    複製程式碼
  • 對命名物件和建構函式時使用帕斯卡命名

    // bad
    function user(options) {
      this.name = options.name;
    }
    
    const bad = new user({
      name: 'nope',
    });
    
    // good
    class User {
      constructor(options) {
        this.name = options.name;
      }
    }
    
    const good = new User({
      name: 'yup',
    });
    複製程式碼
  • 頭部,尾部不要使用下劃線,因為JavaScript的屬性或者方法沒有隱私的概念。前導下換線是一個常見的慣例,表示“私人”,事實上,這些屬性是完全公開的,這樣會讓人產生誤解

    // bad
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    this._firstName = 'Panda';
    
    // good
    this.firstName = 'Panda';
    複製程式碼
  • 不要儲存 this 指標,使用箭頭函式或者 # 繫結來取代

    // bad
    function foo() {
      const self = this;
      return function () {
        console.log(self);
      };
    }
    
    // bad
    function foo() {
      const that = this;
      return function () {
        console.log(that);
      };
    }
    
    // good
    function foo() {
      return () => {
        console.log(this);
      };
    }
    複製程式碼
  • 基本檔名應該與其匯出名字對應

    // file 1 contents
    class CheckBox {
      // ...
    }
    export default CheckBox;
    
    // file 2 contents
    export default function fortyTwo() { return 42; }
    
    // file 3 contents
    export default function insideDirectory() {}
    
    // in some other file
    // bad
    import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename
    import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export
    import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export
    
    // bad
    import CheckBox from './check_box'; // PascalCase import/export, snake_case filename
    import forty_two from './forty_two'; // snake_case import/filename, camelCase export
    import inside_directory from './inside_directory'; // snake_case import, camelCase export
    import index from './inside_directory/index'; // requiring the index file explicitly
    import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly
    
    // good
    import CheckBox from './CheckBox'; // PascalCase export/import/filename
    import fortyTwo from './fortyTwo'; // camelCase export/import/filename
    import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
    // ^ supports both insideDirectory.js and insideDirectory/index.js
    複製程式碼
  • 預設匯出一個方法時,使用駝峰命名錶示。同時,你的檔名應該與方法名一致

    function makeStyleGuide() {
      // ...
    }
    
    export default makeStyleGuide;
    複製程式碼
  • 匯出建構函式,類,單例,函式庫等時,使用帕斯卡命名

    const AirbnbStyleGuide = {
      es6: {
      },
    };
    
    export default AirbnbStyleGuide;
    複製程式碼
  • 縮略詞應該全是大小字母或者全是小寫字母構成,這樣才有可讀性

    // bad
    import SmsContainer from './containers/SmsContainer';
    
    // bad
    const HttpRequests = [
      // ...
    ];
    
    // good
    import SMSContainer from './containers/SMSContainer';
    
    // good
    const HTTPRequests = [
      // ...
    ];
    
    // also good
    const httpRequests = [
      // ...
    ];
    
    // best
    import TextMessageContainer from './containers/TextMessageContainer';
    
    // best
    const requests = [
      // ...
    ];
    複製程式碼

Accessors(訪問方法)

  • 屬性的訪問方法不是必須的

  • 不要使用JavaScript的 getters/setters,因為它們會造成意想不到的壞的影響,並且很難去測試,定位。所以如果你要用訪問函式,使用 getVal()setVal() 這樣的方式

    // bad
    class Dragon {
      get age() {
        // ...
      }
    
      set age(value) {
        // ...
      }
    }
    
    // good
    class Dragon {
      getAge() {
        // ...
      }
    
      setAge(value) {
        // ...
      }
    }
    複製程式碼
  • 如果一個屬性值或者方法返回值是布林型別,使用 isVal()或者 hasVal()這樣的形式

    // bad
    if (!dragon.age()) {
      return false;
    }
    
    // good
    if (!dragon.hasAge()) {
      return false;
    }
    複製程式碼
  • 可以建立類似 get()set() 這樣的函式方法,但是要注意保持一致

    class Jedi {
      constructor(options = {}) {
        const lightsaber = options.lightsaber || 'blue';
        this.set('lightsaber', lightsaber);
      }
    
      set(key, val) {
        this[key] = val;
      }
    
      get(key) {
        return this[key];
      }
    }
    複製程式碼

Events(事件)

  • 當將資料傳遞到事件方法裡面的時候,不要使用原始值直接進行傳遞,應該處理成物件字面量。這樣可以方便其他使用者修改或者檢視傳遞資料

    // 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

  • 通過 $ 來宣告一個承載jquery的元素

    // bad
    const sidebar = $('.sidebar');
    
    // good
    const $sidebar = $('.sidebar');
    
    // good
    const $sidebarBtn = $('.sidebar-btn');
    複製程式碼
  • 將jquery選擇器快取起來

    // bad
    function setSidebar() {
      $('.sidebar').hide();
    
      // ...
    
      $('.sidebar').css({
        'background-color': 'pink',
      });
    }
    
    // good
    function setSidebar() {
      const $sidebar = $('.sidebar');
      $sidebar.hide();
    
      // ...
    
      $sidebar.css({
        'background-color': 'pink',
      });
    }
    複製程式碼
  • 對於 DOM 節點的查詢使用級聯 $('.sidebar ul') 或者 父級 > 子級 $('.sidebar > ul')

  • 塊級jQuery物件查詢(通過選擇器物件進行查詢),使用 find

    // bad
    $('ul', '.sidebar').hide();
    
    // bad
    $('.sidebar').find('ul').hide();
    
    // good
    $('.sidebar ul').hide();
    
    // good
    $('.sidebar > ul').hide();
    
    // good
    $sidebar.find('ul').hide();
    複製程式碼

Standard Library(標準程式庫)

  • 使用 Number.isNaN 來代替全域性的 isNaN,因為全域性的 isNaN 會強制將非數字型別轉換為數字型別,任何強制轉換為非數字的都會返回true

    // bad
    isNaN('1.2'); // false
    isNaN('1.2.3'); // true
    
    // good
    Number.isNaN('1.2.3'); // false
    Number.isNaN(Number('1.2.3')); // true
    複製程式碼
  • 使用 Number.isFinite 來代替全域性的 isFinite,因為全域性的 isFinite 會強制將非數字型別轉換為數字型別,任何強制轉換為有限數字的結果都會返回true

    // bad
    isFinite('2e3'); // true
    
    // good
    Number.isFinite('2e3'); // false
    Number.isFinite(parseInt('2e3', 10)); // true
    複製程式碼

Testing(測試)

  • 無論您使用那種框架,都應該測試!

  • 儘量去寫一些寫的純函式,並且儘量減少突變情況的發生

  • 謹慎使用 stubs(存根) 和 mocks(虛擬資料),他們會讓你的測試更加脆弱

  • Airbnb 主要使用 mocha 來進行測試,偶爾也用 tape 來測試小的獨立模組

  • 100%的測試覆蓋率是最理想的

  • 每當你修復了一個bug,都需要寫一個迴歸測試。未經迴歸測試修正的錯誤,未來一定會重現

相關文章