# Airbnb 자바스크립트 스타일 가이드 2020-09-22

no-whitespace-before-property 부터 eslint 레퍼런스를 읽었습니다.

파일 목록에 분류되어 있는 포스트는 eslint 를 읽고 업데이트 한 게시글 입니다.

# Classes & Constructors

# 클래스 사용 > 프로토타입 직접조작

  • 클래스 문법은 더 간결하고, 쉽다.
// 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;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 상속: extends 사용

  • extends
    • instanceof를 파괴하지 않고(?TODO) 프로포타입 상속을 위한 내장 방법
// 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];
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 클래스 메서드 체이닝: 메서드가 this 를 return

// 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()
  .setHeight(20);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 클래스의 커스텀 toString() 메서드

  • 사용해도 되지만 성공적으로 작동하거나 사이드 이펙트가 없는지 확인해야 한다.
class Jedi {
  constructor(options = {}) {
    this.name = options.name || 'no name';
  }

  getName() {
    return this.name;
  }

  toString() {
    return `Jedi - ${this.getName()}`;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# default 생성자

  • 빈 constructor 는 생략해도 된다.
  • 빈 생성자를 가지는 클래스를 상속하는 클래스에서, 또한 빈 생성자를 가지고 있을 땐 constructor 를 써주지 않아도 된다.
// bad
class Jedi {
  constructor() {}

  getName() {
    return this.name;
  }
}

// bad
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
  }
}

class Jedi {
  getName() {
    return this.name;
  }
}

class Rey extends Jedi { }

// good
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
    this.name = 'Rey';
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 중복된 클래스 멤버를 가지는 클래스 X

  • 중복된 클래스 멤버 선언은. 클래스 멤버를 사용할 때 마지막 것을 선택한다.
  • 중복된 클래스 멤버를 가질 수 있는 것은 버그이다.
// bad
class Foo {
  bar() { return 1; }
  bar() { return 2; }
}

// good
class Foo {
  bar() { return 1; }
}

// good
class Foo {
  bar() { return 2; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 클래스 메서드에서 this 를 사용하지 않을 경우 : static 메서드 사용

// bad
class Foo {
  bar() {
    console.log('bar');
  }
}

// good - this 를 사용할 경우 -> 일반 메서드
class Foo {
  bar() {
    console.log(this.bar);
  }
}

// good - constructor 를 생략할 수 있다.
class Foo {
  constructor() {
    // ...
  }
}

// good - this 를 사용하지 않은 경우 -> static methods
class Foo {
  static bar() {
    console.log('bar');
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Modules

# (import/export) > non-standard 모듈 시스템.

  • (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;
1
2
3
4
5
6
7
8
9
10
11

# * wildcard import X

  • default Export 인지 named export 인지 헷갈릴 수 있다.
// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';

// good
import AirbnbStyleGuide from './AirbnbStyleGuide';
1
2
3
4
5

# from 으로 Import 후 직접적인 export X

  • 간결하지만, export / import 의 일관성을 위해 분리한다.
// bad
// filename es6.js
export { es6 as default } from './AirbnbStyleGuide';

// good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;
1
2
3
4
5
6
7
8

# 하나의 path : 하나의 import

  • 하나의 path 에서 여러개의 import 를 쓴다면 유지보수가 어렵다.
// 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';
1
2
3
4
5
6
7
8
9
10
11
12
13

# mutable: export X

  • 특수한 경우를 제외하고 상수 reference 만 export 한다.
// bad
let foo = 3;
export { foo };

// good
const foo = 3;
export { foo };
1
2
3
4
5
6
7

# 모듈에 export 가 하나만 있을 때: export default > named export

  • 파일에 하나의 export 를 권장
    • 가독성, 유지보수에 낫다.
// bad
export function foo() {}

// good
export default function foo() {}
1
2
3
4
5

# 모든 import 는 non-import 명령문 위에 두기

  • import 는 호이스팅 된다.
    • 호이스팅이 되는 것들은 아래에 두었을때 문제가 발생할 가능성이 있다는 말?
// bad
import foo from 'foo';
foo.init();

import bar from 'bar';

// good
import foo from 'foo';
import bar from 'bar';

foo.init();
1
2
3
4
5
6
7
8
9
10
11

# 여러줄의 imports : 들여쓰기(indent)

// bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';

// good
import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE,
} from 'path';
1
2
3
4
5
6
7
8
9
10
11

# import 문: Webpack loader 구문 X

  • import 에서 webpack 구문을 사용하면 코드를 모듈 번들러에 연결한다 (TODO 웹팩에 대한 지식 부족..)
  • 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';
1
2
3
4
5
6
7

# import/extensions : 자바스크립트 파일이름 extensions X

  • extensions 포함의 문제점.
    • 리팩토링이 금지된다.
      • (? TODO .js -> .jsx .. 등 js 관련 확장자를 다 수정해 줘야하는 문제점?)
// bad
import foo from './foo.js';
import bar from './bar.jsx';
import baz from './baz/index.jsx';

// good
import foo from './foo';
import bar from './bar';
import baz from './baz';
1
2
3
4
5
6
7
8
9

# Iterators and Generators

# 이터레이터 사용: 고차 함수 > 루프 for-in / for-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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 현재 제너레이터는 ES5 트랜스파일링이 잘 되지 않는다.

# 제너레이터의 * 연산자의 적절한 공백 : function* foo()

  • 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* () {
  // ...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# Properties

# 프로퍼티 접근: dot 표기법 사용

const luke = {
  jedi: true,
  age: 28,
};

// bad
const isJedi = luke['jedi'];

// good
const isJedi = luke.jedi;
1
2
3
4
5
6
7
8
9
10

# 변수로 프로퍼티 접근 : [] 사용

const luke = {
  jedi: true,
  age: 28,
};

function getProp(prop) {
  return luke[prop];
}

const isJedi = getProp('jedi');
1
2
3
4
5
6
7
8
9
10

# 지수 연산 : ** > Math.pow

// bad
const binary = Math.pow(2, 10);

// good
const binary = 2 ** 10;
1
2
3
4
5

# Variables

# 변수 선언 : 항상 const / let

  • const / let
    • 전역 변수가 생성되지 않는다.
    • 전역 변수 공간을 어지럽히면 안된다.
// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();
1
2
3
4
5

# 하나의 변수 선언/할당 : 하나의 const/let

  • 새로 변수를 선언하고 추가할 때 더 쉽다.
  • 하나의 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';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# const/ let 의 그룹화

  • 그룹화는, 나중에 이전에 할당 된 변수 중 하나에 따라 변수를 할당해야 할 때 유용합니다. (?TODO)
// 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 합리적인 위치에 변수할당문 사용

  • 필요한 위치 근처에 둔다.
  • let/const 은 block 스코프 이기 때문임.
// 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 변수 할당문의 체이닝 X

  • 변수 할당문의 체이닝은 암묵적으로 글로벌 변수를 만들어 낸다.
// bad
(function example() {
  let a = b = c = 1;
  // let a = ( b = ( c = 1 ) );
  // let 은 a 에게만 적용된다. 
  // b 와 c 는 전역 변수가 된다.
}());

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

//  `const` 도 마찬가지.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 단항 증감 (++, --) X

  • 단항 증감은 세미콜론 자동 삽입의 대상이 된다.
    • silent errors 의 원인이 될 수 있다.
  • num++ / num ++ 보다 num += 1 이 더 바람직하다.
  • 단항 증감을 사용하지 않으면,
    • pre-incrementing/pre-decrementing 실수를 방지할 수 있다.
// 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# = 할당문 전후로 줄바꿈 X, 줄바꿈이 필요하다면 () 사용

  • 할당문 전후로 줄바꿈이 발생하면 난독화를 일으킬 수 있다.
// bad
const foo =
  superLongLongLongLongLongLongLongLongFunctionName();

// bad
const foo
  = 'superLongLongLongLongLongLongLongLongString';

// good
const foo = (
  superLongLongLongLongLongLongLongLongFunctionName()
);

// good
const foo = 'superLongLongLongLongLongLongLongLongString';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 사용하지 않은 변수는 비허용된다.

  • 선언되었지만 사용되지 않은 변수는 리팩토링이 완전하게 되지 않았다는 의미이다.
  • 코드 공간을 불필요하게 차지함.
  • 읽는 사람에게 혼동을 줌.
// bad

var some_unused_var = 42;

// y 는 쓰여지기만 했지 사용되지 않았다.
var y = 10;
y = 5;

// 사용되지 않은 변수
var z = 0;
z = z + 1;

// 사용되지 않은 함수
function getX(x, y) {
    return x;
}

// good

function getXPlusY(x, y) {
  return x + y;
}

var x = 1;
var y = a + 2;

alert(getXPlusY(x, y));

// 나머지 프로퍼티 연산자: 객체에서 특정 키를 의도적으로 빠뜨리고 추출하는 방법이다. 
// 'type' 은 사용하지 않아도 괜찮다.
var { type, ...coords } = data;
// 'coords' 는 data 객체에서 'type' 프로퍼티가 빠진 객체다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# Hoisting

# var, const/let 선언의 호이스팅:

  • var
    • 선언문은 가장 가까운 함수 스코프의 최상단으로 호이스팅 된다.
    • 할당문은 호이스팅 되지 않는다.
  • const/let
    • 선언에는 Temporal Dead Zones (TDZ) 가 존재한다.
    • typeof 는 안전하지 않다. (예시 참고)
// 아래 함수는 참조 에러를 일으킨다 (notDefined 라는 전역 변수가 존재하지 않을 때)
function example() {
  console.log(notDefined); // => throws a ReferenceError
}

// declaredButNotAssigned 는 호이스팅이 일어나기 때문에 사용가능하다.
// Note: 하지만 true 할당은 호이스팅 되지 않음.
function example() {
  console.log(declaredButNotAssigned); // => undefined
  var declaredButNotAssigned = true;
}

// 인터프리터는 변수의 선언을 스코프의 최상단으로 호이스팅 한다.
function example() {
  let declaredButNotAssigned;
  console.log(declaredButNotAssigned); // => undefined
  declaredButNotAssigned = true;
}

// const / let 의 예시
function example() {
  console.log(declaredButNotAssigned); // => throws a ReferenceError
  console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
  const declaredButNotAssigned = true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 14.2 익명함수 표현식의 호이스팅 : 변수 이름 O, 함수할당 X

function example() {
  console.log(anonymous); // => undefined

  anonymous(); // => TypeError anonymous is not a function

  var anonymous = function () {
    console.log('anonymous function expression');
  };
}
1
2
3
4
5
6
7
8
9

# 이름있는 함수 표현식의 호이스팅: 변수이름 O, 함수이름과 body X

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');
  };
}

// 할당된 변수이름과 함수 표현식의 이름기 같은 경우.
function example() {
  console.log(named); // => undefined

  named(); // => TypeError named is not a function

  var named = function named() {
    console.log('named');
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 함수 선언문의 호이스팅: 함수 이름, body O

function example() {
  superPower(); // => Flying

  function superPower() {
    console.log('Flying');
  }
}
1
2
3
4
5
6
7

# Comparison Operators & Equality

# (===) (!==) > (==)

# Conditional statements

if 와 같은 조건문

  • ToBoolean 의 추상 메서드로 강제변환을 사용하여 식을 평가한다.
  • 식을 평가하는 규칙은 다음과 같다.
    • Objects -> true
    • Undefined -> false
    • Null -> false
    • Booleans -> true / false
    • Numbers
      • false : +0, -0, NaN
      • otherwise true
    • Strings
      • false : empty string ''
      • otherwise true
if ([0] && []) {
  // true
  // 배열은 객체이다. 빈객체도 객체임
  // 객체는 항상 true 
}
1
2
3
4
5

# reference: boolean -> 축약 , 문자열/숫자 -> 명시적 비교

// bad
if (isValid === true) {
  // ...
}

// good
if (isValid) {
  // ...
}

// bad
if (name) {
  // ...
}

// good
if (name !== '') {
  // ...
}

// bad
if (collection.length) {
  // ...
}

// good
if (collection.length > 0) {
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# Truth Equality and JavaScript by Angus Croll.🔗

# 어휘 선언이 있는 case/default 문 : { } braces 사용

  • Lexical declarations [어휘 선언]
    • let, const, function, and class
    • 스위치 블록 스코프가 존재한다.
    • case/default 에 { } 가 없다면 여러 case 절에서 동일한 것을 정의하려고 할 때 문제가 발생함.
// bad
switch (foo) {
  case 1:
    let x = 1;
    break;
  case 2:
    const y = 2;
    break;
  case 3:
    function f() {
      // ...
    }
    break;
  default:
    class C {}
}
switch (1) {
  case 1:
    let v ;
    break;
  case 2:
    let v;  // Uncaught SyntaxError: Identifier 'v' has already been declared
    break;
  case 3:
    function f() {
      // ...
    }
    break;
  default:
    class C {}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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 {}
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 삼항의 중첩 X : 기본적으로 단일행 표현식

// 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 불필요한 삼항 표현식 X

// 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;
1
2
3
4
5
6
7
8
9

# 혼합 연산자 : () 로 감싸기

  • 혼합연산자는 우선순위가 모호할 수 있다.
  • +, -, ** 는 예외
  • /, * 는 모호 하다.
// bad
const foo = a && b < 0 || c > 0 || d + 1 === 0;

// bad
const bar = a ** b - 5 % d;

// bad
// (a || b) && c 와 혼동될 수 있다.
if (a || b && c) {
  return d;
}

// bad
const bar = a + b / c * 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# if/else 여러줄 블록 : if 의 닫기 } 와 같은 줄에 else 두기

// bad
if (test) {
  thing1();
  thing2();
}
else {
  thing3();
}

// good
if (test) {
  thing1();
  thing2();
} else {
  thing3();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# return 을 포함하는 if 블록 + else/else if

  • return 을 포함하는 if 블록
    • 후속 else 불필요
    • 후속 else if 의 리턴 -> if 리턴으로 분리
// bad
function foo() {
  if (x) {
    return x;
  } else {
    return y;
  }
}
// good
function foo() {
  if (x) {
    return x;
  }

  return y;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// bad
function cats() {
  if (x) {
    return x;
  } else if (y) {
    return y;
  }
}
// good
function cats() {
  if (x) {
    return x;
  }

  if (y) {
    return y;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// bad
function dogs() {
  if (x) {
    return x;
  } else {
    if (y) {
      return y;
    }
  }
}

// good
function dogs(x) {
  if (x) {
    if (z) {
      return y;
    }
  } else {
    return z;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Control Statements

  • if, while etc

# 제어문의 아주 긴 조건 : 줄바꿈, 논리연산은 새 줄에 시작

  • 연산을 새 줄에 시작하는 것
    • 메서드 체이닝 패턴
    • 가독성
// 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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 제어문 > 선택 연산자

// bad
!isRunning && startRunning();

// good
if (!isRunning) {
  startRunning();
}
1
2
3
4
5
6
7

# 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# // : 한줄 주석

  • 주석의 대상 위에 한줄로 표기
  • 주석 위에는 한 줄 비우기
    • 주석이 블록이 첫줄이 아닌경우 제외
// 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 주석의 시작은 항상 공백

  • 가독성
// 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# FIXME / TODO

  • 실행 가능한 주석
  • FIXME: 재검토해야 할 문제를 지적
  • TODO: 구현해야하는 문제
class Calculator extends Abacus {
  constructor() {
    super();

    // FIXME: 여기서 전역을 사용해서는 안됩니다.
    total = 0;
  }
}
1
2
3
4
5
6
7
8
class Calculator extends Abacus {
  constructor() {
    super();

    // TODO: total은 옵션 매개 변수로 구성 할 수 있어야합니다.
    this.total = 0;
  }
}
1
2
3
4
5
6
7
8

# Whitespace

# 들여쓰기 indent : 2 space character

// bad
function foo() {
∙∙∙∙let name;
}

// bad
function bar() {let name;
}

// good
function baz() {
∙∙let name;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# space-before-blocks: 여는 { 의 앞에 공백 하나

// 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',
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# keyword-spacing : 제어문과 함수의 호출/선언

  • 제어문 : 여는 ( 앞에 공백 하나
  • 함수 호출/선언: 여는 ( 앞에 공백 X
// bad
if(isJedi) {
  fight ();
}

// good
if (isJedi) {
  fight();
}

// bad
function fight () {
  console.log ('Swooosh!');
}

// good
function fight() {
  console.log('Swooosh!');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# space-infix-ops : 연산자 사이 공백

// bad
const x=y+5;

// good
const x = y + 5;
1
2
3
4
5

# eol-last : 파일 마지막 - 하나의 newline(개행) 문자로 끝내기

  • 비어 있지 않은 파일의 후행 줄 바꿈은 일반적인 UNIX 관용구입니다.
  • 후행 줄 바꿈의 이점은 쉘 프롬프트를 방해하지 않고 파일 및 출력 파일을 터미널에 연결하거나 추가하는 기능을 포함합니다.
// bad
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;
1
2
3
4
// bad
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;
1
2
3
4
5
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;
1
2
3

# newline-per-chained-call 체이닝 마다 개행

  • 긴 메서드 체이닝 (2개이상)
    • 들여쓰기
    • . dot 으로 시작하기
      • . 으로 시작하는 것 새로운 문이 아닌 메서드 호출이라는 것을 강조할 수 있다.
// 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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# no-whitespace-before-property 🔗 프로퍼티 앞에 공백 X

  • javascript 는 객체와 속성 사이의 공백을 허용한다.
    • 가독성을 저하하고 오류를 유발할 수 있다.
foo. bar .baz . quz
1
  • 점 주위 (), [] 괄호 앞에 공백을 쓰지 않아야 된다.
  • 체이닝의 개행에서만 허용 한다.
foo
  .bar()
  .baz()
  .qux()
1
2
3
4
/*eslint no-whitespace-before-property: "error"*/
foo [bar] // foo.bar

foo. bar  // foo[bar]

foo .bar  // foo[ bar ]

foo. bar. baz // foo.bar.baz

foo. bar() // foo
  .baz()  //  .bar().baz()

foo
  .bar(). baz()

/*
foo
  .bar()
  .baz()

foo.
  bar().
  baz()
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 블록 다음/ 다음 statement 앞 : 빈 줄

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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# padded-blocks: 블록을 빈 줄로 채우지 말것

// 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# no-multiple-empty-lines: 멀티라인 빈줄 X

  • 공백은 코드의 논리적 섹션을 분리하는데 유용하지만 과도한 공백은 화면을 더 많이 차지 한다.
  • 코드를 읽을 때 필요한 스크롤을 줄이는 것을 목표로 한다.
// bad
class Person {
  constructor(fullName, email, birthday) {
    this.fullName = fullName;


    this.email = email;


    this.setAge(birthday);
  }


  setAge(birthday) {
    const today = new Date();


    const age = this.getAge(today, birthday);


    this.age = age;
  }


  getAge(today, birthday) {
    // ..
  }
}

// good
class Person {
  constructor(fullName, email, birthday) {
    this.fullName = fullName;
    this.email = email;
    this.setAge(birthday);
  }

  setAge(birthday) {
    const today = new Date();
    const age = getAge(today, birthday);
    this.age = age;
  }

  getAge(today, birthday) {
    // ..
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# space-in-parens : ( ) 안에 공백 X

// 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
foo( 'bar' );
var x = ( 1 + 2 ) * 3;

// good
foo('bar');
var x = (1 + 2) * 3;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# array-bracket-spacing : [ ] 안에 공백 X

// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);

// good
const foo = [1, 2, 3];
console.log(foo[0]);
1
2
3
4
5
6
7

# object-curly-spacing : { } 안에 공백 O

// bad
const foo = {clark: 'kent'};

// good
const foo = { clark: 'kent' };
1
2
3
4
5

# 한줄에 100 자(공백 포함) 이상 X

// 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.'));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# block-spacing : { 와 } 가 같은줄 : braces 내부 전후로 공백

// bad
function foo() {return true;}
if (foo) { bar = 0;}

// good
function foo() { return true; }
if (foo) { bar = 0; }
1
2
3
4
5
6
7

# comma-spacing : , 전에 공백 X / , 후 공백 O

// bad
var foo = 1,bar = 2;
var arr = [1 , 2];

// good
var foo = 1, bar = 2;
var arr = [1, 2];
1
2
3
4
5
6
7

# computed-property-spacing : 공백의 일관성

  • 계산된 속성 : 공백을 넣거나 빼거나 둘중 하나로 통일해야 한다.
// bad
obj[foo ]
obj[ 'foo']
var x = {[ b ]: a}
obj[foo[ bar ]]

// good
obj[foo]
obj['foo']
var x = { [b]: a }
obj[foo[bar]]
1
2
3
4
5
6
7
8
9
10
11

# func-call-spacing : 함수 호출 공백 X

// bad
func ();

func
();

// good
func();
1
2
3
4
5
6
7
8

# key-spacing : 키 공백

  • 개체 리터럴 속성에서 키와 값 사이의 간격
    • 키는 붙이고 value 는 띄운다.
// bad
var obj = { foo : 42 };
var obj2 = { foo:42 };

// good
var obj = { foo: 42 };
1
2
3
4
5
6

# no-trailing-spaces : 줄 끝 후행 공백 X

  • 후행 공백 : 공백, 탭 및 기타 유니 코드 공백 문자
  • 소스 제어 시스템에서 diff 로 플래그가 지정되는 문제점.
// bad
var foo = 0;//•••••
var baz = 5;//••

// good
var foo = 0;
var baz = 5;
1
2
3
4
5
6
7

# no-multiple-empty-lines : 빈 멀티라인 X

  • 오직 파일 끝 한줄의 개행 문자만 허용됨.
  • 파일 시작에 개행 문자 X
// bad - 빈 멀티라인
var x = 1;


var y = 2;

// bad - 파일끝에 두 줄 이상의 개행문자
var x = 1;
var y = 2;


// bad - 파일 시작에 하나 이상의 개행문자

var x = 1;
var y = 2;

// good
var x = 1;
var y = 2;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Commas

# Reference & Origin

https://github.com/airbnb/javascript 있는 포스트는 eslint 를 읽고 업데이트 한 게시글 입니다.

# Arrays

# 배열 생성: 리터럴 문법 []

// bad
const items = new Array();

// good
const items = [];
1
2
3
4
5

# 배열의 items 생성 : push

const someStack = [];

// bad
someStack[someStack.length] = 'abracadabra';

// good
someStack.push('abracadabra');
1
2
3
4
5
6
7

# 배열 복사 : ... spread operator

// bad
const len = items.length;
const itemsCopy = [];
let i;

for (i = 0; i < len; i += 1) {
  itemsCopy[i] = items[i];
}

// good
const itemsCopy = [...items];
1
2
3
4
5
6
7
8
9
10
11

# iterable 객체 -> 배열 : spreads ... > Array.from.

const foo = document.querySelectorAll('.foo');

// good
const nodes = Array.from(foo);

// best
const nodes = [...foo];
1
2
3
4
5
6
7

iterable 객체 document.querySelectorAll('.foo')__proto__NodeList

# array-like 객체 -> 배열 : Array.from

let arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };
/*
Object
0: "foo"
1: "bar"
2: "baz"
length: 3
__proto__: Object
*/
// bad
const arr = Array.prototype.slice.call(arrLike);

// good
const arr = Array.from(arrLike); // ["foo", "bar", "baz"]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let arrLike = { '0': 'foo', 1: 'bar', 2: 'baz', length: 3 };
Array.from(arrLike); // ["foo", "bar", "baz"]

arrLike = { '212': 'foo', 1: 'bar', 2: 'baz', length: 3 };
Array.from(arrLike); // [undefined, "bar", "baz"]
1
2
3
4
5

유사 배열의 조건

  1. length 프로퍼티가 존재.
  2. index 번호가 0번부터 시작해서 1씩증가.

# iterable map : Array.from > spread ...

Array.from 은 중간에 배열을 생성하는 것을 방지한다.

const bar = (v) => v + 1;
const foo = [1, 2, 3];

// bad
const baz = [...foo].map(bar);  // [2, 3, 4]

// good
const baz = Array.from(foo, bar)  // [2, 3, 4]
1
2
3
4
5
6
7
8

# 배열 메서드의 callback 에서 return 문을 사용해야 한다

// good
[1, 2, 3].map((x) => {
  const y = x + 1;
  return x * y;
});

// good
[1, 2, 3].map((x) => x + 1);

// bad - callback 에서 return 문을 쓰지 않으면, 첫 이터레이션 이후 `acc` 의 값은 undefined 가 된다.
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  const flatten = acc.concat(item);
});

// good
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
  const flatten = acc.concat(item);
  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;
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 여러 줄이있는 배열의 줄바꿈: 괄호를 열때, 닫기전 사용

// 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,
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# Destructuring

# 객체 다중 프로퍼티 사용 : destructuring

  • 임시 references 생성을 줄여준다.
// 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}`;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 배열 destructuring

const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;
1
2
3
4
5
6
7
8

# 리턴값이 여러개 일 때 : 객체 비구조화 > 배열 비구조화

  • 객체 비구조화는, 추후에 코드에 새 속성을 추가 할 수 있다.
  • 리턴 값을 순서에 상관없이 가져올 수 있다.
  • 호출 할 때, 원하는 data 를 선택할 수 있다.
// bad
function processInput(input) {
  // then a miracle occurs
  return [left, right, top, bottom];
}

// 호출을 할 때 반환값의 순서를 고려해야 하는 문제가 있다.
const [left, __, top] = processInput(input);

// good
function processInput(input) {
  // then a miracle occurs
  return { left, right, top, bottom };
}

// 호출 할 때, 원하는 data 를 선택할 수 있다.
const { left, top } = processInput(input);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Strings

# 문자열: single quotes ''

// bad
const name = "Capt. Janeway";

// bad - template literals:  줄바꿈, interpolation 일때 사용
const name = `Capt. Janeway`;

// good
const name = 'Capt. Janeway';
1
2
3
4
5
6
7
8

# 100 자가 넘는 문자열: \,+ 으로 끊지 말것.

  • 끊어진 문자열 : 작업하기가 힘들고 코드를 검색하기 어렵게 만든다.
// 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.';
1
2
3
4
5
6
7
8
9
10
11
12
13

# 계산된 문자열을 만들 때 : 탬플릿 스트링 > +

  • Template 스트링
    • 가독성
    • 줄 바꿈 -> 엔터
    • interpolation {something} ( 블록이 아님)
// 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}?`;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 문자열 eval() 사용 말것 : 많은 취약점

# 문자열 escape characters : Backslashes -> template string

  • Backslashes 은 가독성 저하
  • template string 으로 backslash 를 줄일 수 있다.
// bad
const foo = '\'this\' \i\s \"quoted\"';

// good
const foo = '\'this\' is "quoted"';
const foo = `my name is '${name}'`;
1
2
3
4
5
6

# Functions

# 이름있는 함수 표현식 > 함수 선언식

  • 함수 선언식
    • 함수 선언이 호이스트된다. (TODO airbnb 스타일 가이드에서의 호이스팅의 의미가 다른가?)
    • 파일에서 함수가 정의되기 전에 함수를 참조할 수 있다
    • 가독성과 유지 보수성을 해친다.
    • 함수의 정의가 파일의 나머지 부분을 이해하는 데 방해가 될만큼 충분히 크거나 복잡하다면 -> 모듈로 추출
  • 이름있는 함수 표현식
    • This eliminates any assumptions made about the Error’s call stack.
    • 에러 콜스택에서의 가정(유추해야 하는일)을 없앤다? 에러를 더 잘찾아낼 수 있다라는 의미로 해석.
// bad
function foo() {
  // ...
}

// bad
const foo = function () {
  // ...
};

// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo() {
  // ...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# IIFE: parentheses 으로 감싸기

  • 모듈 시스템에서의 IIFE 는 거의 사용되지 않는다.
// immediately-invoked function expression (IIFE)
(function () {
  console.log('Welcome to the Internet. Please follow me.');
}());
1
2
3
4

# 비함수블럭에서 함수 선언 X -> 변수에 함수 할당.

  • non-function block : if, while, etc
  • 브라우저마다 비함수블럭에서의 함수블럭 해석을 다르게 한다.

# ECMA-262, block : statements(명령문), 함수선언 : not statements

// bad
if (currentUser) {
  function test() {
    console.log('Nope.');
  }
}

// good
let test;
if (currentUser) {
  test = () => {
    console.log('Yup.');
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 매개변수에 arguments 사용 X

  • 함수의 arguments 프로퍼티보다 매개변수의 arguments 가 우선시 된다.
// bad
function foo(name, options, arguments) {
  // ...
}

// good
function foo(name, options, args) {
  // ...
}
1
2
3
4
5
6
7
8
9

# rest syntax ... > arguments X

  • 함수에서 arguments 은 절대 사용하지 말것.
  • 나머지 인수는 유사배열이 아닌 실제 배열이다.
// bad
function concatenateAll() {
  const args = Array.prototype.slice.call(arguments);
  return args.join('');
}

// good
function concatenateAll(...args) {
  return args.join('');
}
1
2
3
4
5
6
7
8
9
10

# default parameter syntax > 함수 인자 변경

// really bad
function handleThings(opts) {
  // 절대 함수의 인자를 변경해선 안된다.
  // opts 가 거짓이면 원하는 객체로 설정되지만 미묘한 버그가 발생할 수 있습니다.
  opts = opts || {};
  // ...
}

// still bad
function handleThings(opts) {
  if (opts === void 0) {
    opts = {};
  }
  // ...
}

// good
function handleThings(opts = {}) {
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 사이드 이펙트를 유발하는 default parameter 사용 말것.

var b = 1;
// bad
function count(a = b++) {
  console.log(a);
}
count();  // 1
count();  // 2
count(3); // 3
count();  // 3
1
2
3
4
5
6
7
8
9

# default parameters 는 마지막에 적는다.

// bad
function handleThings(opts = {}, name) {
  // ...
}

// good
function handleThings(name, opts = {}) {
  // ...
}
1
2
3
4
5
6
7
8
9

# 함수 객체 생성 : 함수 생성자 X

  • 생성자로 함수 생성
    • 문자열을 'eval()' 와 유사하게 평가한다. (TODO 'return a + b' 이 eval 와 유사하게 평가될 수 있다는 의미인가?)
    • 취약점
// bad
var add = new Function('a', 'b', 'return a + b');

// still bad
var subtract = Function('a', 'b', 'return a - b');
1
2
3
4
5

# 함수 시그니처의 space 유지

  • 함수시그니처
    • functions 그리고 methods 의 입력과 출력을 정의
  • 일관성
  • 이름 추가/제거 -> 공백 추가/제거 의 동작을 안해도됨
// bad
const f = function(){};
const g = function (){};
const h = function() {};
const a = function a() {};

// good
const x = function () {};
const y = function a() {};
1
2
3
4
5
6
7
8
9

# 매개 변수로 전달 된 객체 조작 금지

  • 함수의 (original)caller 에게 변수 부작용의 가능성.
// bad
function f1(obj) {
  obj.key = 1;
}

// good
function f2(obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}
1
2
3
4
5
6
7
8
9

# 매개변수 재할당 금지

  • 매개변수 재할당
    • arguments 객체 접근 -> 예기치 않은 행동 발생
    • 최적화 이슈, especially in V8.
// 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) {
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 가변 함수 : spread operator ... > apply

  • variadic functions (가변 함수)
    • 가변 개수의 인수 허용
  • apply 메서드로 context(this) 를 제공해 줄 필요 없어 깨끗하다.
  • apply 메서드를 한번 더 적용하기 힘들다.
// 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]);
1
2
3
4
5
6
7
8
9
10
11
12
13

# 함수의 여러줄의 시그니쳐/호출 : 들여쓰기, 한줄에 하나씩

// bad
function foo(bar,
             baz,
             quux) {
  // ...
}

// good
function foo(
  bar,
  baz,
  quux,
) {
  // ...
}

// bad
console.log(foo,
  bar,
  baz);

// good
console.log(
  foo,
  bar,
  baz,
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Arrow Functions

# 익명 함수: 화살표함수 사용

  • 익명 함수를 사용해야 할 때 중 하나는 인라인 콜백(inline callback)을 사용할 때 이다.
  • 화살표 함수의 함수의 실행 문맥(this)은 함수를 호출한 곳의 컨텍스트(this) 를 이어받을 수 있다.
    • 만약 function 키워드를 사용한 익명함수를 인라인 콜백에서 썼을 경우의 this 는 실행컨텍스트를 예측하기가 어렵다.
  • 화살표 함수는 간결하다.
  • 만약 인라인 콜백이 복잡하여 졌을 때, 콜백의 로직을 함수 외부에 선언하는 작업이 필요해 질 것이다.
    • 이 때 외부에서, reference 에 함수 표현식을 할당하면, 인라인 콜백 대신 reference 로 대신할 수 있다.
// 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;
});
1
2
3
4
5
6
7
8
9
10
11

# 명령문이 1개인 화살표 함수 : 중괄호 생략 -> 암시적 반환

  • 중괄호를 사용해야 하는경우 반환을 하기 위해선 return 문을 써야 한다.
  • Syntactic sugar : 가독성
// 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 + 1}.`);

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

// good : obj 를 중괄호 없이 리턴하고 싶을땐, obj 를 () 으로 감싼다. 
[1, 2, 3].map((number, index) => ({
  [index]: number,
}));

// 일반함수는 암시적 리턴이 없다. side effect 가 있을 수 있다? 
function foo(callback) {
  const val = callback();
  if (val === true) {
    // Do something if callback returns true
  }
}

let bool = false;

// bad , 'bool = true' 명령문의 결과값을 반환할 의도가 없기 때문에 중괄호 생략으로 한줄로 작성하는 것은 부적절 하다. 
foo(() => bool = true);

// good
foo(() => {
  bool = true;
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 여러줄로 이뤄진 1개의 명령문의 암시적 반환: () 으로 감싼다.

  • 함수의 시작과 끝을 명확히 할 수 있다.
// bad
['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod,
  )
);

// good
['get', 'post', 'put'].map((httpMethod) => (
  Object.prototype.hasOwnProperty.call(
    httpMagicObjectWithAVeryLongName,
    httpMethod,
  )
));
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 화살표 함수의 인자: 항상 () 로 감싸기

  • 명확성, 일관성
  • 새로운 인자를 첨삭할 때 () 의 변동을 줄여준다.
    • x -> (x, y) , 혹은 그 반대를 하면 () 의 변동이 있다.
// bad
[1, 2, 3].map(x => x * x);

// good
[1, 2, 3].map((x) => x * x);

// bad
[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!`
));

// 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;
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 화살표함수의 (=>) 와 비교 연산자 (<=, >=) 의 혼동 : 명령문을 () 로 감싼다.

// 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;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 암시적 반환할 때, body 의 위치

// bad
(foo) =>
  bar;

(foo) =>
  (bar);

// good
(foo) => bar;
(foo) => (bar);
(foo) => (
   bar
)
1
2
3
4
5
6
7
8
9
10
11
12
13

# Classes & Constructors

# 클래스 사용 > 프로토타입 직접조작

  • 클래스 문법은 더 간결하고, 쉽다.
// 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;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 상속: extends 사용

  • extends
    • instanceof를 파괴하지 않고(?TODO) 프로포타입 상속을 위한 내장 방법
// 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];
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 클래스 메서드 체이닝: 메서드가 this 를 return

// 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()
  .setHeight(20);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 클래스의 커스텀 toString() 메서드

  • 사용해도 되지만 성공적으로 작동하거나 사이드 이펙트가 없는지 확인해야 한다.
class Jedi {
  constructor(options = {}) {
    this.name = options.name || 'no name';
  }

  getName() {
    return this.name;
  }

  toString() {
    return `Jedi - ${this.getName()}`;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# default 생성자

  • 빈 constructor 는 생략해도 된다.
  • 빈 생성자를 가지는 클래스를 상속하는 클래스에서, 또한 빈 생성자를 가지고 있을 땐 constructor 를 써주지 않아도 된다.
// bad
class Jedi {
  constructor() {}

  getName() {
    return this.name;
  }
}

// bad
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
  }
}

class Jedi {
  getName() {
    return this.name;
  }
}

class Rey extends Jedi { }

// good
class Rey extends Jedi {
  constructor(...args) {
    super(...args);
    this.name = 'Rey';
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 중복된 클래스 멤버를 가지는 클래스 X

  • 중복된 클래스 멤버 선언은. 클래스 멤버를 사용할 때 마지막 것을 선택한다.
  • 중복된 클래스 멤버를 가질 수 있는 것은 버그이다.
// bad
class Foo {
  bar() { return 1; }
  bar() { return 2; }
}

// good
class Foo {
  bar() { return 1; }
}

// good
class Foo {
  bar() { return 2; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 클래스 메서드에서 this 를 사용하지 않을 경우 : static 메서드 사용

// bad
class Foo {
  bar() {
    console.log('bar');
  }
}

// good - this 를 사용할 경우 -> 일반 메서드
class Foo {
  bar() {
    console.log(this.bar);
  }
}

// good - constructor 를 생략할 수 있다.
class Foo {
  constructor() {
    // ...
  }
}

// good - this 를 사용하지 않은 경우 -> static methods
class Foo {
  static bar() {
    console.log('bar');
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Modules

# (import/export) > non-standard 모듈 시스템.

  • (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;
1
2
3
4
5
6
7
8
9
10
11

# * wildcard import X

  • default Export 인지 named export 인지 헷갈릴 수 있다.
// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';

// good
import AirbnbStyleGuide from './AirbnbStyleGuide';
1
2
3
4
5

# from 으로 Import 후 직접적인 export X

  • 간결하지만, export / import 의 일관성을 위해 분리한다.
// bad
// filename es6.js
export { es6 as default } from './AirbnbStyleGuide';

// good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;
1
2
3
4
5
6
7
8

# 하나의 path : 하나의 import

  • 하나의 path 에서 여러개의 import 를 쓴다면 유지보수가 어렵다.
// 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';
1
2
3
4
5
6
7
8
9
10
11
12
13

# mutable: export X

  • 특수한 경우를 제외하고 상수 reference 만 export 한다.
// bad
let foo = 3;
export { foo };

// good
const foo = 3;
export { foo };
1
2
3
4
5
6
7

# 모듈에 export 가 하나만 있을 때: export default > named export

  • 파일에 하나의 export 를 권장
    • 가독성, 유지보수에 낫다.
// bad
export function foo() {}

// good
export default function foo() {}
1
2
3
4
5

# 모든 import 는 non-import 명령문 위에 두기

  • import 는 호이스팅 된다.
    • 호이스팅이 되는 것들은 아래에 두었을때 문제가 발생할 가능성이 있다는 말?
// bad
import foo from 'foo';
foo.init();

import bar from 'bar';

// good
import foo from 'foo';
import bar from 'bar';

foo.init();
1
2
3
4
5
6
7
8
9
10
11

# 여러줄의 imports : 들여쓰기(indent)

// bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';

// good
import {
  longNameA,
  longNameB,
  longNameC,
  longNameD,
  longNameE,
} from 'path';
1
2
3
4
5
6
7
8
9
10
11

# import 문: Webpack loader 구문 X

  • import 에서 webpack 구문을 사용하면 코드를 모듈 번들러에 연결한다 (TODO 웹팩에 대한 지식 부족..)
  • 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';
1
2
3
4
5
6
7

# import/extensions : 자바스크립트 파일이름 extensions X

  • extensions 포함의 문제점.
    • 리팩토링이 금지된다.
      • (? TODO .js -> .jsx .. 등 js 관련 확장자를 다 수정해 줘야하는 문제점?)
// bad
import foo from './foo.js';
import bar from './bar.jsx';
import baz from './baz/index.jsx';

// good
import foo from './foo';
import bar from './bar';
import baz from './baz';
1
2
3
4
5
6
7
8
9

# Iterators and Generators

# 이터레이터 사용: 고차 함수 > 루프 for-in / for-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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 현재 제너레이터는 ES5 트랜스파일링이 잘 되지 않는다.

# 제너레이터의 * 연산자의 적절한 공백 : function* foo()

  • 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* () {
  // ...
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

# Properties

# 프로퍼티 접근: dot 표기법 사용

const luke = {
  jedi: true,
  age: 28,
};

// bad
const isJedi = luke['jedi'];

// good
const isJedi = luke.jedi;
1
2
3
4
5
6
7
8
9
10

# 변수로 프로퍼티 접근 : [] 사용

const luke = {
  jedi: true,
  age: 28,
};

function getProp(prop) {
  return luke[prop];
}

const isJedi = getProp('jedi');
1
2
3
4
5
6
7
8
9
10

# 지수 연산 : ** > Math.pow

// bad
const binary = Math.pow(2, 10);

// good
const binary = 2 ** 10;
1
2
3
4
5

# Variables

# 변수 선언 : 항상 const / let

  • const / let
    • 전역 변수가 생성되지 않는다.
    • 전역 변수 공간을 어지럽히면 안된다.
// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();
1
2
3
4
5

# 하나의 변수 선언/할당 : 하나의 const/let

  • 새로 변수를 선언하고 추가할 때 더 쉽다.
  • 하나의 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';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# const/ let 의 그룹화

  • 그룹화는, 나중에 이전에 할당 된 변수 중 하나에 따라 변수를 할당해야 할 때 유용합니다. (?TODO)
// 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 합리적인 위치에 변수할당문 사용

  • 필요한 위치 근처에 둔다.
  • let/const 은 block 스코프 이기 때문임.
// 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 변수 할당문의 체이닝 X

  • 변수 할당문의 체이닝은 암묵적으로 글로벌 변수를 만들어 낸다.
// bad
(function example() {
  let a = b = c = 1;
  // let a = ( b = ( c = 1 ) );
  // let 은 a 에게만 적용된다. 
  // b 와 c 는 전역 변수가 된다.
}());

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

//  `const` 도 마찬가지.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 단항 증감 (++, --) X

  • 단항 증감은 세미콜론 자동 삽입의 대상이 된다.
    • silent errors 의 원인이 될 수 있다.
  • num++ / num ++ 보다 num += 1 이 더 바람직하다.
  • 단항 증감을 사용하지 않으면,
    • pre-incrementing/pre-decrementing 실수를 방지할 수 있다.
// 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# = 할당문 전후로 줄바꿈 X, 줄바꿈이 필요하다면 () 사용

  • 할당문 전후로 줄바꿈이 발생하면 난독화를 일으킬 수 있다.
// bad
const foo =
  superLongLongLongLongLongLongLongLongFunctionName();

// bad
const foo
  = 'superLongLongLongLongLongLongLongLongString';

// good
const foo = (
  superLongLongLongLongLongLongLongLongFunctionName()
);

// good
const foo = 'superLongLongLongLongLongLongLongLongString';
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 사용하지 않은 변수는 비허용된다.

  • 선언되었지만 사용되지 않은 변수는 리팩토링이 완전하게 되지 않았다는 의미이다.
  • 코드 공간을 불필요하게 차지함.
  • 읽는 사람에게 혼동을 줌.
// bad

var some_unused_var = 42;

// y 는 쓰여지기만 했지 사용되지 않았다.
var y = 10;
y = 5;

// 사용되지 않은 변수
var z = 0;
z = z + 1;

// 사용되지 않은 함수
function getX(x, y) {
    return x;
}

// good

function getXPlusY(x, y) {
  return x + y;
}

var x = 1;
var y = a + 2;

alert(getXPlusY(x, y));

// 나머지 프로퍼티 연산자: 객체에서 특정 키를 의도적으로 빠뜨리고 추출하는 방법이다. 
// 'type' 은 사용하지 않아도 괜찮다.
var { type, ...coords } = data;
// 'coords' 는 data 객체에서 'type' 프로퍼티가 빠진 객체다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# Hoisting

# var, const/let 선언의 호이스팅:

  • var
    • 선언문은 가장 가까운 함수 스코프의 최상단으로 호이스팅 된다.
    • 할당문은 호이스팅 되지 않는다.
  • const/let
    • 선언에는 Temporal Dead Zones (TDZ) 가 존재한다.
    • typeof 는 안전하지 않다. (예시 참고)
// 아래 함수는 참조 에러를 일으킨다 (notDefined 라는 전역 변수가 존재하지 않을 때)
function example() {
  console.log(notDefined); // => throws a ReferenceError
}

// declaredButNotAssigned 는 호이스팅이 일어나기 때문에 사용가능하다.
// Note: 하지만 true 할당은 호이스팅 되지 않음.
function example() {
  console.log(declaredButNotAssigned); // => undefined
  var declaredButNotAssigned = true;
}

// 인터프리터는 변수의 선언을 스코프의 최상단으로 호이스팅 한다.
function example() {
  let declaredButNotAssigned;
  console.log(declaredButNotAssigned); // => undefined
  declaredButNotAssigned = true;
}

// const / let 의 예시
function example() {
  console.log(declaredButNotAssigned); // => throws a ReferenceError
  console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
  const declaredButNotAssigned = true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 14.2 익명함수 표현식의 호이스팅 : 변수 이름 O, 함수할당 X

function example() {
  console.log(anonymous); // => undefined

  anonymous(); // => TypeError anonymous is not a function

  var anonymous = function () {
    console.log('anonymous function expression');
  };
}
1
2
3
4
5
6
7
8
9

# 이름있는 함수 표현식의 호이스팅: 변수이름 O, 함수이름과 body X

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');
  };
}

// 할당된 변수이름과 함수 표현식의 이름기 같은 경우.
function example() {
  console.log(named); // => undefined

  named(); // => TypeError named is not a function

  var named = function named() {
    console.log('named');
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 함수 선언문의 호이스팅: 함수 이름, body O

function example() {
  superPower(); // => Flying

  function superPower() {
    console.log('Flying');
  }
}
1
2
3
4
5
6
7

# Comparison Operators & Equality

# (===) (!==) > (==)

# Conditional statements

if 와 같은 조건문

  • ToBoolean 의 추상 메서드로 강제변환을 사용하여 식을 평가한다.
  • 식을 평가하는 규칙은 다음과 같다.
    • Objects -> true
    • Undefined -> false
    • Null -> false
    • Booleans -> true / false
    • Numbers
      • false : +0, -0, NaN
      • otherwise true
    • Strings
      • false : empty string ''
      • otherwise true
if ([0] && []) {
  // true
  // 배열은 객체이다. 빈객체도 객체임
  // 객체는 항상 true 
}
1
2
3
4
5

# reference: boolean -> 축약 , 문자열/숫자 -> 명시적 비교

// bad
if (isValid === true) {
  // ...
}

// good
if (isValid) {
  // ...
}

// bad
if (name) {
  // ...
}

// good
if (name !== '') {
  // ...
}

// bad
if (collection.length) {
  // ...
}

// good
if (collection.length > 0) {
  // ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# Truth Equality and JavaScript by Angus Croll.🔗

# 어휘 선언이 있는 case/default 문 : { } braces 사용

  • Lexical declarations [어휘 선언]
    • let, const, function, and class
    • 스위치 블록 스코프가 존재한다.
    • case/default 에 { } 가 없다면 여러 case 절에서 동일한 것을 정의하려고 할 때 문제가 발생함.
// bad
switch (foo) {
  case 1:
    let x = 1;
    break;
  case 2:
    const y = 2;
    break;
  case 3:
    function f() {
      // ...
    }
    break;
  default:
    class C {}
}
switch (1) {
  case 1:
    let v ;
    break;
  case 2:
    let v;  // Uncaught SyntaxError: Identifier 'v' has already been declared
    break;
  case 3:
    function f() {
      // ...
    }
    break;
  default:
    class C {}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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 {}
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 삼항의 중첩 X : 기본적으로 단일행 표현식

// 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 불필요한 삼항 표현식 X

// 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;
1
2
3
4
5
6
7
8
9

# 혼합 연산자 : () 로 감싸기

  • 혼합연산자는 우선순위가 모호할 수 있다.
  • +, -, ** 는 예외
  • /, * 는 모호 하다.
// bad
const foo = a && b < 0 || c > 0 || d + 1 === 0;

// bad
const bar = a ** b - 5 % d;

// bad
// (a || b) && c 와 혼동될 수 있다.
if (a || b && c) {
  return d;
}

// bad
const bar = a + b / c * 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# if/else 여러줄 블록 : if 의 닫기 } 와 같은 줄에 else 두기

// bad
if (test) {
  thing1();
  thing2();
}
else {
  thing3();
}

// good
if (test) {
  thing1();
  thing2();
} else {
  thing3();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# return 을 포함하는 if 블록 + else/else if

  • return 을 포함하는 if 블록
    • 후속 else 불필요
    • 후속 else if 의 리턴 -> if 리턴으로 분리
// bad
function foo() {
  if (x) {
    return x;
  } else {
    return y;
  }
}
// good
function foo() {
  if (x) {
    return x;
  }

  return y;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// bad
function cats() {
  if (x) {
    return x;
  } else if (y) {
    return y;
  }
}
// good
function cats() {
  if (x) {
    return x;
  }

  if (y) {
    return y;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// bad
function dogs() {
  if (x) {
    return x;
  } else {
    if (y) {
      return y;
    }
  }
}

// good
function dogs(x) {
  if (x) {
    if (z) {
      return y;
    }
  } else {
    return z;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Control Statements

  • if, while etc

# 제어문의 아주 긴 조건 : 줄바꿈, 논리연산은 새 줄에 시작

  • 연산을 새 줄에 시작하는 것
    • 메서드 체이닝 패턴
    • 가독성
// 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();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 제어문 > 선택 연산자

// bad
!isRunning && startRunning();

// good
if (!isRunning) {
  startRunning();
}
1
2
3
4
5
6
7

# 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# // : 한줄 주석

  • 주석의 대상 위에 한줄로 표기
  • 주석 위에는 한 줄 비우기
    • 주석이 블록이 첫줄이 아닌경우 제외
// 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 주석의 시작은 항상 공백

  • 가독성
// 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# FIXME / TODO

  • 실행 가능한 주석
  • FIXME: 재검토해야 할 문제를 지적
  • TODO: 구현해야하는 문제
class Calculator extends Abacus {
  constructor() {
    super();

    // FIXME: 여기서 전역을 사용해서는 안됩니다.
    total = 0;
  }
}
1
2
3
4
5
6
7
8
class Calculator extends Abacus {
  constructor() {
    super();

    // TODO: total은 옵션 매개 변수로 구성 할 수 있어야합니다.
    this.total = 0;
  }
}
1
2
3
4
5
6
7
8

# Whitespace

# 들여쓰기 indent : 2 space character

// bad
function foo() {
∙∙∙∙let name;
}

// bad
function bar() {let name;
}

// good
function baz() {
∙∙let name;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# space-before-blocks: 여는 { 의 앞에 공백 하나

// 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',
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# keyword-spacing : 제어문과 함수의 호출/선언

  • 제어문 : 여는 ( 앞에 공백 하나
  • 함수 호출/선언: 여는 ( 앞에 공백 X
// bad
if(isJedi) {
  fight ();
}

// good
if (isJedi) {
  fight();
}

// bad
function fight () {
  console.log ('Swooosh!');
}

// good
function fight() {
  console.log('Swooosh!');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# space-infix-ops : 연산자 사이 공백

// bad
const x=y+5;

// good
const x = y + 5;
1
2
3
4
5

# eol-last : 파일 마지막 - 하나의 newline(개행) 문자로 끝내기

  • 비어 있지 않은 파일의 후행 줄 바꿈은 일반적인 UNIX 관용구입니다.
  • 후행 줄 바꿈의 이점은 쉘 프롬프트를 방해하지 않고 파일 및 출력 파일을 터미널에 연결하거나 추가하는 기능을 포함합니다.
// bad
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;
1
2
3
4
// bad
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;
1
2
3
4
5
import { es6 } from './AirbnbStyleGuide';
  // ...
export default es6;
1
2
3

# newline-per-chained-call 체이닝 마다 개행

  • 긴 메서드 체이닝 (2개이상)
    • 들여쓰기
    • . dot 으로 시작하기
      • . 으로 시작하는 것 새로운 문이 아닌 메서드 호출이라는 것을 강조할 수 있다.
// 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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# no-whitespace-before-property 🔗 프로퍼티 앞에 공백 X

  • javascript 는 객체와 속성 사이의 공백을 허용한다.
    • 가독성을 저하하고 오류를 유발할 수 있다.
foo. bar .baz . quz
1
  • 점 주위 (), [] 괄호 앞에 공백을 쓰지 않아야 된다.
  • 체이닝의 개행에서만 허용 한다.
foo
  .bar()
  .baz()
  .qux()
1
2
3
4
/*eslint no-whitespace-before-property: "error"*/
foo [bar] // foo.bar

foo. bar  // foo[bar]

foo .bar  // foo[ bar ]

foo. bar. baz // foo.bar.baz

foo. bar() // foo
  .baz()  //  .bar().baz()

foo
  .bar(). baz()

/*
foo
  .bar()
  .baz()

foo.
  bar().
  baz()
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 블록 다음/ 다음 statement 앞 : 빈 줄

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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# padded-blocks: 블록을 빈 줄로 채우지 말것

// 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# no-multiple-empty-lines: 멀티라인 빈줄 X

  • 공백은 코드의 논리적 섹션을 분리하는데 유용하지만 과도한 공백은 화면을 더 많이 차지 한다.
  • 코드를 읽을 때 필요한 스크롤을 줄이는 것을 목표로 한다.
// bad
class Person {
  constructor(fullName, email, birthday) {
    this.fullName = fullName;


    this.email = email;


    this.setAge(birthday);
  }


  setAge(birthday) {
    const today = new Date();


    const age = this.getAge(today, birthday);


    this.age = age;
  }


  getAge(today, birthday) {
    // ..
  }
}

// good
class Person {
  constructor(fullName, email, birthday) {
    this.fullName = fullName;
    this.email = email;
    this.setAge(birthday);
  }

  setAge(birthday) {
    const today = new Date();
    const age = getAge(today, birthday);
    this.age = age;
  }

  getAge(today, birthday) {
    // ..
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# space-in-parens : ( ) 안에 공백 X

// 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
foo( 'bar' );
var x = ( 1 + 2 ) * 3;

// good
foo('bar');
var x = (1 + 2) * 3;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# array-bracket-spacing : [ ] 안에 공백 X

// bad
const foo = [ 1, 2, 3 ];
console.log(foo[ 0 ]);

// good
const foo = [1, 2, 3];
console.log(foo[0]);
1
2
3
4
5
6
7

# object-curly-spacing : { } 안에 공백 O

// bad
const foo = {clark: 'kent'};

// good
const foo = { clark: 'kent' };
1
2
3
4
5

# 한줄에 100 자(공백 포함) 이상 X

// 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.'));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# block-spacing : { 와 } 가 같은줄 : braces 내부 전후로 공백

// bad
function foo() {return true;}
if (foo) { bar = 0;}

// good
function foo() { return true; }
if (foo) { bar = 0; }
1
2
3
4
5
6
7

# comma-spacing : , 전에 공백 X / , 후 공백 O

// bad
var foo = 1,bar = 2;
var arr = [1 , 2];

// good
var foo = 1, bar = 2;
var arr = [1, 2];
1
2
3
4
5
6
7

# computed-property-spacing : 공백의 일관성

  • 계산된 속성 : 공백을 넣거나 빼거나 둘중 하나로 통일해야 한다.
// bad
obj[foo ]
obj[ 'foo']
var x = {[ b ]: a}
obj[foo[ bar ]]

// good
obj[foo]
obj['foo']
var x = { [b]: a }
obj[foo[bar]]
1
2
3
4
5
6
7
8
9
10
11

# func-call-spacing : 함수 호출 공백 X

// bad
func ();

func
();

// good
func();
1
2
3
4
5
6
7
8

# key-spacing : 키 공백

  • 개체 리터럴 속성에서 키와 값 사이의 간격
    • 키는 붙이고 value 는 띄운다.
// bad
var obj = { foo : 42 };
var obj2 = { foo:42 };

// good
var obj = { foo: 42 };
1
2
3
4
5
6

# no-trailing-spaces : 줄 끝 후행 공백 X

  • 후행 공백 : 공백, 탭 및 기타 유니 코드 공백 문자
  • 소스 제어 시스템에서 diff 로 플래그가 지정되는 문제점.
// bad
var foo = 0;//•••••
var baz = 5;//••

// good
var foo = 0;
var baz = 5;
1
2
3
4
5
6
7

# no-multiple-empty-lines : 빈 멀티라인 X

  • 오직 파일 끝 한줄의 개행 문자만 허용됨.
  • 파일 시작에 개행 문자 X
// bad - 빈 멀티라인
var x = 1;


var y = 2;

// bad - 파일끝에 두 줄 이상의 개행문자
var x = 1;
var y = 2;


// bad - 파일 시작에 하나 이상의 개행문자

var x = 1;
var y = 2;

// good
var x = 1;
var y = 2;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Commas

# comma-style: 컴마로 시작 X

// 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',
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# comma-dangle : 마지막행 쉼표 -> 깨끗한 diff

  • 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'],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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 (rest 뒤에는 comma 가 없어야 한다)
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
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# Semicolons

# semi 로 명시적으로 명령문 종료

  • JavaScript 는 각 문 끝에 세미콜론이 필요하지 않음
  • 자동 세미콜론 삽입 (ASI): 대부분 JS 엔진, 세미콜론 위치 판단 -> 자동 추가
  • ASI 몇 가지 특이한 동작 -> 잘못 해석하면 코드가 깨진다.
  • ASI 의 시기를 알고, ESLint 가 이러한 잠재적으로 예상치 못한 경우로부터 코드를 보호하도록하는 것이 가장 좋습니다.
    • \n 문자는 아래에 해당하지 않을 때 세미콜론을 붙인다.
      1. 닫히지 않은 괄호 : (, [ , { paren, array literal, or object literal
      2. ., , 로 끝남
      3. 다음 토큰을 증가/감소 하는 --/++
      4. { 가 없는 : for(), while(), do, if(), else:
      5. 다음 줄이 [, (, +, *, /,-, ,, ., 으로 시작할 때
      6. some other binary operator that can only be found between two tokens in a single expression.
        • single expression 에서, 두 토큰 사이의 이항 연산자(?)
// 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 - ASI, return 이 단독줄에 있을 때 `undefined` 를 반환함.
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';
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# Type Casting & Coercion

타입 캐스팅 & 강제 형변환

# 명령문의 시작부분에서 강제 형변환 하기

# no-new-wrappers : Strings

  1. 원시형 래퍼 타입 : String, Number, Boolean 사용

    • 메서드와 같은 객체와 유사한 기능을 제공
    • var text = "Hello world".substring(2);
    • 연관된 래퍼 유형의 객체가 생성 된 다음 소멸된다.
  2. new wrapper instance

    var stringObject = new String("Hello world");
    var numberObject = new Number(33);
    var booleanObject = new Boolean(false);
    
    1
    2
    3
  3. new wrapper String, Number, Boolean 를 사용하지 않는 것에 대한 사례

    var stringObject = new String("Hello world");
    console.log(typeof stringObject);       // "object"
    
    var text = "Hello world";
    console.log(typeof text);               // "string"
    
    var booleanObject = new Boolean(false);
    if (booleanObject) {    // all objects are truthy!
        console.log("This executes");
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
// => 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);
1
2
3
4
5
6
7
8
9
10
11
12
13

# no-new-wrappers : Numbers

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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 성능 Bitshift > parseInt

// good
/**
 * parseInt 가 코드상에서 느리게 작동한다면 
 * 문자열 -> 숫자 강제 변환 시 Bitshifting 가 많이 빠르다.
 */
const val = inputValue >> 0;
1
2
3
4
5
6

# bitshift 주의점

  • Numbers 는 64 bit 값
  • bitshift 는 32 bit integer 를 리턴함.
    • 32 bit Int, 2,147,483,647 보다 더 큰 값에서 예상치 못한 값으로 변환됌.
2147483647 >> 0; // => 2147483647
2147483648 >> 0; // => -2147483648
2147483649 >> 0; // => -2147483647
1
2
3

# no-new-wrappers : Booleans

const age = 0;

// bad
const hasAge = new Boolean(age);

// good
const hasAge = Boolean(age);

// best
const hasAge = !!age;
1
2
3
4
5
6
7
8
9
10

# Naming Conventions

# id-length : 이름 설명 > 한글자 이름

// bad
function q() {
  // ...
}

// good
function query() {
  // ...
}
1
2
3
4
5
6
7
8
9

# camelcase : naming objects, functions, instances.

// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}

// good 
const thisIsMyObject = {};
function thisIsMyFunction() {}
1
2
3
4
5
6
7
8

# new-cap : 생성자 & 클래스 이름에만 PascalCase 사용

// 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',
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# no-underscore-dangle : 후행/선행 밑줄 X

  • 자바 스크립트는 속성이나 방법 측면에서 프라이버시 개념이 없다
  • 선행 밑줄은 "비공개"를 의미하는 일반적인 규칙이지만 실제로 이러한 속성은 완전히 공개되어 있다.
  • 이 규칙은 개발자가 변경 사항이 'breaking' 으로 간주되지 않거나 테스트가 필요하지 않다고 잘못 생각하게 만들 수 있다.
  • tl; dr : "비공개"상태를 원하는 경우 눈에 띄게 표시되지 않아야합니다.
// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
this._firstName = 'Panda';

// good
this.firstName = 'Panda';

// good, in environments where WeakMaps are available
// see https://kangax.github.io/compat-table/es6/#test-WeakMap
const firstNames = new WeakMap();
firstNames.set(this, 'Panda');
1
2
3
4
5
6
7
8
9
10
11
12

# Function#bind : 화살표 함수 > reference 에 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);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# default export as filename

// file 1 contents
class CheckBox {
  // ...
}

// PascalCase filename
export default CheckBox; 

// file 2 contents camelCase filename
export default function fortyTwo() { return 42; }

// file 3 contents camelCase filename
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# camelCase : export-default a 함수

function makeStyleGuide() {
  // ...
}

export default makeStyleGuide;
1
2
3
4
5

# PascalCase : export a 생성자 / 클래스 / singleton / 함수 라이브러리 / bare object

const AirbnbStyleGuide = {
  es6: {
  },
};

export default AirbnbStyleGuide;
1
2
3
4
5
6

# 줄임말 / 이니셜 : 모두 대문자/소문자 일치

// 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 = [
  // ...
];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# optionally uppercase : export / const / can trust it to never change

  • UPPERCASE_VARIABLES 는 프로그래머에게 변수 (및 해당 속성)가 변경되지 않도록 신뢰할 수 있음을 알려줌
  • exported 상수 외, 파일 내 상수에는 대문자를 사용하지 마십시오.
  • exported objects, 대문자를 사용하고 모든 중첩 속성이 변경되지 않도록 유지합니다.
// bad
const PRIVATE_VARIABLE = '파일 내에서 불필요하게 대문자를 사용해서는 안됩니다.';

// bad
export const THING_TO_BE_CHANGED = '분명히 대문자가 아니어야합니다';

// bad
export let REASSIGNABLE_VARIABLE = '대문자 변수와 함께 let을 사용하지 마십시오.';

// ---

// 허용되지만 의미 값을 제공하지 않습니다.
export const apiKey = 'SOMEKEY';

// 대부분의 경우 더 좋음
export const API_KEY = 'SOMEKEY';

// ---

// bad - 불필요한 대문자 키 : semantic 값인 경우에만 대문자. 
export const MAPPING = {
  KEY: 'value'
};

// good
export const MAPPING = {
  key: 'value'
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# Accessors

# 프로퍼티에 대한 접근자 함수는 불필요

# getters/setters -> unexpected side effects, test, maintain

접근자 함수 만들기

// bad
class Dragon {
  get age() {
    // ...
  }

  set age(value) {
    // ...
  }
}

// good
class Dragon {
  getAge() {
    // ...
  }

  setAge(value) {
    // ...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# isVal() or hasVal() : boolean property/method

// bad
if (!dragon.age()) {
  return false;
}

// good
if (!dragon.hasAge()) {
  return false;
}
1
2
3
4
5
6
7
8
9

# 일관성 있는 get() and set() functions

class Jedi {
  constructor(options = {}) {
    const lightsaber = options.lightsaber || 'blue';
    this.set('lightsaber', lightsaber);
  }

  set(key, val) {
    this[key] = val;
  }

  get(key) {
    return this[key];
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Events

# 이벤트에 data payload 추가 : object literal(hash) > raw value

이벤트 페이로드에 더 많은 데이터를 추가 할 수 있습니다.

// 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
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Standard Library

Standard Library 는 기능적으로 손상되었지만 레거시 이유로 남아 있는 utilities 를 가지고 있다.

# no-restricted-globals : Number.isNaN > global isNaN

  • global isNaN
    • non-numbers 를 numbers 로 강제 변환 한다.
    • NaN을 강제하는 모든 것에 대해 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
1
2
3
4
5
6
7

# no-restricted-globals : Number.isFinite > global isFinite

  • global isFinite
    • non-numbers 를 numbers 로 강제 변환 한다.
    • 유한 수로 강제되는 모든 것에 대해 true 를 반환
// bad
isFinite('2e3'); // true

// good
Number.isFinite('2e3'); // false
Number.isFinite(parseInt('2e3', 10)); // true
1
2
3
4
5
6

# Testing

function foo() {
  return true;
}
1
2
3

No, but seriously

  • 많은 작은 순수 함수를 작성, mutation 이 발생하는 위치를 최소화
  • stubs / mocks 에 주의하기 - 테스트를 더 취약하게 만들 수 있음
  • Airbnb 는 주로 mocha / jest 사용, tape : 가끔 작은 개별 모듈에서 사용됨
  • 100 % 테스트 커버리지는 도달하기가 항상 실용적이지는 않더라도 노력하기에 좋은 목표입니다.
  • 버그를 수정할 때마다 회귀 테스트를 작성하십시오. 회귀 테스트없이 수정 된 버그는 거의 확실하게 향후 다시 break 될 것입니다.

# Reference & Origin

https://github.com/airbnb/javascript

# Comments