Airbnb JavaScript Style 閱讀註解

Airbnb JavaScript Style 閱讀註解

提供一種合理的javascript的規範,對原文主要內容進行翻譯,同時對部份內容進行註釋。推薦你們先收藏,在寫代碼的時候能夠方便參考。javascript

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

若是您想閱讀原文?css

若是您想在github上查看?java

若是您想了解並使用 babel with airbnb?node

Types(數據類型)

  • 簡單的基本數據類型,直接使用其值react

    - `string`
    
    - `number`
    
    - `boolean`
    
    - `null`
    
    - `undefined`
    
    - `symbol`
const foo = 1;
let bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
  • 複雜的基本數據類型,直接使用其值的引用jquery

    - `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的意思webpack

Symbol()的聲明,由於 Symbol()返回值是一個相似於字符串的基本類型,不是一個對象,因此不能使用 new 命令git

let ylone = Symbol();
typeof(ylone);
?    
"symbol"
  
//爲聲明加上描述
let ylone1 = Symbol('hello');
ylone1;
?
Symbol(hello);

不管是不加描述,仍是所加的描述相同, Symbol() 函數的返回值都不相同es6

Symbol.for('key') 也會返回一個Symbol,可是Symbol.for()採用登記機制(會被登記在全局環境中供搜索),若是以前key已經存在,則直接返回該值,不然新建一個值。好比,若是你調用 Symbol.for("cat")30 次,每次都會返回同一個Symbol值,可是調用Symbol("cat")30 次,會返回 30 個不一樣的Symbol值。github

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、一、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;
}
  • 註釋文字以空格做爲開始,方便閱讀
// 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,都須要寫一個迴歸測試。未經迴歸測試修正的錯誤,將來必定會重現
相關文章
相關標籤/搜索