자바스크립트 최신 문법 총정리 및 실무 활용 사례 🚀
안녕, 자바스크립트 마스터가 되고 싶은 친구들! 오늘은 우리가 사랑하는 (때론 미워하는 😅) 자바스크립트의 최신 문법을 총정리하고, 실무에서 어떻게 활용하는지 알아볼 거야. 준비됐어? 그럼 출발~! 🏎️💨
잠깐! 이 글을 읽기 전에 알아둘 점이 있어. 우리가 다룰 내용은 꽤 방대하고 심도 있어. 하지만 걱정 마! 마치 친구가 옆에서 설명해주는 것처럼 쉽고 재미있게 풀어낼 거니까. 그리고 혹시 더 자세한 내용이 필요하다면, 재능넷(https://www.jaenung.net)의 '지식인의 숲'에서 추가 정보를 찾아볼 수 있다는 것도 알아둬!
1. ES6+ 문법의 핵심, 한 눈에 보기 👀
자, 이제 본격적으로 ES6+ 문법의 핵심을 살펴볼 거야. ES6부터 시작된 자바스크립트의 현대화 작업은 우리의 코딩 생활을 완전히 바꿔놓았지. 그럼 하나씩 알아보자!
1.1 let과 const: 변수 선언의 새로운 방식 🆕
var
로 시작했던 우리의 변수 선언 여정이 let
과 const
의 등장으로 새로운 국면을 맞이했어. 이 둘의 차이점을 명확히 알아두면 코딩 life가 한결 편해질 거야!
- let: 재할당 가능한 변수를 선언할 때 사용해.
- const: 한 번 할당하면 변경할 수 없는 상수를 선언할 때 사용해.
예를 들어볼까?
let age = 25;
age = 26; // 가능해!
const PI = 3.14159;
PI = 3.14; // 에러 발생! const는 재할당 불가능
let과 const의 가장 큰 특징은 블록 스코프를 가진다는 거야. 이게 무슨 말이냐고? 간단한 예제로 설명해줄게.
{
let x = 10;
const y = 20;
}
console.log(x); // ReferenceError: x is not defined
console.log(y); // ReferenceError: y is not defined
보이지? 블록 밖에서는 x와 y에 접근할 수 없어. 이런 특성 덕분에 우리는 더 안전하고 예측 가능한 코드를 작성할 수 있게 됐어.
🚨 주의사항: const
로 선언한 객체나 배열의 내부 값은 변경할 수 있어. const가 불변을 보장하는 건 그 변수에 다른 객체를 재할당하는 것을 막는 거지, 객체 내부의 속성 변경까지 막는 건 아니야!
const person = { name: "Alice" };
person.name = "Bob"; // 이건 가능해!
person = { name: "Charlie" }; // 하지만 이건 에러!
1.2 화살표 함수: 간결함의 극치 🏹
화살표 함수는 ES6에서 도입된 가장 인기 있는 기능 중 하나야. 기존의 함수 표현식을 훨씬 간결하게 만들어주지.
// 기존 함수 표현식
const greet = function(name) {
return `Hello, ${name}!`;
};
// 화살표 함수
const greet = (name) => `Hello, ${name}!`;
화살표 함수의 장점은 단순히 문법이 간결해지는 것뿐만이 아니야. this
바인딩 방식이 달라져서, 콜백 함수에서 this
를 사용할 때 발생하는 많은 혼란을 줄여줘.
예를 들어볼까?
const counter = {
count: 0,
increment: function() {
setInterval(() => {
console.log(++this.count);
}, 1000);
}
};
counter.increment();
이 코드에서 화살표 함수를 사용하지 않았다면, this.count
는 undefined
가 됐을 거야. 하지만 화살표 함수는 자신만의 this
를 만들지 않고 외부 스코프의 this
를 그대로 사용하기 때문에, 우리가 원하는 대로 동작해.
💡 팁: 화살표 함수를 사용할 때는 주의할 점도 있어. 메서드나 생성자 함수로는 적합하지 않아. 이런 경우에는 전통적인 함수 선언 방식을 사용하는 게 좋아.
1.3 템플릿 리터럴: 문자열 조합의 혁명 🎭
문자열을 다룰 때 가장 흔히 겪는 고통, 바로 여러 줄의 문자열을 만들거나 변수를 문자열에 삽입하는 거야. ES6의 템플릿 리터럴은 이 고통에서 우리를 해방시켜줬어!
const name = "Alice";
const age = 25;
// 기존 방식
console.log("My name is " + name + " and I'm " + age + " years old.");
// 템플릿 리터럴 사용
console.log(`My name is ${name} and I'm ${age} years old.`);
백틱(`)을 사용해서 문자열을 감싸고, ${} 안에 변수나 표현식을 넣으면 돼. 이렇게 하면 문자열 연결을 위해 + 연산자를 사용할 필요가 없어져.
여러 줄의 문자열도 쉽게 만들 수 있어:
const multiLine = `
This is a
multi-line
string.
`;
console.log(multiLine);
이전에는 이런 걸 만들려면 '\n'을 사용하거나 문자열을 여러 줄로 나눠서 연결해야 했는데, 이제는 그럴 필요가 없어졌지!
1.4 구조 분해 할당: 객체와 배열을 쉽게 다루자 🧩
구조 분해 할당(Destructuring assignment)은 객체나 배열의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 표현식이야. 이 기능을 사용하면 코드가 훨씬 간결해지고 가독성도 좋아져.
먼저 객체 구조 분해를 볼까?
const person = { name: "Bob", age: 30, job: "Developer" };
// 기존 방식
const name = person.name;
const age = person.age;
const job = person.job;
// 구조 분해 할당
const { name, age, job } = person;
console.log(name); // "Bob"
console.log(age); // 30
console.log(job); // "Developer"
배열도 구조 분해할 수 있어:
const colors = ["red", "green", "blue"];
// 기존 방식
const firstColor = colors[0];
const secondColor = colors[1];
// 구조 분해 할당
const [first, second] = colors;
console.log(first); // "red"
console.log(second); // "green"
구조 분해 할당은 함수의 매개변수로도 사용할 수 있어, API 응답을 처리할 때 특히 유용해.
function displayUser({ name, age, email }) {
console.log(`Name: ${name}, Age: ${age}, Email: ${email}`);
}
const user = { name: "Alice", age: 28, email: "alice@example.com", country: "USA" };
displayUser(user);
이 예제에서 displayUser
함수는 객체에서 필요한 속성만 골라서 사용하고 있어. country
속성은 무시되고 있지.
⚠️ 주의: 존재하지 않는 속성을 구조 분해하려고 하면 undefined
가 할당돼. 이를 방지하기 위해 기본값을 설정할 수 있어:
const { name, age, gender = "Unknown" } = person;
1.5 스프레드 연산자와 rest 파라미터: ...의 마법 ✨
스프레드 연산자(...
)는 ES6에서 도입된 강력한 기능 중 하나야. 이 연산자는 iterable 객체를 "펼쳐서" 개별 요소로 만들어줘. 배열, 문자열, 객체 등에 사용할 수 있지.
먼저 배열에서의 사용법을 볼까?
const fruits = ['apple', 'banana'];
const moreFruits = ['orange', ...fruits, 'grape'];
console.log(moreFruits); // ['orange', 'apple', 'banana', 'grape']
객체에서도 사용할 수 있어:
const person = { name: "Alice", age: 30 };
const employee = { ...person, job: "Developer" };
console.log(employee); // { name: "Alice", age: 30, job: "Developer" }
스프레드 연산자를 사용하면 배열이나 객체를 쉽게 복사하거나 합칠 수 있어. 특히 불변성(immutability)을 유지해야 할 때 유용하지.
한편, rest 파라미터는 스프레드 연산자와 비슷하게 생겼지만, 함수의 파라미터에서 사용돼. 여러 개의 인자를 배열로 모아주는 역할을 해.
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
이 예제에서 ...numbers
는 모든 인자를 배열로 모아주고 있어. 이렇게 하면 함수가 받을 수 있는 인자의 개수에 제한이 없어지지.
💡 팁: 스프레드 연산자와 rest 파라미터를 잘 활용하면, 함수형 프로그래밍 스타일의 코드를 작성하기가 훨씬 쉬워져. 특히 불변성을 유지하면서 데이터를 조작할 때 매우 유용해!
1.6 클래스: 객체 지향 프로그래밍의 새로운 문법 🏛️
ES6에서는 클래스 문법이 도입됐어. 이전에도 자바스크립트에서 객체 지향 프로그래밍이 가능했지만, 프로토타입 기반의 상속은 다른 언어에 익숙한 개발자들에게는 조금 낯설었거든. 클래스 문법은 이런 간극을 줄여주고, 더 직관적인 객체 지향 프로그래밍을 가능하게 해줬어.
간단한 클래스 예제를 볼까?
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
}
}
const alice = new Person("Alice", 25);
alice.sayHello(); // "Hello, my name is Alice and I'm 25 years old."
클래스를 사용하면 생성자, 메서드, 상속 등의 개념을 더 명확하게 표현할 수 있어. 예를 들어, 상속은 이렇게 구현할 수 있지:
class Employee extends Person {
constructor(name, age, job) {
super(name, age);
this.job = job;
}
introduce() {
super.sayHello();
console.log(`I work as a ${this.job}.`);
}
}
const bob = new Employee("Bob", 30, "Developer");
bob.introduce();
// "Hello, my name is Bob and I'm 30 years old."
// "I work as a Developer."
여기서 super
키워드는 부모 클래스의 생성자나 메서드를 호출할 때 사용돼.
🔍 깊이 들어가기: 클래스 문법은 결국 프로토타입 기반의 상속을 "설탕 문법(Syntactic sugar)"으로 감싼 거야. 내부적으로는 여전히 프로토타입을 사용하고 있지만, 개발자가 보기에는 훨씬 깔끔하고 이해하기 쉬운 형태로 바뀐 거지.
1.7 모듈 시스템: 코드 구조화의 새로운 방법 📦
ES6에서 도입된 모듈 시스템은 자바스크립트 코드를 더 효율적으로 구조화하고 관리할 수 있게 해줘. 이전에는 전역 네임스페이스 오염이나 의존성 관리 같은 문제들이 있었는데, 모듈 시스템으로 이런 문제들을 해결할 수 있게 됐지.
모듈을 만들고 내보내는 방법은 이래:
// math.js
export const PI = 3.14159;
export function square(x) {
return x * x;
}
export default function cube(x) {
return x * x * x;
}
그리고 이렇게 가져와서 사용할 수 있어:
// main.js
import cube, { PI, square } from './math.js';
console.log(PI); // 3.14159
console.log(square(4)); // 16
console.log(cube(3)); // 27
여기서 cube
는 default export로, 중괄호 없이 임의의 이름으로 import 할 수 있어. 반면 PI
와 square
는 named export로, 정확한 이름을 사용해 중괄호 안에 넣어 import 해야 해.
💡 모듈 시스템의 장점:
- 코드를 더 작고 관리하기 쉬운 단위로 나눌 수 있어.
- 의존성을 명확하게 표현할 수 있어.
- 전역 네임스페이스 오염을 방지할 수 있어.
- 코드 재사용성이 높아져.
모듈 시스템을 사용하면, 대규모 애플리케이션을 개발할 때 코드를 더 효율적으로 구조화하고 관리할 수 있어. 특히 프론트엔드 개발에서 컴포넌트 기반 아키텍처를 구현할 때 매우 유용하지.
1.8 Promise와 async/await: 비동기 처리의 혁명 🔄
자바스크립트에서 비동기 처리는 항상 중요한 주제였어. ES6에서 도입된 Promise와 이후 ES2017에서 추가된 async/await는 비동기 코드를 더 쉽고 깔끔하게 작성할 수 있게 해줬지.
먼저 Promise를 살펴볼까?
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { id: 1, name: "John Doe" };
if (data) {
resolve(data);
} else {
reject("Data not found");
}
}, 1000);
});
}
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
Promise는 비동기 작업의 최종 완료 또는 실패를 나타내는 객체야. then()
과 catch()
메서드를 통해 성공과 실패 상황을 처리할 수 있지.
하지만 Promise 체인이 길어지면 코드가 복잡해질 수 있어. 이런 문제를 해결하기 위해 async/await가 도입됐어:
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.error(error);
}
}
getData();
async/await를 사용하면 비동기 코드를 마치 동기 코드처럼 작성할 수 있어. 이렇게 하면 코드의 가독성이 크게 향상되고, 에러 처리도 더 직관적으로 할 수 있지.
🚀 실무 팁: async/await는 Promise를 기반으로 동작해. 따라서 Promise를 반환하는 함수라면 모두 await와 함께 사용할 수 있어. 예를 들어, fetch API나 데이터베이스 쿼리 같은 비동기 작업을 다룰 때 매우 유용하지!
이런 비동기 처리 방식은 특히 웹 개발에서 중요해. API 호출, 파일 읽기/쓰기, 데이터베이스 작업 등 시간이 걸리는 작업을 처리할 때 코드의 실행을 블로킹하지 않고 효율적으로 처리할 수 있거든.
1.9 Map과 Set: 새로운 데이터 구조 🗺️
ES6에서는 Map과 Set이라는 새로운 데이터 구조가 도입됐어. 이들은 기존의 객체와 배열을 보완하는 역할을 해.
먼저 Map을 볼까?
const userRoles = new Map();
userRoles.set('John', 'admin');
userRoles.set('Jane', 'editor');
userRoles.set('Bob', 'subscriber');
console.log(userRoles.get('John')); // 'admin'
console.log(userRoles.has('Jane')); // true
console.log(userRoles.size); // 3
userRoles.delete('Bob');
console.log(userRoles.size); // 2
Map은 키-값 쌍을 저장하는 객체와 비슷하지만, 몇 가지 장점이 있어:
- 키로 어떤 타입의 값이든 사용할 수 있어 (객체도 가능!)
- 삽입 순서를 기억해
- size 속성으로 쉽게 크기를 알 수 있어
- iteration이 더 쉬워
이제 Set을 볼까?
const uniqueNumbers = new Set([1, 2, 3, 4, 4, 5]);
console.log(uniqueNumbers); // Set(5) { 1, 2, 3, 4, 5 }
uniqueNumbers.add(6);
console.log(uniqueNumbers.has(4)); // true
console.log(uniqueNumbers.size); // 6
uniqueNumbers.delete(3);
console.log(uniqueNumbers.size); // 5
Set은 중복을 허용하지 않는 값들의 집합이야. 배열과 비슷하지만, 각 값은 단 한 번만 등장할 수 있어.
💡 실무 활용 팁: Set은 배열에서 중복을 제거할 때 매우 유용해!
const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
Map과 Set은 특히 대량의 데이터를 다룰 때 성능상 이점이 있어. 객체나 배열보다 더 효율적으로 데이터를 추가, 삭제, 검색할 수 있지.
1.10 Symbol: 새로운 원시 타입 🔣
ES6에서 도입된 Symbol은 자바스크립트의 7번째 원시 타입이야. Symbol의 주요 특징은 각 Symbol 값이 고유하다는 거야.
const sym1 = Symbol('my symbol');
const sym2 = Symbol('my symbol');
console.log(sym1 === sym2); // false
Symbol은 주로 객체의 고유한 속성 키를 만들 때 사용돼. 이를 통해 속성 이름의 충돌을 방지할 수 있지.
const MY_KEY = Symbol();
const obj = {
[MY_KEY]: 'Hello, Symbol!'
};
console.log(obj[MY_KEY]); // 'Hello, Symbol!'
Symbol은 또한 내장 Symbol들을 통해 자바스크립트의 내부 동작을 커스터마이즈할 수 있게 해줘. 예를 들어, Symbol.iterator
를 사용하면 객체를 이터러블(iterable)하게 만들 수 있어:
const myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
for (let value of myIterable) {
console.log(value);
}
// 1
// 2
// 3
🔍 깊이 들어가기: Symbol은 완전히 private하지는 않아. Object.getOwnPropertySymbols()
메서드를 사용하면 객체의 모든 Symbol 속성을 가져올 수 있어. 하지만 일반적인 방법으로는 접근할 수 없기 때문에, 어느 정도의 프라이버시를 제공한다고 볼 수 있지.
2. 실무에서의 ES6+ 활용 사례 💼
자, 이제 우리가 배운 이 멋진 기능들을 실제 프로젝트에서 어떻게 활용할 수 있는지 살펴볼 시간이야. 실제 개발 시나리오를 통해 ES6+ 문법의 강력함을 체험해보자!
2.1 리액트 컴포넌트 작성하기 ⚛️
리액트는 현대 웹 개발에서 가장 인기 있는 라이브러리 중 하나야. ES6+의 기능들은 리액트 컴포넌트를 작성할 때 정말 유용하게 사용돼. 예를 들어볼까?
import React, { useState, useEffect } from 'react';
const UserProfile = ({ userId }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUser = async () => {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (error) {
console.error('Failed to fetch user:', error);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]);
if (loading) return <div>Loading...</div>;
if (!user) return <div>User not found</div>;
const { name, email, role } = user;
return (
<div>
<h2>{name}</h2>
<p>Email: {email}</p>
<p>Role: {role}</p>
</div>
);
};
export default UserProfile;
이 예제에서 우리는 여러 ES6+ 기능을 사용하고 있어:
- 화살표 함수로 컴포넌트를 정의했어.
- 구조 분해 할당으로 props와 state를 간편하게 사용했지.
- async/await로 API 호출을 깔끔하게 처리했어.
- 템플릿 리터럴로 동적 URL을 생성했고.
- 단축 속성명으로 JSX 내에서 변수를 간단히 사용했어.