Rev. 2.73

ECMAScript 2015(ES6)가 우후죽순처럼 활개를 치고 있습니다. Transpile(Source-to-source compiler)이 필요한 소스들은 몇 번이고 무시하려고 했지만 ES6만큼은 그러지 못했습니다. 요즘 왠만한 Node.js 프로젝트나 React에 기반을 둬서 나오는 결과물들은 이미 장악되었다고 해도 과언이 아닐정도로 많은 사람이 애용하고 있어, 원만히 소스를 읽는 데에 큰 장애가 생겨났습니다. 이런, 까막눈이 되고 말았군요. 그러던 중 때마침 Airbnb에서 ES5이었던 자바스크립트 스타일 가이드를 ES6으로 업데이트했기에 학습차 번역해 보았습니다.

1. 유형(Types)

Primitives: 원시형(Primitive type)은 그 값을 직접 조작합니다.

  • string
  • number
  • boolean
  • null
  • undefined
const foo = 1;
let bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9

Complex: 참조형(Complex type)은 참조를 통해 값을 조작합니다.

  • object
  • array
  • function
const foo = [1, 2];
const bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // => 9, 9

2. 참조(References)

모든 참조에는 const를 사용하고 var를 사용하지 않습니다.

왜죠? 참조를 다시 할당할 수 없어서, 버그로 연결되거나 이해하기 어려운 코드가 되는 것을 예방합니다.

eslint rules: prefer-const, no-const-assign.

// bad
var a = 1;
var b = 2;

// good
const a = 1;
const b = 2;

참조를 다시 할당해야 하는 경우 var 대신에 let을 사용하세요.

왜죠? var는 함수-범위(function-scoped)이고 let은 블록-범위(block-scoped)이기 때문입니다.

eslint rules: no-var.

// bad
var count = 1;
if (true) {
  count += 1;
}

// good, use the let.
let count = 1;
if (true) {
  count += 1;
}

letconst는 모두 블록-범위(block-scoped)인 것에 주의해야 합니다.

// const와 let은 선언 된 블록 안에서만 존재함. 
{
  let a = 1;
  const b = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError

3. 객체(Objects)

객체를 만들 때에는 리터럴 구문을 사용합니다.

eslint rules: no-new-object.

// bad
const item = new Object();

// good
const item = {};

코드가 브라우저에서 실행되는 경우 예약어를 키로 사용하지 마세요. 이것은 IE8에서 작동하지 않습니다. 더 알아보기. ES6 모듈과 서버 사이드에서는 사용할 수 있습니다.

// bad
const superman = {
  default: { clark: 'kent' },
  private: true,
};

// good
const superman = {
  defaults: { clark: 'kent' },
  hidden: true,
};

예약어 대신에 알기 쉬운 동의어(Readable Synonyms)를 사용하세요.

// bad
const superman = {
  class: 'alien',
};

// bad
const superman = {
  klass: 'alien',
};

// good
const superman = {
  type: 'alien',
};

동적인 속성 이름을 가진 객체를 만들 때에는 계산된 속성 이름(Computed Property Names)을 사용하세요.

왜죠? 이렇게하면 객체 속성을 한 개의 장소에서 정의 할 수 있습니다.

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,
};

메소드에 단축 구문(Object Shorthand)을 사용하세요.

eslint rules: object-shorthand.

// bad
const atom = {
  value: 1,

  addValue: function (value) {
    return atom.value + value;
  },
};

// good
const atom = {
  value: 1,

  addValue(value) {
    return atom.value + value;
  },
};

속성에 단축 구문(Object Concise)을 사용하세요.

왜죠? 표현이나 설명이 간결해지기 때문입니다.

eslint rules: object-shorthand.

const lukeSkywalker = 'Luke Skywalker';

// bad
const obj = {
  lukeSkywalker: lukeSkywalker,
};

// good
const obj = {
  lukeSkywalker,
};

속성의 단축 구문(Object Concise)은 객체 선언의 시작 부분에 무리를 지어줍니다.

왜죠? 어떤 속성이 단축 구문을 사용하고 있는지를 알기가 쉽기 때문입니다.

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,
};

속성 이름에 작은 따옴표를 사용하는 경우는 오직 잘못된 식별자(Invalid Identifiers)일 때입니다.

왜죠? 주관적으로 쉽게 읽을 수 있는 것을 항상 고민해야 합니다. 이 것은 구문이 강조되고, 수많은 JS엔진에 쉽게 최적화되어 있습니다.

eslint rules: quote-props.

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

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

4. 배열(Arrays)

배열을 만들 때 리터럴 구문을 사용하세요.

eslint rules: no-array-constructor.

// bad
const items = new Array();

// good
const items = [];

배열에 항목을 직접 대체하지 말고 Array#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 $lt; len; i++) {
  itemsCopy[i] = items[i];
}

// good
const itemsCopy = [...items];

Array-Like 객체를 배열로 변환하려면 Array#from을 사용하세요.

const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);

5. 구조화 대입(Destructuring)

여러 속성에서 객체에 접근할 때 객체 구조화 대입(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}`;
}

배열에 구조화 대입(Destructuring)을 사용하세요.

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];
}

// 호출자에 반환되는 데이터의 순서를 고려해야 함
const [left, __, top] = processInput(input);

// good
function processInput(input) {
  // 그러면 기적이 일어난다
  return { left, right, top, bottom };
}

// 호출하면서 필요한 데이터만 선택할 수 있음
const { left, right } = processInput(input);

6. 문자열(Strings)

문자열에는 작은 따옴표''를 사용하세요.

eslint rules: quotes.

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

// good
const name = 'Capt. Janeway';

100자 이상의 문자열은 여러 행을 사용하여 연결할 수 있습니다.

주의: 문자열 연결이 많으면 성능에 영향을 줄 수 있습니다. jsPerf & Discussion.

// 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.';

프로그램에서 문자열을 생성하는 경우, 문자열 연결이 아닌 템플릿 문자열(Template Strings)을 사용하세요.

왜죠? 템플릿 문자열의 문자열 완성 기능과 다중 문자열 기능을 가진 간결한 구문으로 가독성이 좋아지기 때문입니다.

eslint rules: prefer-template.

// bad
function sayHi(name) {
  return 'How are you, ' + name + '?';
}

// bad
function sayHi(name) {
  return ['How are you, ', name, '?'].join();
}

// good
function sayHi(name) {
  return `How are you, ${name}?`;
}
절대로 eval()을 사용하지 않습니다. 이것은 지금까지 수많은 취약점을 만들어 왔기 때문입니다.

7. 함수(Functions)

함수 선언 대신에 함수 표현식을 사용합니다.

왜죠? 이름이 붙은 함수 선언은 콜스택에서 쉽게 알수 있습니다. 또한 함수 선언의 몸 전체가 Hoist됩니다. 반면 함수는 참조만 Hoist됩니다. 이 규칙은 함수 부분을 항상 애로우 함수로 대체 사용할 수 있습니다.

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

// good
function foo() {
}

함수 표현식(Function expressions):

// 즉시-호출(Immediately-Invoked) 함수 표현식(IIFE)
(() => {
  console.log('Welcome to the Internet. Please follow me.');
})();

함수 이외의 블록 (ifwhile 등)에 함수를 선언하지 않습니다. 브라우저는 변수에 함수를 할당하는 처리를 할 수는 있지만, 모두 다르게 해석됩니다.

주의: ECMA-262에서 block은 statements 목록에 정의되지만, 함수 선언은 statements가 없습니다. 이 문제는 ECMA-262의 설명을 참조하세요.

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

// good
let test;
if (currentUser) {
  test = () => {
    console.log('Yup.');
  };
}

매개변수(parameter)에 arguments를 절대로 지정하지 않습니다. 이것은 함수 영역으로 전달 될 arguments객체의 참조를 덮어 써버릴 것입니다.

// bad
function nope(name, options, arguments) {
  // ...stuff...
}

// good
function yup(name, options, args) {
  // ...stuff...
}

arguments를 사용하지 않습니다. 대신 레스트(Rest) 문법인 ...을 사용하세요.

왜죠? ... 를 이용하여 여러가지 매개변수를 모두 사용할 수 있습니다. 추가로 rest 매개변수인 arguments는 Array-Like 객체가 아니라 진정한 배열(Array)입니다.

// bad
function concatenateAll() {
  const args = Array.prototype.slice.call(arguments);
  return args.join('');
}

// good
function concatenateAll(...args) {
  return args.join('');
}

함수의 매개변수를 조작하지 말고 기본 매개변수(Default Parameters)를 사용하세요.

// really bad
function handleThings(opts) {
  // 안되! 함수의 매개변수를 조작하지 않습니다. 
  // 만약 opts가 falsy 인 경우는 바란대로 객체가 설정됩니다. 
  // 그러나 미묘한 버그를 일으키는 원인이 될수도 있습니다. 
  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');

함수 시그네이쳐(Signature)에 공백을 사용합니다.

왜죠? 일관성이 좋고, 함수이름을 추가 하거나 삭제할 때 공백을 제거할 필요가 없습니다.

// bad
const f = function(){};
const g = function (){};
const h = function() {};

// good
const x = function () {};
const y = function a() {};

절대로 매개변수를 조작하지 않습니다.

왜죠? 매개변수로 전달 된 객체를 조작하는 것은 원래의 호출에 원치 않는 변수 부작용을 일으킬 수 있습니다.

eslint rules: no-param-reassign.

// bad
function f1(obj) {
  obj.key = 1;
};

// good
function f2(obj) {
  const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
};

절대로 매개변수를 다시 지정하지 않습니다.

왜죠? arguments 객체에 접근하는 경우 다시 지정된 매개변수는 예기치 않은 동작이 발생할 수 있습니다. 그리고 특히 V8 최적화에 문제가 발생할 수 있습니다.

eslint rules: no-param-reassign.

// bad
function f1(a) {
  a = 1;
}

function f2(a) {
  if (!a) { a = 1; }
}

// good
function f3(a) {
  const b = a || 1;
}

function f4(a = 1) {
}

8. 애로우 함수(Arrow Functions)

함수 표현식을 사용해야하는 경우(익명 함수와 같은), 애로우 함수(Arrow Functions)를 사용하세요.

왜죠? 애로우 함수는 함수가 실행되는 컨텍스트의 this를 가두어줍니다. 이것은 너무나 원했던 것이며 구문도 더욱 간결해집니다.

언제 쓰죠? 복잡한 함수 논리를 정의한 함수의 바깥쪽으로 이동하고 싶은 경우입니다.

eslint rules: prefer-arrow-callback, arrow-spacing.

// 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을 사용할 수 있습니다. 그렇지 않으면 return 문을 사용해야 합니다.

왜죠? 가독성이 좋아지기 때문입니다. 여러 함수가 연결되는 경우에 쉽게 읽을 수 있습니다.

언제 쓰죠? 객체를 반환하는 경우.

eslint rules: arrow-parens, arrow-body-style.

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

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

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

구문의 길이가 여러 행에 걸치는 경우 가독성을 향상시키기 위해 괄호() 안에 써주세요.

왜죠? 함수의 시작과 끝 부분을 알아보기 쉽게 합니다.

// bad
[1, 2, 3].map(number => 'As time went by, the string containing the ' +
  `${number} became much longer. So we needed to break it over multiple ` +
  'lines.'
);

// good
[1, 2, 3].map(number => (
  `As time went by, the string containing the ${number} became much ` +
  'longer. So we needed to break it over multiple lines.'
));

함수의 인수가 한 개인 경우 괄호()를 생략할 수 있습니다.

왜죠? 시각적 혼란이 덜하기 때문입니다.

eslint rules: arrow-parens.

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

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

// good
[1, 2, 3].map(number => (
  `A long string with the ${number}. It’s so long that we’ve broken it ` +
  'over multiple lines!'
));

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

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

9. 생성자(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를 사용하세요.

왜죠? 프로토타입을 상속하기 위해 내장된 방식으로 instanceof 를 파괴할 수 없기 때문입니다.

// 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()
  .setHeight(20);

조작된(Custom) toString() 메소드를 이용할 수도 있지만, 올바르게 작동 하는지, 부작용이 없는지를 꼭 확인하세요.

class Jedi {
  constructor(options = {}) {
    this.name = options.name || 'no name';
  }

  getName() {
    return this.name;
  }

  toString() {
    return `Jedi - ${this.getName()}`;
  }
}

10. 모듈(Modules)

비표준 모듈 시스템이 아니라면 항상 (import/export) 를 사용하세요. 이렇게 함으로써 원하는 모듈 시스템에 언제든지 트랜스파일(Transpile) 할 수 있습니다.

왜죠? 모듈은 곧 미래입니다. 미래를 선점하고 애용합시다.

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

와일드카드를 이용한 가져오기는 사용하지 않습니다.

왜죠? single default export인 것에 주의할 필요가 있기 때문입니다.

// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';

// good
import AirbnbStyleGuide from './AirbnbStyleGuide';

import 문에서 직접 추출(Export)하지 않습니다.

왜죠? 한개의 라인이라 간결하기는 하지만, import와 export하는 방법을 명확하게 구분함으로써 일관성을 유지할 수 있습니다.

// bad
// filename es6.js
export { es6 as default } from './airbnbStyleGuide';

// good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;

11. 이터레이터와 제너레이터(Iterators and Generators)

이터레이터(Iterators)를 사용하지 않습니다. for-of 루프 대신 map()reduce()같은 자바스크립트의 고급함수(higher-order functions)를 사용하세요.

왜죠? 이것은 불변(Immutable)의 규칙을 적용합니다. 값을 반환하는 함수를 처리하는 것이 부작용을 예측하기가 더 쉽습니다.

eslint rules: no-iterator.

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;

현재 제너레이터(Generators)는 사용하지 않는 것이 좋습니다

왜죠? ES5에서 트랜스파일(Transpile)이 올바로 작동하지 않습니다.

12. 속성(Properties)

속성에 접근하려면 점.을 사용하세요.

eslint rules: dot-notation.

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

13. 변수(Variables)

변수를 선언할 때는 항상 const를 사용하세요. 그렇지 않으면 전역 변수로 선언됩니다. 글로벌 네임 스페이스가 오염되지 않도록 캡틴 플래닛(역자주: 환경보호와 생태를 테마로 한 슈퍼히어로 애니메이션)도 경고하고 있습니다.

// bad
superPower = new SuperPower();

// good
const superPower = new SuperPower();

하나의 변수 선언에 대해 하나의 const를 사용하세요.

왜죠? 이 방법은 새로운 변수를 쉽게 추가할 수 있습니다. 또한 구분 기호의 차이에 의한 ;,로 다시금 대체하는 작업에 대해 신경쓸 필요가 없습니다.

eslint rules: one-var.

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

먼저 const를 그룹화하고 그 다음으로 let을 그룹화 하세요.

왜죠? 이전에 할당 된 변수에 따라 나중에 새로운 변수를 추가하는 경우에 유용하기 때문입니다.

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

변수를 할당을 필요로 하는 부분에서 적당한 장소에 배치해야 합니다.

왜죠? letconst는 함수 범위에는 없는 블록 범위이기 때문입니다.

// good
function () {
  test();
  console.log('doing stuff..');

  //..other stuff..

  const name = getName();

  if (name === 'test') {
    return false;
  }

  return name;
}

// bad - unnecessary function call
function (hasName) {
  const name = getName();

  if (!hasName) {
    return false;
  }

  this.setFirstName(name);

  return true;
}

// good
function (hasName) {
  if (!hasName) {
    return false;
  }

  const name = getName();
  this.setFirstName(name);

  return true;
}

14. 호이스팅(Hoisting)

var 선언은 할당이 없는 상태로 범위(Scope)의 위로 Hoist될 수 있습니다. 하지만 constlet 선언은 시간적 데드 존(Temporal Dead Zones (TDZ))이라는 새로운 개념의 혜택을 받고 있습니다. 이것은 왜 typeof가 안전하지 않은가(typeof is no longer safe)를 알고있는 것이 중요합니다.

// (notDefined가 글로벌 변수에 존재하지 않는다고 가정했을 경우)
// 이것은 잘 작동하지 않습니다. 
function example() {
  console.log(notDefined); // => throws a ReferenceError
}

// 변수를 참조하는 코드 후에 그 변수를 선언한 경우
// 변수가 Hoist되어서 작동합니다.
// 주의: `true` 값 자체는 Hoist할 수 없습니다.
function example() {
  console.log(declaredButNotAssigned); // => undefined
  var declaredButNotAssigned = true;
}

// 인터프린터는 변수 선언을 범위(Scope)의 시작부분에 Hoist합니다.
// 위의 예는 다음과 같이 다시 작성할 수 있습니다:
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;
}

익명 함수 표현식에서는 함수가 할당되기 전에 변수가 Hoist될 수 있습니다.

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

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

  var anonymous = function () {
    console.log('anonymous function expression');
  };
}

명명된 함수의 경우도 마찬가지로 변수가 Hoist될 수 있습니다. 함수이름과 함수본문은 Hoist되지 않습니다.

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

함수 선언은 함수이름과 함수본문이 Hoist됩니다.

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

  function superPower() {
    console.log('Flying');
  }
}

더 자세한 정보는 Ben CherryJavaScript Scoping & Hoisting을 참조하세요.

15. 조건식과 등가식(Comparison Operators & Equality)

==!= 보다는 ===!==를 사용하세요.

if와 같은 조건문은 ToBoolean방법에 의한 강제 형(Type) 변환으로 구분되고 항상 다음과 같은 간단한 규칙을 따릅니다:

eslint rules: eqeqeq.

  • Objectstrue로 구분됩니다.
  • Undefinedfalse로 구분됩니다.
  • Nullfalse로 구분됩니다.
  • Booleansboolean형의 값으로 구분됩니다.
  • Numberstrue로 구분됩니다. 그러나, +0, -0, 또는 NaN인 경우 false로 구분됩니다.
  • Stringstrue로 구분됩니다. 그러나, 비어있는 ''경우는 false로 구분됩니다.
if ([0]) {
  // true
  // 배열은 객체이므로 true로 구분됩니다.
}

손쉬운 방법(Shortcuts)을 사용하세요.

// bad
if (name !== '') {
  // ...stuff...
}

// good
if (name) {
  // ...stuff...
}

// bad
if (collection.length > 0) {
  // ...stuff...
}

// good
if (collection.length) {
  // ...stuff...
}

더 자세한 내용은 여기를 참조하세요. Truth Equality and JavaScript by Angus Croll.

16. 블록(Blocks)

여러 줄의 블록은 중괄호{}를 사용합니다.

// bad
if (test)
  return false;

// good
if (test) return false;

// good
if (test) {
  return false;
}

// bad
function () { return false; }

// good
function () {
  return false;
}

여러 블록에 걸친 ifelse를 사용하는 경우, elseif블록의 끝 중괄호{}와 같은 행에 두세요.

eslint rules: brace-style.

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

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

17. 주석(Comments)

여러 줄의 주석에는 /** ... */를 사용하세요. 그 안에는 설명과 모든 매개변수와 반환값에 대한 형식과 값을 표기합니다.

// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {

  // ...stuff...

  return element;
}

// good
/**
 * make() returns a new element
 * based on the passed in tag name
 *
 * @param {String} tag
 * @return {Element} element
 */
function make(tag) {

  // ...stuff...

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

문제를 지적하고 재고를 촉구하거나 문제의 해결책을 제시하는 경우 등, 주석 앞에 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;
  }
}

18. 공백(Whitespace)

탭에는 공백 2개를 설정하세요.

eslint rules: indent.

// bad
function () {
∙∙∙∙const name;
}

// bad
function () {
∙const name;
}

// good
function () {
∙∙const name;
}

중괄호{} 앞에 공백을 넣어주세요.

eslint rules: 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',
});

제어 구문(if, while 등)의 괄호() 앞에 공백을 넣어주세요. 함수 선언과 함수 호출시 인수 목록 앞에는 공백을 넣지 않습니다.

eslint rules: space-after-keywords, space-before-keywords.

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

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

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

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

연산자 사이에는 공백이 있습니다.

eslint rules: space-infix-ops.

// bad
const x=y+5;

// good
const x = y + 5;

파일의 마지막에 빈 줄을 하나 넣어주세요.

// bad
(function (global) {
  // ...stuff...
})(this);

// bad
(function (global) {
  // ...stuff...
})(this);↵
↵

// good
(function (global) {
  // ...stuff...
})(this);↵

메소드 체인이 길어지는 경우 적절히 들여쓰기(indentation) 하세요. 행이 메소드 호출이 아닌 새로운 문장임을 강조하기 위해 선두에 점.을 배치하세요.

// 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').class('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);

블록과 다음 Statement 사이에 빈 줄을 넣어주세요.

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

블록에 빈 줄을 끼워넣지 않습니다.

eslint rules: padded-blocks.

// bad
function bar() {

  console.log(foo);

}

// also bad
if (baz) {

  console.log(qux);
} else {
  console.log(foo);

}

// good
function bar() {
  console.log(foo);
}

// good
if (baz) {
  console.log(qux);
} else {
  console.log(foo);
}

괄호() 안에 공백을 추가하지 않습니다.

eslint rules: space-in-parens.

// bad
function bar( foo ) {
  return foo;
}

// good
function bar(foo) {
  return foo;
}

// bad
if ( foo ) {
  console.log(foo);
}

// good
if (foo) {
  console.log(foo);
}

대괄호[] 안에 공백을 추가하지 않습니다.

eslint rules: array-bracket-spacing.

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

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

중괄호{} 안에 공백을 추가합니다.

eslint rules: object-curly-spacing.

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

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

한 줄에 100문자(공백 포함)가 넘는 코드는 피하세요.

왜죠? 가독성과 유지 보수성을 보장합니다.

eslint rules: max-len.

// bad
const foo = 'Whatever national crop flips the window. The cartoon reverts within the screw. Whatever wizard constrains a helpful ally. The counterpart ascends!';

// bad
$.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.'));

// good
const foo = 'Whatever national crop flips the window. The cartoon reverts within the screw. ' +
  'Whatever wizard constrains a helpful ally. The counterpart ascends!';

// good
$.ajax({
  method: 'POST',
  url: 'https://airbnb.com/',
  data: { name: 'John' },
})
  .done(() => console.log('Congratulations!'))
  .fail(() => console.log('You have failed this city.'));

19. 쉼표(Commas)

쉼표로 시작: 제발 그만하세요.

eslint rules: comma-style.

// 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',
};

마지막에 쉼표: 좋습니다.

eslint rules: comma-dangle.

왜죠? 이것은 git의 diff를 깨끗하게 합니다. 또한 Babel과 같은 트랜스 컴파일러는 끝에 불필요한 쉼표를 알아서 제거합니다. 이것은 기존 브라우저에서 불필요한 쉼표 문제를 걱정할 필요가 없다는 것을 의미합니다.

// bad - git diff without trailing comma
const hero = {
     firstName: 'Florence',
-    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb graph', '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',
];

20. 세미콜론(Semicolons)

물론 사용합시다.

eslint rules: semi.

// bad
(function () {
  const name = 'Skywalker'
  return name
})()

// good
(() => {
  const name = 'Skywalker';
  return name;
})();

// good (guards against the function becoming an argument when two files with IIFEs are concatenated)
;(() => {
  const name = 'Skywalker';
  return name;
})();

Read more.

21. 형변환과 강제(Type Casting & Coercion)

문장의 시작 부분에서 형(Type)을 강제합니다.

String:

//  => this.reviewScore = 9;

// bad
const totalScore = this.reviewScore + '';

// good
const totalScore = String(this.reviewScore);

Number: Number형으로 변환하려면 parseInt를 사용하세요. 항상 형변환을 위한 기수(radix)를 인수로 전달합니다.

eslint rules: radix.

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가 병목이되고, 성능적인 이유에서 Bitshift를 사용해야 하는 경우, 무엇을(what) 왜(why)에 대한 설명을 댓글로 남겨 주세요.

// good
/**
 * parseInt가 병목이되고 있었기 때문에, 
 * Bitshift 문자열을 수치로 강제로 변환하여 
 * 성능을 향상시킵니다.
 */
const val = inputValue >> 0;

주의: Bitshift를 사용하는 경우 수치는 64-비트 값들로 표현되어 있지만, Bitshift를 연산하면 항상 32-비트 단 정밀도로 돌려 주어집니다(source). 32-비트 이상의 값을 비트 이동하면 예상치 못한 행동을 일으킬 가능성이 있습니다. Discussion. 부호있는 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);

// good
const hasAge = !!age;

22. 명명 규칙(Naming Conventions)

하나의 문자로 구성된 이름은 피하세요. 이름에서 의도를 읽을 수 있도록 해야 합니다.

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

// good
function query() {
  // ..stuff..
}

객체, 함수 인스턴스에는 camelCase(소문자로 시작)를 사용하세요.

eslint rules: camelcase.

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

// good
const thisIsMyObject = {};
function thisIsMyFunction() {}

클래스와 생성자는 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',
});

Private 속성 이름은 앞에 밑줄_을 사용하세요.

eslint rules: no-underscore-dangle.

// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';

// good
this._firstName = 'Panda';

this에 대한 참조를 저장하지 않습니다. 애로우 함수 또는 Function#bind를 사용하세요.

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

파일을 하나의 클래스로 추출(Export)할 경우 파일 이름은 클래스 이름과 정확하게 일치해야 합니다.

// file contents
class CheckBox {
  // ...
}
export default CheckBox;

// in some other file
// bad
import CheckBox from './checkBox';

// bad
import CheckBox from './check_box';

// good
import CheckBox from './CheckBox';

export-default 함수의 경우, camelCase(소문자로 시작)를 사용하세요. 파일이름은 함수이름과 동일해야 합니다.

function makeStyleGuide() {
}

export default makeStyleGuide;

싱글톤(singleton) / 함수 라이브러리(function library) / 단순한 객체(bare object)를 추출하는 경우, PascalCase(대문자로 시작)를 사용하세요.

const AirbnbStyleGuide = {
  es6: {
  }
};

export default AirbnbStyleGuide;

23. 액세서(Accessors)

속성에 대한 접근자(Accessor) 함수는 필요하지 않습니다.

접근자 함수가 필요한 경우 getVal()setVal('hello')로 하세요.

// bad
dragon.age();

// good
dragon.getAge();

// bad
dragon.age(25);

// good
dragon.setAge(25);

속성이 boolean의 경우 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];
  }
}

24. 이벤트(Events)

(DOM 이벤트, Backbone 이벤트)처럼 자신의 이벤트 페이로드 값을 전달하려면 원시값 대신 해시인수를 전달합니다. 이렇게 하면 나중에 개발자가 이벤트에 관련된 모든 핸들러를 찾아 업데이트하지 않고 이벤트 페이로드에 값을 추가할 수 있습니다. 예를 들면:

// bad
$(this).trigger('listingUpdated', listing.id);

...

$(this).on('listingUpdated', function (e, listingId) {
  // do something with listingId
});

보다 아래쪽이 더 선호됨:

// good
$(this).trigger('listingUpdated', { listingId: listing.id });

...

$(this).on('listingUpdated', function (e, data) {
  // do something with data.listingId
});

25. jQuery

jQuery 객체 변수 앞에는 $로 구분합니다.

// bad
const sidebar = $('.sidebar');

// good
const $sidebar = $('.sidebar');

// good
const $sidebarBtn = $('.sidebar-btn');

jQuery의 검색 결과를 캐시합니다.

// bad
function setSidebar() {
  $('.sidebar').hide();

  // ...stuff...

  $('.sidebar').css({
    'background-color': 'pink'
  });
}

// good
function setSidebar() {
  const $sidebar = $('.sidebar');
  $sidebar.hide();

  // ...stuff...

  $sidebar.css({
    'background-color': 'pink'
  });
}

DOM의 검색에는 $('.sidebar ul') 또는 $('.sidebar > ul')과 같은 Cascading을 사용하세요. jsPerf

jQuery 객체의 검색에는 범위가있는 find 를 사용하세요.

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

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

// good
$('.sidebar ul').hide();

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

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

26. ECMAScript 5 호환성(ECMAScript 5 Compatibility)

Kangax의 ES5 호환성 표를 참조하세요.

27. ECMAScript 6 스타일(ECMAScript 6 Styles)

이것은 ES6 명세 링크를 모아 놓은 것입니다.

28. 테스팅(Testing)

물론 해야 합니다.

function () {
  return true;
}

물론 심각하게:

  • 대부분 테스트 프레임워크를 이용하여 테스트를 작성합니다.
  • 작은 기능의 함수를 자주 쓰고 이변이 발생할 수 있는 부분을 최소화하기 위해 노력합니다.
  • stubs와 mocks에 주의하세요. 이 것들로 인해 테스트가 깨지기 쉽습니다.
  • Airbnb는 mocha를 이용하고 있습니다. 작게 분할된 개별 모듈은 tape을 사용합니다.
  • 지금은 달성할 필요가 없어도 100%의 테스트 커버리지를 목표로하는 것이 좋습니다.
  • 버그를 수정할 때 마다 회귀 테스트를 씁니다. 회귀 테스트 없는 버그 수정은 나중에 반드시 다시 출현할 것입니다.

Comments

Got something to add? You can just leave a comment.

Your Reaction Time!

captcha

avatar