1.1 기본형 : 기본형에 접근하면 해당 값에 직접 작업하게 됩니다.
const foo = 1;
let bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
Symbol과 BigInt는 충실하게 폴리필될 수 없으므로 이를 기본적으로 지원하지 않는 브라우저/환경을 타겟으로 삼을 때는 사용하면 안 됩니다.
1.2 복합 : 복합 유형에 액세스하면 해당 값에 대한 참조에서 작업하게 됩니다.
const foo = [1, 2];
const bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
2.1 변수 선언은 가급적 const 를 사용하고, var 를 사용하지 않는다. eslint: prefer-const, no-const-assign
왜? 이렇게 하면 참조를 재할당할 수 없게 되어 버그와 이해하기 어려운 코드가 생길 수 있습니다.
// bad
var a = 1;
var b = 2;
// good
const a = 1;
const b = 2;
2.2 참조를 재할당 해야한다면 var 대신 let 을 사용한다. no-var
// bad
var count = 1;
if (true) {
count += 1;
}
// good, use the let.
let count = 1;
if (true) {
count += 1;
}
2.3 let 과 const 는 선언된 블록 안에서만 존재하는 블록 스코프이다.
// const and let only exist in the blocks they are defined in.
{
let a = 1;
const b = 1;
var c = 1;
}
console.log(a); // ReferenceError
console.log(b); // ReferenceError
console.log(c); // Prints 1
a위의 코드에서 and 를 참조하면 bReferenceError가 생성되고, while에는 c숫자가 들어 있습니다. 이는 aand b가 블록 범위이고, while은 c포함하는 함수로 범위가 지정되기 때문입니다.
3.1 객체 생성을 위해 리터럴 구문을 사용하세요. eslint:no-new-object
// bad
const item = new Object();
// good
const item = {};
3.2 동적 프로퍼티명을 갖는 오브젝트를 작성할 때, 계산된 프로퍼티명(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,
};
3.3 객체 메서드 단축형을 사용하세요. eslint: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;
},
};
3.4 속성 값 단축형을 사용하세요. eslint:object-shorthand
왜? 더 짧고 설명적이기 때문입니다.
const lukeSkywalker = 'Luke Skywalker';
// bad
const obj = {
lukeSkywalker: lukeSkywalker,
};
// good
const obj = {
lukeSkywalker,
};
3.5 객체 선언의 시작부분에 단축 속성을 그룹화합니다.
왜? 어떤 속성이 단축형을 사용하는지 알아내는 것이 더 쉽기 때문입니다.
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,
};
3.6 잘못된 식별자인 속성만 인용하세요. eslint:quote-props
왜? 일반적으로 우리는 주관적으로 읽기가 더 쉽다고 생각합니다. 구문 강조가 개선되고 많은 JS 엔진에서 더 쉽게 최적화됩니다.
// bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5,
};
// good
const good = {
foo: 3,
bar: 4,
'data-blah': 5,
};
3.7 hasOwnProperty, propertyIsEnumerable, isPrototypeOf 와 같은 Object.prototype 메서드를 직접적으로 사용하지 않는다. eslint:no-prototype-builtins
왜? 이러한 메서드는 해당 객체의 속성에 의해 가려질 수 있습니다. { hasOwnProperty: false }를 고려하세요. 또는 객체가 null 객체(Object.create(null))일 수 있습니다. ES2022를 지원하는 최신 브라우저나 https://npmjs.com/object.hasown과 같은 폴리필을 사용하면 Object.hasOwn을 Object.prototype.hasOwnProperty.call의 대안으로 사용할 수도 있습니다.
// bad
console.log(object.hasOwnProperty(key));
// good
console.log(Object.prototype.hasOwnProperty.call(object, key));
// better
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
console.log(has.call(object, key));
// best
console.log(Object.hasOwn(object, key)); // only supported in browsers that support ES2022
/* or */
import has from 'has'; // https://www.npmjs.com/package/has
console.log(has(object, key));
/* or */
console.log(Object.hasOwn(object, key)); // https://www.npmjs.com/package/object.hasown
3.8 얕은 복사(shallow-copy) 객체 보다 객체 확산 구문을 선호합니다. 객체 나머지 매개변수 구문을 사용하여 특정 속성이 생략된 새 객체를 가져옵니다. eslint:prefer-object-spread
// very bad
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this
// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// good
const original = { a: 1, b: 2 };
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
4.1 Use the literal syntax for array creation. eslint: no-array-constructor
// bad
const items = new Array();
// good
const items = [];
4.2 배열에 항목을 추가하려면 직접 할당하는 대신 Array#push
를 사용하세요.Array#push
const someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
4.3 배열을 복사하려면 배열 스프레드를 사용하세요.
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
4.4 반복 가능한 객체를 배열로 변환하려면 Array.from 대신 ... 스프레드를 사용합니다.
const foo = document.querySelectorAll('.foo');
// good
const nodes = Array.from(foo);
// best
const nodes = [...foo];
4.5 배열과 유사한 객체를 배열로 변환하려면 Array.from을 사용합니다.
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };
// bad
const arr = Array.prototype.slice.call(arrLike);
// good
const arr = Array.from(arrLike);
4.6 반복 가능한 객체를 매핑할 때는 spread ... 대신 Array.from을 사용하면 중간 배열을 만들지 않아도 됩니다.
// bad
const baz = [...foo].map(bar);
// good
const baz = Array.from(foo, bar);
4.7 배열 메서드 콜백에서 return 문을 사용합니다. 함수 본문이 부작용 없이 표현식을 반환하는 단일 문으로 구성된 경우 return을 생략해도 됩니다. eslint: array-callback-return
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map((x) => x + 1);
// bad - no returned value means `acc` becomes undefined after the first iteration
[[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;
});
4.8 배열에 여러 줄이 있는 경우 배열 대괄호를 연 다음, 배열 대괄호를 닫기 전에 줄 바꿈을 사용합니다.
// 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,
];
5.1 객체의 여러 속성에 접근하고 사용할 때 객체 구조 분해를 사용하세요. eslint: prefer-destructuring
Why? 구조 분해는 해당 속성에 대한 임시 참조를 만들지 않고, 객체에 반복적으로 액세스하지 않아도 됩니다. 객체에 반복적으로 액세스하면 반복되는 코드가 더 많아지고, 더 많은 읽기가 필요하며, 실수할 가능성이 더 커집니다. 또한 객체 구조 분해는 블록에서 사용되는 객체 구조에 대한 단일 정의 사이트를 제공하므로, 무엇이 사용되는지 확인하기 위해 전체 블록을 읽을 필요가 없습니다.
// 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}`;
}
5.2 array destructuring을 사용하세요. eslint: prefer-destructuring
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
5.3 여러 반환 값에 대해 배열 분해가 아닌 객체 분해를 사용하세요.
Why? 시간이 지남에 따라 새 속성을 추가하거나 호출 사이트를 중단하지 않고도 사물의 순서를 변경할 수 있습니다.
// bad
function processInput(input) {
// then a miracle occurs
return [left, right, top, bottom];
}
// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);
// good
function processInput(input) {
// then a miracle occurs
return { left, right, top, bottom };
}
// the caller selects only the data they need
const { left, top } = processInput(input);
6.1 문자열에는 작은 따옴표를 사용하세요. eslint: quotes
// bad
const name = "Capt. Janeway";
// bad - template literals should contain interpolation or newlines
const name = `Capt. Janeway`;
// good
const name = 'Capt. Janeway';
6.2 줄이 100자를 넘게 만드는 문자열은 문자열 연결을 사용하여 여러 줄에 걸쳐 작성해서는 안 됩니다.
Why? 끊어진 문자열은 작업하기 힘들고 코드 검색도 어렵게 만듭니다.
// 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.';
6.3 프로그래밍 방식으로 문자열을 구축할 때 연결 대신 템플릿 문자열을 사용하세요. eslint: prefer-template template-curly-spacing
Why? 템플릿 문자열은 적절한 줄바꿈 및 문자열 보간 기능을 통해 읽기 쉽고 간결한 구문을 제공합니다.
// 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}?`;
}
6.4 문자열에 eval()을 사용하지 마세요. 너무 많은 취약점이 노출됩니다. eslint: no-eval
6.5 문자열에서 불필요하게 문자를 이스케이프하지 마세요 eslint: no-useless-escape
Why? 백슬래시는 가독성을 해치므로 필요할 때만 사용해야 합니다.
// bad
const foo = '\'this\' \i\s \"quoted\"';
// good
const foo = '\'this\' is "quoted"';
const foo = `my name is '${name}'`;
7.1 함수 선언 대신 명명된 함수 표현식을 사용하세요. eslint: func-style, func-names
Why? 함수 선언은 호이스트되므로 파일에서 정의하기 전에 함수를 참조하기가 쉽습니다. 이는 가독성과 유지 관리성을 해칩니다. 함수 정의가 너무 크거나 복잡하여 파일의 나머지 부분을 이해하는 데 방해가 되는 경우, 아마도 함수를 자체 모듈로 추출해야 할 때입니다! 이름이 포함된 변수에서 유추되었는지 여부에 관계없이 표현식에 명시적으로 이름을 지정하는 것을 잊지 마세요(최신 브라우저나 Babel과 같은 컴파일러를 사용할 때 종종 그렇습니다). 이렇게 하면 오류의 호출 스택에 대한 모든 가정이 제거됩니다. (Discussion)
// bad
function foo() {
// ...
}
// bad
const foo = function () {
// ...
};
// good
// lexical name distinguished from the variable-referenced invocation(s)
const short = function longUniqueMoreDescriptiveLexicalFoo() {
// ...
};
7.2 즉시 호출되는 함수 표현식을 괄호로 묶습니다. eslint: wrap-iife
Why? 즉시 호출되는 함수 표현식은 단일 단위입니다. 이것과 호출 괄호를 모두 괄호로 감싸면 이를 깔끔하게 표현할 수 있습니다. 모듈이 도처에 있는 세상에서는 IIFE가 거의 필요하지 않다는 점에 유의하세요.
// immediately-invoked function expression (IIFE)
(function () {
console.log('Welcome to the Internet. Please follow me.');
}());
7.3 함수가 아닌 블록(if, while 등)에서 함수를 선언하지 마세요. 대신 함수를 변수에 할당하세요. 브라우저에서 허용하지만 모두 다르게 해석하는데, 이는 나쁜 소식입니다. eslint: no-loop-func
// bad
let i;
for (i = 10; i; i--) {
(function() {
return i;
})();
}
// bad
while(i) {
let a = function() {
return i;
};
a();
}
// good
const a = function() {};
let i;
for (i = 10; i; i--) {
a();
}
7.4 참고: ECMA-262는 블록을 문장 목록으로 정의합니다. 함수 선언은 문장이 아닙니다.
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
7.5 매개변수에 arguments라는 이름을 붙이지 마세요. 이것은 모든 함수 범위에 주어진 arguments 객체보다 우선합니다.
// bad
function foo(name, options, arguments) {
// ...
}
// good
function foo(name, options, args) {
// ...
}
7.6 절대 arguments 를 이용하지 않는다. 대신에 rest 파라미터(...) 를 이용한다. eslint: prefer-rest-params
Why? ... is explicit about which arguments you want pulled. Plus, 나머지 인수(rest arguments)는 실제 배열이며, 단순히 배열과 비슷한 인수가 아닙니다.
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
7.7 함수 인수를 변경하는 대신 기본 매개변수 구문을 사용하세요.
Use default parameter syntax rather than mutating function arguments.
// really bad
function handleThings(opts) {
// No! We shouldn’t mutate function arguments.
// Double bad: if opts is falsy it'll be set to an object which may
// be what you want but it can introduce subtle bugs.
opts = opts || {};
// ...
}
// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
}
7.8 기본 매개변수를 사용하여 부작용을 방지하세요.
Why? 이는 추론하기 혼란스럽습니다.
let b = 1;
// bad
function count(a = b++) {
console.log(a);
}
count(); // 1
count(); // 2
count(3); // 3
count(); // 3
7.9 기본 매개변수는 항상 마지막에 넣으세요. eslint: default-param-last
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
}
7.10 새로운 함수를 생성하기 위해 Function 생성자를 사용하지 마십시오. eslint: no-new-func
Why? 이런 방식으로 함수를 생성하면 eval()과 비슷하게 문자열을 평가하여 취약점이 발생합니다.
// bad
const add = new Function('a', 'b', 'return a + b');
// still bad
const subtract = Function('a', 'b', 'return a - b');
7.11 익명함수는 function과 괄호 사이에 공백이 없다. 기명 함수(named function)는 함수 이름과 괄호 사이에 공백이 없다.
async arrow function인 경우 async와 arrow function 사이에 공백이 있다. eslint: space-before-function-paren space-before-blocks
Why? 일관성이 좋으며 이름을 추가하거나 제거할 때 공백을 추가하거나 제거할 필요가 없습니다.
// bad
const f = function(){};
const g = function (){};
const h = function() {};
// good
const x = function () {};
const y = function a() {};
7.12 가급적 mutate parameter는 사용하지 않는다. 하지만, 필요에 의해서는 주의하여 사용한다. eslint: no-param-reassign
Why? 매개변수로 전달된 객체를 조작하면 원래 호출자에서 원치 않는 변수 부작용이 발생할 수 있습니다.
// bad
function f1(obj) {
obj.key = 1;
}
// good
function f2(obj) {
const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1;
}
7.13 파라미터를 재할당하지 않는다. 단, 파라미터의 속성에 대해서는 재할당이 가능하다. eslint: no-param-reassign
Why? 매개변수를 재할당하면 예상치 못한 동작이 발생할 수 있으며, 특히 arguments 객체에 액세스할 때 그렇습니다. 또한 최적화 문제가 발생할 수 있으며, 특히 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) {
// ...
}
7.14 가변함수를 호출할 때는 spread 연산자(...)를 사용한다. eslint: prefer-spread
Why? 더 깔끔하고 컨텍스트를 제공할 필요가 없으며 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]);
7.15 함수의 정의가 멀티라인인 경우, 각 항목은 한 줄에 있고 마지막 항목에는 끝에 쉼표가 있어야 합니다. eslint: function-paren-newline
// bad
function foo(bar,
baz,
quux) {
// ...
}
// good
function foo(
bar,
baz,
quux,
) {
// ...
}
// bad
console.log(foo,
bar,
baz);
// good
console.log(
foo,
bar,
baz,
);
8.1 익명 함수를 사용해야 하는 경우(인라인 콜백을 전달하는 경우) 화살표 함수 표기법을 사용하세요.prefer-arrow-callback,arrow-spacing
Why? 이는 일반적으로 원하는 바인 this 컨텍스트에서 실행되는 함수 버전을 생성하며, 더 간결한 구문입니다.
Why not?
상당히 복잡한 함수가 있다면 해당 논리를 별도의 명명된 함수 표현식으로 옮길 수도 있습니다.
// 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;
});
8.2 함수 본문이 부작용 없이 표현식을 반환하는 단일 문장으로 구성된 경우 중괄호를 생략하고 암묵적 return을 사용합니다. 그렇지 않은 경우 중괄호를 유지하고 return 문장을 사용합니다.eslint: arrow-parens, arrow-body-style
Why? 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
[1, 2, 3].map((number, index) => ({
[index]: number,
}));
// No implicit return with side effects
function foo(callback) {
const val = callback();
if (val === true) {
// Do something if callback returns true
}
}
let bool = false;
// bad
foo(() => bool = true);
// good
foo(() => {
bool = true;
});
8.3 표현식이 여러 줄에 걸쳐 있는 경우 가독성을 높이기 위해 괄호로 묶습니다.
Why? 이는 함수의 시작과 끝을 명확하게 보여줍니다.
// bad
['get', 'post', 'put'].map((httpMethod) => Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
);
// good
['get', 'post', 'put'].map((httpMethod) => (
Object.prototype.hasOwnProperty.call(
httpMagicObjectWithAVeryLongName,
httpMethod,
)
));
8.4 명확성과 일관성을 위해 항상 인수 주위에 괄호를 포함하십시오. eslint: arrow-parens
Why? 인수를 추가하거나 제거할 때 diff churn을 최소화합니다.
// 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;
});
8.5 화살표 함수 구문(=>)과 비교 연산자(<=, >=)를 혼동하지 마세요. eslint: no-confusing-arrow
// 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;
};
8.6 암묵적 반환을 사용하여 화살표 함수 본문의 위치 적용. eslint: implicit-arrow-linebreak
//eslint default option:beside is on example
// bad
(foo) =>
bar;
(foo) =>
(bar);
// good
(foo) => bar;
(foo) => (bar);
(foo) => (
bar
)
9.1 항상 클래스를 사용하세요. 프로토타입을 직접 조작하지 마세요.
왜? 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;
}
}
9.2 상속은 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];
}
}
9.3 필요하다면, 메서드의 반환값으로 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);
9.4 toString()을 작성하는 것을 허용하지만 올바르게 동작하는 지와 side effect 가 없는지를 꼭 확인한다.
class Jedi {
constructor(options = {}) {
this.name = options.name || 'no name';
}
getName() {
return this.name;
}
toString() {
return `Jedi - ${this.getName()}`;
}
}
9.5 클래스는 지정되지 않은 경우 기본 생성자를 갖습니다. 빈 생성자 함수나 부모 클래스에 위임하는 함수는 불필요합니다. eslint:no-useless-constructor
// bad
class Jedi {
constructor() {}
getName() {
return this.name;
}
}
// bad
class Rey extends Jedi {
constructor(...args) {
super(...args);
}
}
// good
class Rey extends Jedi {
constructor(...args) {
super(...args);
this.name = 'Rey';
}
}
9.6 중복된 클래스 멤버를 피하세요. eslint:no-dupe-class-members
왜? 중복된 클래스 멤버 선언은 마지막 것을 조용히 선호할 것입니다. 중복이 있는 것은 거의 확실히 버그입니다.
// bad
class Foo {
bar() { return 1; }
bar() { return 2; }
}
// good
class Foo {
bar() { return 1; }
}
// good
class Foo {
bar() { return 2; }
}
9.7 클래스 메서드는 this외부 라이브러리나 프레임워크에서 특정 비정적 메서드를 사용해야 하는 경우가 아니면 정적 메서드를 사용하거나 정적 메서드로 만들어야 합니다. 인스턴스 메서드가 되면 수신자의 속성에 따라 다르게 동작한다는 것을 나타내야 합니다. eslint:class-methods-use-this
// bad
class Foo {
bar() {
console.log('bar');
}
}
// good - this is used
class Foo {
bar() {
console.log(this.bar);
}
}
// good - constructor is exempt
class Foo {
constructor() {
// ...
}
}
// good - static methods aren't expected to use this
class Foo {
static bar() {
console.log('bar');
}
}
10.1 비표준 모듈 시스템 대신 항상 모듈( 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;
10.2 와일드카드 가져오기를 사용하지 마세요.
왜? 이렇게 하면 단일 기본 내보내기가 가능합니다.
// bad
import * as AirbnbStyleGuide from './AirbnbStyleGuide';
// good
import AirbnbStyleGuide from './AirbnbStyleGuide';
10.3 import문으로부터 직접 export를 하지 않는다.
왜? 한 줄짜리는 간결하지만, 가져오기와 내보내기의 명확한 방법이 하나 있으면 일관성이 생깁니다.
// bad
// filename es6.js
export { es6 as default } from './AirbnbStyleGuide';
// good
// filename es6.js
import { es6 } from './AirbnbStyleGuide';
export default es6;
10.4 한 곳의 경로에서만 가져옵니다. eslint:no-duplicate-imports
왜? 같은 경로에서 가져오는 여러 줄이 있으면 코드를 유지하기가 더 어려워질 수 있습니다.
// 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';
10.5 변경 가능한 바인딩을 내보내지 마세요. eslint:import/no-mutable-exports
왜? 일반적으로 변형은 피해야 하지만, 특히 가변 바인딩을 내보낼 때는 피해야 합니다. 이 기술은 일부 특수한 경우에 필요할 수 있지만, 일반적으로 상수 참조만 내보내야 합니다.
// bad
let foo = 3;
export { foo };
// good
const foo = 3;
export { foo };
10.6 단일 내보내기가 있는 모듈에서는 명명된 내보내기보다 기본 내보내기를 선호합니다. eslint:import/prefer-default-export
왜? 한 가지만 내보내는 파일을 더 많이 장려하기 위해서인데, 이는 가독성과 유지 관리에 더 좋습니다.
// bad
export function foo() {}
// good
export default function foo() {}
10.7 모든 import 문은 상위에 위치한다. eslint:import/first
왜? imports가 호이스트되기 때문에, 모두 맨 위에 두면 놀라운 동작이 방지됩니다.
// bad
import foo from 'foo';
foo.init();
import bar from 'bar';
// good
import foo from 'foo';
import bar from 'bar';
foo.init();
10.8 멀티라인 import 문은 배열 및 객체 리터럴과 마찬가지로 들여쓰기가 필요합니다. eslint:object-curly-newline
왜? 중괄호는 스타일 가이드의 다른 모든 중괄호 블록과 동일한 들여쓰기 규칙을 따르고, 후행 쉼표도 마찬가지입니다.
// bad
import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path';
// good
import {
longNameA,
longNameB,
longNameC,
longNameD,
longNameE,
} from 'path';
10.9 모듈 가져오기 명령문에서 Webpack 로더 구문을 허용하지 마세요. eslint:import/no-webpack-loader-syntax
왜? imports에서 Webpack 구문을 사용하면 코드가 모듈 번들러에 결합되기 때문입니다. .에서 로더 구문을 사용하는 것을 선호합니다
// 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';
10.10 JavaScript 파일 이름 확장자를 포함하지 마세요 eslint:import/extensions
왜? 확장을 포함하면 리팩토링이 방해되고 모든 소비자에게 가져오는 모듈의 구현 세부 사항을 부적절하게 하드코딩합니다.
// 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';
11.1 반복자를 사용하지 마세요. for-in 또는 for-of와 같은 루프 대신 JavaScript의 고차 함수를 선호하세요. eslint: no-iterator no-restricted-syntax
Why? 이것은 우리의 불변 규칙을 강제합니다. 값을 반환하는 순수 함수를 다루는 것은 부작용을 다루는 것보다 추론하기 쉽습니다.
배열을 반복하려면 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);
11.2 Don’t use generators for now.
Why? ES5로 잘 변환되지 않습니다.
11.3 생성기를 사용해야 하거나 당사의 조언을 무시하는 경우 해당 함수 서명이 적절한 간격으로 배치되어 있는지 확인하십시오.. eslint: generator-star-spacing
Why? 함수와 는 동일한 개념적 키워드의 일부입니다. 는 함수의 수정자가 아니고, 함수*는 함수와 다른 고유한 구조입니다.
// 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* () {
// ...
};
12.1 프로퍼티에 엑세스 하는 경우, . 을 사용하세요. eslint: dot-notation
Use dot notation when accessing properties.
const luke = {
jedi: true,
age: 28,
};
// bad
const isJedi = luke['jedi'];
// good
const isJedi = luke.jedi;
12.2 변수를 사용하여 속성에 액세스할 때는 대괄호 표기법 []을 사용합니다.
Use bracket notation [] when accessing properties with a variable.
const luke = {
jedi: true,
age: 28,
};
function getProp(prop) {
return luke[prop];
}
const isJedi = getProp('jedi');
12.3 거듭 제곱을 계산할 때는 연산자 를 사용하세요. eslint: prefer-exponentiation-operator
Use exponentiation operator when calculating exponentiations.
// bad
const binary = Math.pow(2, 10);
// good
const binary = 2 ** 10;
13.1 항상 const나 let을 사용하여 변수를 선언하세요. 그렇지 않으면 전역 변수가 생성됩니다. 우리는 전역 네임스페이스를 오염시키는 것을 피하고 싶습니다. eslint: no-undef prefer-const
// bad
superPower = new SuperPower();
// good
const superPower = new SuperPower();
13.2 변수나 할당문 당 하나의 const 또는 let 선언을 사용하세요. eslint: one-var
Why? 이런 식으로 새로운 변수 선언을 추가하는 것이 더 쉽고, ; 를 로 바꾸거나 구두점만 있는 diff를 도입하는 것에 대해 걱정할 필요가 없습니다. 또한 모든 선언을 한꺼번에 건너뛰는 대신 디버거로 각 선언을 단계별로 살펴볼 수 있습니다.
// 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';
13.3 모든 const를 그룹화한 다음, 모든 let을 그룹화합니다.
Why? 이는 나중에 이전에 할당된 변수 중 하나에 따라 변수를 할당해야 할 때 유용합니다.
// 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;
13.4 필요한 곳에 변수를 할당하고, 합리적인 위치에 변수를 배치하세요.
Why? let과 const는 블록 범위이며 함수 범위가 아닙니다.
// 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;
}
13.5 변수 할당을 체인하지 마세요. eslint: no-multi-assign
Why? 변수 할당을 체이닝하면 암묵적인 전역 변수가 생성됩니다.
// bad
(function example() {
// JavaScript interprets this as
// let a = ( b = ( c = 1 ) );
// The let keyword only applies to variable a; variables b and c become
// global variables.
let a = b = c = 1;
}());
console.log(a); // throws ReferenceError
console.log(b); // 1
console.log(c); // 1
// good
(function example() {
let a = 1;
let b = a;
let c = a;
}());
console.log(a); // throws ReferenceError
console.log(b); // throws ReferenceError
console.log(c); // throws ReferenceError
// the same applies for const
13.6 단항 증가 및 감소(++, --) 사용을 피하세요. eslint no-plusplus
Why? eslint 설명서에 따르면, 단항 증가 및 감소 문은 자동 세미콜론 삽입의 적용을 받으며 애플리케이션 내에서 값을 증가 또는 감소시킬 때 조용한 오류를 일으킬 수 있습니다. 또한 num++ 또는 num ++ 대신 num += 1과 같은 문으로 값을 변경하는 것이 더 표현력이 좋습니다. 단항 증가 및 감소 문을 허용하지 않으면 의도치 않게 값을 사전 증가/사전 감소시키는 것도 방지할 수 있으며, 이는 프로그램에서 예상치 못한 동작을 일으킬 수도 있습니다.
// bad
const array = [1, 2, 3];
let num = 1;
num++;
--num;
let sum = 0;
let truthyCount = 0;
for (let i = 0; i < array.length; i++) {
let value = array[i];
sum += value;
if (value) {
truthyCount++;
}
}
// good
const array = [1, 2, 3];
let num = 1;
num += 1;
num -= 1;
const sum = array.reduce((a, b) => a + b, 0);
const truthyCount = array.filter(Boolean).length;
13.7 할당에서 = 앞이나 뒤에 줄바꿈을 피하세요. 할당이 max-len을 위반하는 경우 값을 괄호로 묶으세요. eslint operator-linebreak.
Why? Linebreaks 줄바꿈은 할당의 값을 모호하게 만들 수 있습니다.
// bad
const foo =
superLongLongLongLongLongLongLongLongFunctionName();
// bad
const foo
= 'superLongLongLongLongLongLongLongLongString';
// good
const foo = (
superLongLongLongLongLongLongLongLongFunctionName()
);
// good
const foo = 'superLongLongLongLongLongLongLongLongString';
13.8 사용하지 않는 변수를 허용하지 않습니다. eslint: no-unused-vars
Why? 코드의 어느 곳에서도 선언되고 사용되지 않는 변수는 불완전한 리팩토링으로 인한 오류일 가능성이 가장 높습니다. 이러한 변수는 코드에서 공간을 차지하고 독자의 혼란을 초래할 수 있습니다.
// bad
const some_unused_var = 42;
// Write-only variables are not considered as used.
let y = 10;
y = 5;
// A read for a modification of itself is not considered as used.
let z = 0;
z = z + 1;
// Unused function arguments.
function getX(x, y) {
return x;
}
// good
function getXPlusY(x, y) {
return x + y;
}
const x = 1;
const y = a + 2;
alert(getXPlusY(x, y));
// 'type' is ignored even if unused because it has a rest property sibling.
// 'type'은 나머지 속성 형제가 있기 때문에 사용되지 않더라도 무시됩니다.
// This is a form of extracting an object that omits the specified keys.
// 이는 지정된 키를 생략한 객체를 추출하는 형태입니다.
const { type, ...coords } = data;
// 'coords' is now the 'data' object without its 'type' property.
// 'coords'는 이제 'type' 속성이 없는 'data' 객체입니다.
14.1 var 선언은 가장 가까운 둘러싼 함수 범위의 맨 위로 끌어올려지지만 할당은 그렇지 않습니다. const와 let 선언은 Temporal Dead Zones(TDZ)라는 새로운 개념으로 축복받았습니다. typeof가 더 이상 안전하지 않은 이유를 아는 것이 중요합니다. typeof is no longer safe
// we know this wouldn’t work (assuming there is no notDefined global variable)
// 우리는 이것이 작동하지 않을 것이라는 것을 알고 있습니다(notDefined 전역 변수가 없다고 가정)
function example() {
console.log(notDefined); // => throws a ReferenceError
}
// creating a variable declaration after you reference the variable will work due to variable hoisting. Note: the assignment value of `true` is not hoisted.
// 변수를 참조한 후 변수 선언을 만들면 변수 호이스팅으로 인해 작동합니다. 참고: `true`의 할당 값은 호이스팅되지 않습니다.
function example() {
console.log(declaredButNotAssigned); // => undefined
var declaredButNotAssigned = true;
}
// the interpreter is hoisting the variable declaration to the top of the scope, which means our example could be rewritten as:
// 인터프리터는 변수 선언을 범위의 맨 위로 끌어올리고 있습니다. 즉, 우리의 예제는 다음과 같이 다시 작성될 수 있습니다.
function example() {
let declaredButNotAssigned;
console.log(declaredButNotAssigned); // => undefined
declaredButNotAssigned = true;
}
// using const and let
function example() {
console.log(declaredButNotAssigned); // => throws a ReferenceError
console.log(typeof declaredButNotAssigned); // => throws a ReferenceError
const declaredButNotAssigned = true;
}
14.2 Anonymous function expressions은 변수 이름을 호이스트하지만 함수 할당은 호이스트하지 않습니다.
function example() {
console.log(anonymous); // => undefined
anonymous(); // => TypeError anonymous is not a function
var anonymous = function () {
console.log('anonymous function expression');
};
}
14.3 Named function expressions은 함수 이름이나 함수 본문이 아닌 변수 이름을 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');
};
}
// the same is true when the function name
// is the same as the variable name.
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
var named = function named() {
console.log('named');
};
}
14.4 14.4 함수 선언은 함수 이름과 함수 본문을 Hoist합니다.
function example() {
superPower(); // => Flying
function superPower() {
console.log('Flying');
}
}
14.5 변수, 클래스, 함수는 사용하기 전에 정의해야 합니다. eslint: no-use-before-define
Why? 변수, 클래스 또는 함수가 사용된 후에 선언되면 독자가 참조되는 것이 무엇인지 알 수 없으므로 가독성에 해를 끼칠 수 있습니다. 독자가 사물의 사용을 마주치기 전에 먼저 사물의 출처(다른 모듈에서 가져왔든, 파일에서 정의되었든)를 마주치는 것이 훨씬 더 명확합니다.
// bad
// Variable a is being used before it is being defined.
// 변수 a는 정의되기 전에 사용되고 있습니다.
console.log(a); // this will be undefined, since while the declaration is hoisted, the initialization is not
// 선언이 호이스트되는 동안 초기화가 수행되지 않으므로 이는 정의되지 않습니다.
var a = 10;
// Function fun is being called before being defined.
// 함수 fun이 정의되기 전에 호출됩니다.
fun();
function fun() {}
// Class A is being used before being defined.
// 클래스 A는 정의되기 전에 사용되고 있습니다.
new A(); // ReferenceError: Cannot access 'A' before initialization
class A {
}
// `let` and `const` are hoisted, but they don't have a default initialization.
// `let`과 `const`는 호이스트되지만 기본 초기화가 없습니다.
// The variables 'a' and 'b' are in a Temporal Dead Zone where JavaScript
// 변수 'a'와 'b'는 JavaScript가 있는 Temporal Dead Zone에 있습니다.
// knows they exist (declaration is hoisted) but they are not accessible
// 존재한다는 것을 알고 있지만(선언이 호이스트됨) 액세스할 수 없습니다.
// (as they are not yet initialized).
// (아직 초기화되지 않았기 때문에)
console.log(a); // ReferenceError: Cannot access 'a' before initialization
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let a = 10;
const b = 5;
// good
var a = 10;
console.log(a); // 10
function fun() {}
fun();
class A {
}
new A();
let a = 10;
const b = 5;
console.log(a); // 10
console.log(b); // 5
For more information refer to JavaScript Scoping & Hoisting by Ben Cherry.
[참고]
https://google.github.io/styleguide/jsguide.html#file-name
https://google.github.io/styleguide/tsguide.html
https://airbnb.io/javascript/react/
https://docs.gitlab.com/development/fe_guide/style/javascript/
https://eslint.org/docs/latest/rules/