요즘 자바스크립트 코드를 열면 예전 문법으론 못 보던 기호들이 잔뜩 보인다. 점 세 개, 물음표 점, 물음표 두 개 같은 것들이다. 이것들은 몇 년 사이 언어에 추가된 문법인데, 하나같이 자주 쓰던 지겨운 코드를 짧고 안전하게 줄여준다. 처음엔 낯설어도 익히고 나면 옛날 방식으로 돌아가기 싫어진다. 다른 사람 코드를 읽으려면 어차피 알아야 하기도 하다. 이번엔 실무에서 매일 쓰는 것들만 골라, 무엇을 줄여주는지 실제 실행 결과와 함께 정리한다.


템플릿 리터럴은 문자열 조립을 끝낸다. 예전엔 변수와 문자열을 더하기로 이어 붙였다. 따옴표와 더하기가 뒤엉켜 실수하기 딱 좋았다. 백틱(`)으로 감싸면 ${ } 안에 변수든 식이든 그대로 끼워 넣는다.


const name = "kim", age = 20;
console.log(`${name} is ${age} years, next ${age + 1}`);
// kim is 20 years, next 21


중괄호 안에서는 계산도 된다. 게다가 백틱 문자열은 줄바꿈을 그대로 품어서, 여러 줄 문자열도 이어 붙이기 없이 그냥 쓴다. 나는 이걸 안 뒤로 문자열 더하기를 거의 안 쓴다.


구조 분해는 값을 골라 꺼낸다. 배열이나 객체에서 원하는 값을 변수로 빼낼 때, 하나하나 인덱스나 점으로 접근하던 걸 한 줄로 줄인다. 배열은 순서대로, 객체는 이름으로 꺼낸다.


const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first, second, rest); // 1 2 [ 3, 4, 5 ]
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1


배열 분해에서 ...rest는 남은 걸 배열로 쓸어 담는다. 아래 두 줄은 임시 변수 없이 두 값을 맞바꾸는 유명한 요령이다. 객체 분해는 더 자주 쓴다.


const user = { id: 7, nick: "hong", role: "admin" };
const { nick, role, level = "bronze" } = user;
console.log(nick, role, level); // hong admin bronze
const { id: userId } = user;
console.log(userId); // 7


이름으로 꺼내니 순서는 상관없다. level = "bronze"처럼 없는 값에 기본값을 줄 수 있고, id: userId처럼 다른 이름으로 받을 수도 있다. 나는 함수가 객체를 받을 때 매개변수 자리에서 바로 분해해, 필요한 필드만 뽑아 쓴다. 코드가 훨씬 깔끔해진다.


스프레드는 펼치고 rest는 모은다. 점 세 개(...)는 자리에 따라 반대 일을 한다. 값 자리에선 펼쳐 놓고, 받는 자리에선 그러모은다.


const arr1 = [1, 2], arr2 = [3, 4];
console.log([...arr1, ...arr2]); // [ 1, 2, 3, 4 ]
const base = { a: 1, b: 2 };
console.log({ ...base, b: 99, c: 3 }); // { a: 1, b: 99, c: 3 }
console.log(Math.max(...[5, 2, 9, 1])); // 9


배열을 펼쳐 새 배열로 합치고, 객체를 펼쳐 새 객체로 합친다. 겹치는 키는 뒤엣것이 이긴다. 여기선 b가 99로 덮인다. 이렇게 만든 건 원본을 안 건드리고 새 걸 만드는 거라, 상태를 안전하게 갱신할 때 요긴하다.


function sum(...nums) {
return nums.reduce((acc, n) => acc + n, 0);
}
console.log(sum(1, 2, 3, 4)); // 10


함수 매개변수 자리에 ...nums를 쓰면 넘어온 인자를 전부 배열로 받는다. 개수가 정해지지 않은 인자를 다룰 때 쓴다.


기본 매개변수로 빈자리를 채운다. 인자를 안 넘기면 그 자리는 undefined가 된다. 예전엔 이걸 함수 안에서 일일이 검사해 기본값을 채웠는데, 이제 매개변수 자리에서 바로 정한다.


function greet(who = "world") {
return "hi " + who;
}
console.log(greet()); // hi world
console.log(greet("kim")); // hi kim


인자가 없거나 undefined일 때만 기본값이 쓰인다. 함수 첫머리를 채우던 검사 코드가 통째로 사라진다.


옵셔널 체이닝은 없는 길에서 안 터진다. 중첩된 객체에서 깊은 값을 꺼낼 때, 중간이 null이나 undefined면 예전엔 그 자리에서 에러가 나며 코드가 죽었다. 물음표 점(?.)은 중간이 비면 거기서 멈추고 조용히 undefined를 준다.


const data = { profile: { addr: null } };
console.log(data.profile?.addr?.city); // undefined
console.log(data.account?.email); // undefined


data.account가 없어도 에러 없이 undefined가 나온다. 옛날엔 data.account && data.account.email처럼 매 단계를 검사해야 했다. 나는 서버에서 받은, 어떤 필드가 있을지 없을지 모르는 데이터를 다룰 때 이걸 제일 많이 쓴다.


널 병합은 진짜 빈 값만 걸러낸다. 값이 없을 때 기본값을 주려고 예전엔 ||(OR)를 썼는데, 여기 함정이 있다. ||는 0이나 빈 문자열도 없는 값 취급해 기본값으로 갈아치운다. 물음표 두 개(??)는 nullundefined일 때만 기본값을 준다.


const count = 0;
console.log(count || 10); // 10 (0을 버림!)
console.log(count ?? 10); // 0 (0을 지킴)


0이 멀쩡한 값인데 ||는 이걸 버리고 10을 준다. 나는 수량이 0인 걸 표시해야 하는데 자꾸 기본값으로 바뀌는 버그를 || 때문에 겪었다. 값 자체가 유효한 0이나 빈 문자열일 수 있으면 ??를 써야 한다.


객체 표기도 짧아졌다. 변수 이름과 넣을 키 이름이 같으면 한 번만 쓰면 되고, 키 이름을 변수로 정하고 싶으면 대괄호로 감싼다.


const name = "kim", age = 20, key = "dynamic";
console.log({ name, age, [key]: true });
// { name: 'kim', age: 20, dynamic: true }


{ name }{ name: name }의 줄임이다. [key]는 변수 key의 값인 "dynamic"이 실제 키가 된다. 키 이름을 실행 중에 정해야 할 때 쓴다.


for...of로 값을 바로 순회한다. 배열을 돌 때 인덱스를 세던 옛 방식 대신, for...of는 값을 직접 하나씩 꺼내준다. 인덱스도 같이 필요하면 entries를 곁들인다.


const scores = [90, 85, 70];
for (const [i, s] of scores.entries()) {
console.log(i, s);
}
// 0 90 / 1 85 / 2 70


entries가 인덱스와 값을 한 쌍으로 주고, 그걸 구조 분해로 is에 받는다. 앞에서 나온 문법들이 여기서 자연스레 겹쳐 쓰인다. 모던 문법의 진짜 힘은 이렇게 서로 맞물릴 때 나온다.


여기 모은 문법들은 새로운 기능을 주는 게 아니라, 늘 하던 일을 짧고 덜 위험하게 만들어 준다. 문자열 조립, 값 꺼내기, 합치기, 기본값 채우기, 없는 값 다루기. 전부 예전에도 하던 것들이다. 나는 이걸 익히고 나서 코드가 눈에 띄게 짧아지고 실수도 줄었다. 처음엔 기호가 낯설지만, 무엇을 줄여주는지 한 번 이해하면 손이 먼저 기억한다.