
항상 const 또는 let을 사용하여 변수를 선언하자. 변수를 재할당해야 하는 경우가 아니면 기본적으로 const를 사용하고 var는 사용하지 말자. (const와 let은 대부분 다른 언어의 변수처럼 블록 스코프이지만, var는 함수 스코프로 이해하기 어려운 버그를 일으킬 수 있다.)
변수는 선언 전에 사용되어서는 안 된다.
한 번에 하나의 변수 선언만 하자. (let a = 1, b = 2; 금지)
Array() 생성자를 사용을 피하자. 혼란스럽고 모순되는 사용법이 존재하기 때문이다.
Before
const a = new Array(2); // [undefined, undefined]
const b = new Array(2, 3); // [2, 3];
After
const a = [2];
const b = [2, 3];
// new Array(2) 대신
const c = [];
c.length = 2;
// [0, 0, 0, 0, 0]
Array.from<number>({length: 5}).fill(0);
배열에 숫자가 아닌 속성을 정의하거나 사용하지 말자(length는 제외).
배열은 본래 순차적인 데이터 저장을 위한 자료구조이기 때문에, 배열의 인덱스(즉, 숫자형 인덱스) 외에 임의의 문자열 속성을 정의하는 것은 혼란을 초래할 수 있다.
대신 Map(또는 Object)을 사용하자.
Before
const arr = [1, 2, 3];
arr.customProperty = "Hello";
console.log(arr.customProperty); // "Hello"
console.log(arr.length); // 3 (길이는 영향을 받지 않음)
for (const key in arr) {
console.log(key); // "0", "1", "2", "customProperty"(예상치 못한 속성)
}
console.log(Object.keys(arr)); // ["0", "1", "2", "customProperty"]
After
const map = new Map();
map.set("customProperty", "Hello");
map.set(0, 1);
map.set(1, 2);
console.log(map.get("customProperty")); // "Hello"
console.log(map.get(0)); // 1
스프레드 문법은 이터러블(iterable) 객체를 펼쳐서 새로운 배열이나 객체를 만들 때 유용하지만, 그 사용에 있어서 몇 가지 주의해야 할 점이 있다.
스프레드 문법은 이터러블만 펼쳐야 한다:
스프레드 문법을 사용할 때 펼칠 값은 이터러블이어야 한다. 이터러블이란 배열, 문자열, 맵, 세트 같은 데이터 구조를 말하며, 그 안의 요소들을 순차적으로 접근할 수 있다.
원시 값(primitive types)인 null, undefined, 숫자, 문자열 등의 값은 스프레드 문법을 사용할 수 없다.
Before
const foo = [7];
const bar = [5, ...(shouldUseFoo && foo)]; // 에러 발생할 수 있음
After
const foo = shouldUseFoo ? [7] : []; // 조건에 따라 배열 선택.
const bar = [5, ...foo];
스프레드되는 값은 생성되는 값과 자료형과 구조를 일치하는 것이 권장된다:
Before
const fooStrings = ['a', 'b', 'c'];
const ids = {...fooStrings}; // {0: 'a', 1: 'b', 2: 'c'}
After
const fooStrings = ['a', 'b', 'c'];
const ids = [...fooStrings, 'd', 'e']; // ['a', 'b', 'c', 'd', 'e']
스프레드 문법은 얕은 복사(shallow copy)를 수행한다.
얕은 복사는 객체나 배열의 1단계 깊이까지만 복사한다. 따라서 다음과 같은 상황이 발생한다:
const arr = [1, 2, 3]; // 기본 자료형만 포함된 배열
const spreadArr = [...arr];
spreadArr[0] = 99;
console.log(arr); // [1, 2, 3] (원본 불변)
console.log(spreadArr); // [99, 2, 3] (복사본만 변경됨)
const arr = [1, 2, { a: 3 }]; // 참조 자료형이 포함된 배열
const spreadArr = [...arr];
spreadArr[2].a = 10;
console.log(arr); // [1, 2, { a: 10 }] (원본도 영향받음)
console.log(spreadArr); // [1, 2, { a: 10 }] (복사본도 동일)
구조 분해 할당은 코드의 가독성을 높이고, 변수를 더 쉽게 추출할 수 있도록 돕는 유용한 기능이다. 하지만 올바르게 사용하는 방법이 중요하다.
배열을 구조 분해하여 변수에 값을 할당하는 방법은 다음과 같다:
const [a, b, c, ...rest] = generateResults();
let [, b,, d] = someArray;
배열에 기본값을 설정하는 방법:
Before
function destructuring([a, b] = [4, 2]) { … }
After
function destructured([a = 4, b = 2] = []) { … }
배열 대신 객체 구조 분해를 사용하는 것이 더 좋은 경우가 많다. 배열 구조 분해는 순서대로 값을 추출하므로, 배열의 순서가 바뀌면 코드가 깨질 수 있다. 반면, 객체 구조 분해는 키 이름으로 값을 추출하기 때문에 순서에 의존하지 않아 더 안전하고 직관적이다.
const { name, age, location } = user;
Object() 생성자는 객체를 생성하는 데 사용될 수 있지만, 객체 리터럴 {}을 사용하는 것이 훨씬 더 간결하고 명확하다.
Before
let obj = new Object();
obj.a = 1;
obj.b = 2;
After
let obj = { a: 1, b: 2 };
for (... in ...)는 객체의 모든 열거 가능한(enumerable) 속성을 순회한다.
여기에는 해당 객체의 직접 속성뿐만 아니라 프로토타입 체인에서 상속된 속성도 포함될 수 있으므로 주의하자.
Before
const someObj = { a: 1, b: 2 };
Object.prototype.c = 3; // 프로토타입에 추가된 속성
for (const key in someObj) {
console.log(key); // 출력: "a", "b", "c" (예상치 못한 속성 포함)
}
After
for (const key in someObj) {
if (someObj.hasOwnProperty(key)) {
console.log(key); // 출력: "a", "b" (프로토타입 속성 제외)
}
}
hasOwnProperty는 해당 속성이 객체의 직접 속성인지 확인for (const key of Object.keys(someObj)) {
console.log(key); // 출력: "a", "b"
}
Object.keys()는 객체의 직접 열거 가능한 속성의 키만 반환for (const [key, value] of Object.entries(someObj)) {
console.log(key, value); // 출력: "a 1", "b 2"
}
Object.entries()는 [키, 값] 쌍의 배열을 반환스프레드 문법은 기존 객체의 얕은 복사(shallow copy)를 생성한다.
중복 키가 있는 경우, 나중에 정의된 값이 기존 값을 덮어쓴다.
const foo = { num: 1 };
const foo2 = {
...foo,
num: 5,
}; // 기존 foo의 내용을 복사하고, num을 5로 덮어씀.
const foo3 = {
num: 5,
...foo,
}; // foo의 내용을 복사하면서 기존 num: 5를 덮어씀.
console.log(foo2.num); // 출력: 5
console.log(foo3.num); // 출력: 1
객체를 생성할 때 오직 객체만 스프레드하자:
배열과 기본형(null과 undefined 포함)을 스프레드 해서는 안된다.
const fooStrings = ['a', 'b', 'c'];
const ids = {...fooStrings}; // {0: 'a', 1: 'b', 2: 'c'}
기본 Object 프로토타입이 아닌 다른 프로토타입을 갖는 객체(예: 클래스 정의, 클래스 인스턴스, 함수)의 스프레드는 주의해야 한다:
스프레드 문법은 객체의 열거 가능한(enumerable) 비-프로토타입(non-prototype) 속성만 복사한다. (Object.keys(obj)로 반환되는 속성들)
커스텀 프로토타입을 가지는 객체는 프로토타입 체인에 있는 속성(메서드 등)은 복사되지 않는다.
계산된 프로퍼티명(computed property names)를 사용하면 더 동적으로 키를 정의할 수 있다:
JavaScript에서 객체 리터럴에서의 속성 정의는 보통 정적인 문자열이나 식별자를 사용하지만, Computed property names는 객체의 키를 런타임 시 계산된 값으로 설정할 때 사용된다.
const dynamicKey = 'age';
const obj = {
[dynamicKey]: 25, // 'age': 25
};
console.log(obj.age); // 25
계산된 속성이 심볼(예: [Symbol.iterator])이 아닌 한, dict 스타일(따옴표로 묶인) 키로 간주된다. (즉, 따옴표로 묶이지 않은 키와 혼합하지 말자).
구조 분해는 단순한 한 단계 수준에서만 사용이 권장된다:
중첩된 구조 분해는 가독성을 해치고, 매개변수의 복잡성을 증가시킨다. 중첩된 구조를 분해해야 한다면, 함수를 나누어 처리하거나 별도로 객체를 추출하여 사용하는 것이 낫다.
Before
function nestedTooDeeply({x: {num, str}}: {x: Options}) {}
After
interface Options {
num?: number;
str?: string;
}
function destructured({num, str = 'default'}: Options = {}) {}
기본값은 구조 분해의 왼쪽에서 명시적으로 설정한다.
Before
function destructured({num, str}: Options = {num: 42, str: 'default'}) {
console.log(num, str);
}
After
function destructured({num = 42, str = 'default'}: Options = {}) {
console.log(num, str);
}
GitHub - google/gts: ☂️ TypeScript style guide, formatter, and linter.
Typescript Google Code Style Part 1
Typescript Google Code Style Part 2
Typescript Google Code Style Part 3