Skip to main content
Advertisement

ES6 핵심 문법

ES6(ECMAScript 2015)는 JavaScript의 가장 큰 문법 개편이었습니다. 템플릿 리터럴, 화살표 함수, 클래스, 모듈 등 현대 JavaScript의 근간이 되는 기능들이 도입되었습니다.


템플릿 리터럴(Template Literals)

백틱(`)을 사용하는 문자열 표현 방식입니다.

const name = 'Alice';
const age = 30;

// 기존 방식
const msg1 = '안녕하세요, ' + name + '님! 나이: ' + age;

// 템플릿 리터럴
const msg2 = `안녕하세요, ${name}님! 나이: ${age}`;

// 표현식 삽입
const result = `2 + 3 = ${2 + 3}`;
const status = `상태: ${age >= 18 ? '성인' : '미성년자'}`;
const func = `길이: ${name.length}`;

// 멀티라인 문자열
const html = `
<div class="card">
<h2>${name}</h2>
<p>나이: ${age}</p>
</div>
`;

// 중첩 템플릿 리터럴
const items = ['사과', '바나나', '체리'];
const list = `항목 목록:
${items.map((item, i) => ` ${i + 1}. ${item}`).join('\n')}`;

태그드 템플릿 리터럴

// 태그 함수: 템플릿 리터럴을 직접 처리
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i - 1];
return result + (value ? `<mark>${value}</mark>` : '') + str;
});
}

const name = 'Alice';
const score = 95;
const msg = highlight`${name}님의 점수는 ${score}점입니다.`;
// <mark>Alice</mark>님의 점수는 <mark>95</mark>점입니다.

// 실전: SQL 인젝션 방지
function sql(strings, ...values) {
const escaped = values.map(v => escapeSQL(v));
return strings.reduce((query, str, i) => {
return query + (escaped[i - 1] ?? '') + str;
});
}

const username = "Alice'; DROP TABLE users; --";
const query = sql`SELECT * FROM users WHERE name = ${username}`;
// 안전하게 이스케이프됨

// styled-components 방식 (React CSS-in-JS)
const Button = styled.button`
background: ${props => props.primary ? '#007bff' : 'white'};
color: ${props => props.primary ? 'white' : '#007bff'};
padding: 0.5rem 1rem;
`;

기본 매개변수(Default Parameters)

// 기존 방식
function greet(name, greeting) {
name = name || '손님';
greeting = greeting || '안녕하세요';
return `${greeting}, ${name}!`;
}

// ES6 기본 매개변수
function greet(name = '손님', greeting = '안녕하세요') {
return `${greeting}, ${name}!`;
}

greet(); // 안녕하세요, 손님!
greet('Alice'); // 안녕하세요, Alice!
greet('Bob', '하이'); // 하이, Bob!

// 표현식도 기본값으로 사용 가능
function createUser(
name,
id = Math.random().toString(36).slice(2),
createdAt = new Date().toISOString()
) {
return { name, id, createdAt };
}

// 이전 매개변수를 참조 가능
function makeRange(start, end = start + 10) {
return Array.from({ length: end - start }, (_, i) => start + i);
}
makeRange(5); // [5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
makeRange(3, 7); // [3, 4, 5, 6]

// undefined만 기본값 트리거 (null은 그대로)
function test(x = 'default') {
return x;
}
test(undefined); // 'default'
test(null); // null
test(0); // 0
test(''); // ''

나머지/전개 연산자 심화

나머지 매개변수(Rest Parameters)

// 마지막 매개변수에만 사용 가능
function sum(...numbers) {
return numbers.reduce((acc, n) => acc + n, 0);
}

sum(1, 2, 3, 4, 5); // 15

// 앞의 매개변수와 조합
function logMessage(level, ...messages) {
console[level](...messages);
}

logMessage('log', '안녕', '세상', '!');
logMessage('error', '에러 발생:', new Error('문제'));

// 비구조화와 나머지
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first: 1, second: 2, rest: [3, 4, 5]

const { name, age, ...others } = { name: 'Alice', age: 30, city: 'Seoul', job: 'Dev' };
// name: 'Alice', age: 30, others: { city: 'Seoul', job: 'Dev' }

전개 연산자(Spread Operator)

// 배열 전개
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

const combined = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]
const withMiddle = [...arr1, 10, ...arr2]; // [1, 2, 3, 10, 4, 5, 6]
const copy = [...arr1]; // 얕은 복사

// 함수 인수로
Math.max(...arr1); // 3
console.log(...arr1); // 1 2 3

// 문자열 전개
const chars = [..."hello"]; // ['h', 'e', 'l', 'l', 'o']

// 객체 전개
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }
const overridden = { ...obj1, b: 99, ...obj2 }; // { a: 1, b: 99, c: 3, d: 4 }

// 객체 복사 (얕은 복사)
const original = { name: 'Alice', settings: { theme: 'dark' } };
const copy = { ...original };
copy.name = 'Bob'; // original 영향 없음
copy.settings.theme = 'light'; // original.settings도 변경됨! (참조)

// 실전: 불변 업데이트 패턴 (React state)
const updateUser = (user, updates) => ({ ...user, ...updates });
const updated = updateUser(original, { name: 'Bob', email: 'bob@example.com' });

화살표 함수 심화

// 다양한 형태
const double = x => x * 2;
const add = (a, b) => a + b;
const greet = () => 'Hello!';
const getObject = () => ({ name: 'Alice' }); // 객체는 소괄호로 감싸기

// 여러 줄
const process = (data) => {
const filtered = data.filter(Boolean);
const mapped = filtered.map(x => x * 2);
return mapped;
};

// this 바인딩 차이 - 가장 중요한 특징!
const counter = {
count: 0,

// 일반 함수: 자신만의 this
incrementRegular: function() {
setInterval(function() {
this.count++; // this는 undefined (strict mode) 또는 window
console.log(this.count);
}, 1000);
},

// 화살표 함수: 상위 스코프의 this 상속
incrementArrow: function() {
setInterval(() => {
this.count++; // this는 counter 객체
console.log(this.count); // 올바르게 동작
}, 1000);
}
};

// 클래스에서 이벤트 핸들러
class Button {
constructor() {
this.clickCount = 0;
// 화살표 함수로 this 바인딩 유지
this.handleClick = () => {
this.clickCount++;
console.log(`클릭 ${this.clickCount}`);
};
}
}

// 화살표 함수를 사용하면 안 되는 경우
const obj = {
name: 'Alice',

// 나쁨: 화살표 함수는 자신의 this가 없음
getName: () => this.name, // undefined

// 좋음
getName() {
return this.name; // 'Alice'
}
};

단축 메서드 표기법

// 객체 메서드 단축 표기
const user = {
name: 'Alice',
age: 30,

// 기존 방식
greet: function() {
return `안녕하세요, ${this.name}!`;
},

// 단축 메서드
greet() {
return `안녕하세요, ${this.name}!`;
},

// async 메서드
async fetchData() {
return await fetch('/api/data');
},

// getter/setter
get info() {
return `${this.name} (${this.age}세)`;
},

set info(value) {
[this.name, this.age] = value.split(',');
}
};

// 속성 단축 표기
const name = 'Alice';
const age = 30;

// 기존
const user1 = { name: name, age: age };

// 단축
const user2 = { name, age }; // 변수명과 키가 같으면 단축 가능

// 계산된 속성명
const prefix = 'get';
const dynamicObj = {
[`${prefix}Name`]() { return this.name; },
[`${prefix}Age`]() { return this.age; },
name: 'Bob',
age: 25
};

dynamicObj.getName(); // 'Bob'

for...of와 이터러블

// for...of: 이터러블 순회
const numbers = [1, 2, 3, 4, 5];

for (const num of numbers) {
console.log(num);
}

// 문자열도 이터러블
for (const char of 'hello') {
console.log(char); // h, e, l, l, o
}

// Map과 Set
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const [key, value] of map) {
console.log(key, value);
}

// for...in vs for...of
const arr = [10, 20, 30];
arr.custom = 'extra';

for (const key in arr) {
console.log(key); // '0', '1', '2', 'custom' (모든 열거 가능 속성)
}

for (const value of arr) {
console.log(value); // 10, 20, 30 (값만, 'custom' 없음)
}

// 인덱스가 필요하면 entries() 사용
for (const [index, value] of arr.entries()) {
console.log(index, value);
}

Symbol

// Symbol: 유일한 원시값
const sym1 = Symbol('설명');
const sym2 = Symbol('설명');
console.log(sym1 === sym2); // false - 항상 유일

// 객체 키로 사용 (충돌 방지)
const ID = Symbol('id');
const user = {
[ID]: 12345,
name: 'Alice'
};

user[ID]; // 12345
// JSON.stringify에 포함되지 않음
// for...in으로 열거되지 않음

// 전역 Symbol 레지스트리
const globalSym = Symbol.for('app.id');
const sameSym = Symbol.for('app.id');
console.log(globalSym === sameSym); // true

// Well-known Symbol
class Range {
constructor(start, end) {
this.start = start;
this.end = end;
}

[Symbol.iterator]() {
let current = this.start;
const end = this.end;
return {
next() {
if (current <= end) {
return { value: current++, done: false };
}
return { value: undefined, done: true };
}
};
}
}

for (const num of new Range(1, 5)) {
console.log(num); // 1, 2, 3, 4, 5
}

Map과 Set

// Map: 키-값 쌍 (객체와 달리 모든 타입이 키 가능)
const map = new Map();
map.set('name', 'Alice');
map.set(42, '숫자 키');
map.set({ id: 1 }, '객체 키');

map.get('name'); // 'Alice'
map.has('name'); // true
map.size; // 3
map.delete('name');
map.clear();

// 생성자로 초기화
const map2 = new Map([
['key1', 'value1'],
['key2', 'value2']
]);

// 순회
for (const [key, value] of map2) {
console.log(key, value);
}

// 객체를 Map으로
const obj = { a: 1, b: 2 };
const mapFromObj = new Map(Object.entries(obj));

// Set: 유일한 값의 컬렉션
const set = new Set([1, 2, 3, 2, 1]);
console.log([...set]); // [1, 2, 3] - 중복 제거

set.add(4);
set.has(3); // true
set.delete(1);
set.size; // 3

// 배열 중복 제거
const unique = [...new Set([1, 2, 3, 2, 1, 3])]; // [1, 2, 3]

// 교집합, 합집합, 차집합
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5, 6]);

const union = new Set([...a, ...b]); // 합집합
const intersection = new Set([...a].filter(x => b.has(x))); // 교집합
const difference = new Set([...a].filter(x => !b.has(x))); // 차집합

WeakMap과 WeakSet

// WeakMap: 키는 반드시 객체, 약한 참조 (GC 가능)
const cache = new WeakMap();

function processUser(user) {
if (cache.has(user)) {
return cache.get(user);
}

const result = expensiveComputation(user);
cache.set(user, result);
return result;
}

// user 객체가 다른 곳에서 참조가 없어지면 WeakMap에서도 자동 제거
// 메모리 누수 방지!

// WeakSet: 객체만 저장, 약한 참조
const visited = new WeakSet();

function visitPage(pageObj) {
if (visited.has(pageObj)) {
console.log('이미 방문한 페이지');
return;
}
visited.add(pageObj);
renderPage(pageObj);
}

// Map vs WeakMap
// Map: 키가 참조되지 않아도 GC 안 됨 (메모리 누수 가능)
// WeakMap: 키 객체가 GC되면 항목도 자동 삭제

고수 팁

1. 태그드 템플릿으로 DSL 만들기

// HTML 이스케이프
function safe(strings, ...values) {
const escaped = values.map(v =>
String(v).replace(/&/g, '&amp;').replace(/</g, '&lt;')
);
return strings.reduce((result, str, i) => result + (escaped[i-1] ?? '') + str);
}

const userInput = '<script>alert("xss")</script>';
const html = safe`<p>사용자 입력: ${userInput}</p>`;
// <p>사용자 입력: &lt;script&gt;alert("xss")&lt;/script&gt;</p>

2. Symbol.toPrimitive로 타입 변환 커스터마이징

const temperature = {
celsius: 100,
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.celsius;
if (hint === 'string') return `${this.celsius}°C`;
return this.celsius; // default
}
};

console.log(+temperature); // 100 (number hint)
console.log(`${temperature}`); // '100°C' (string hint)
console.log(temperature + 0); // 100 (default hint)

3. Proxy로 Map을 객체처럼 사용

function createReactiveObject(initial = {}) {
const data = new Map(Object.entries(initial));
const listeners = new Set();

return new Proxy({}, {
get(_, key) {
if (key === 'subscribe') return (fn) => listeners.add(fn);
return data.get(key);
},
set(_, key, value) {
data.set(key, value);
listeners.forEach(fn => fn(key, value));
return true;
}
});
}

const state = createReactiveObject({ count: 0 });
state.subscribe((key, val) => console.log(`${key} = ${val}`));
state.count = 1; // 'count = 1' 출력
state.count = 2; // 'count = 2' 출력
Advertisement