본문 바로가기

Language/Javascript

[ES6+] ES7 ~ ES13 추가 기능

728x90
ES6+
  • ES6+는 이전 포스팅에서도 언급했지만, ECMAScript2015(ES6) 이후의 버전들을 묶어서 부르는 것

 

ES7 (ECMAScript 2016)
Array.prototype.includes
  • 배열에 특정 요소가 포함되어 있는 지 확인하는 메서드
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false

 

지수 연산자
  • Math.pow()를 더 간결한 문법으로 제공
2 ** 3; // 8 == (2^3) == Math.pow(2,3)

 

ES7 (ECMAScript 2016)
async와 await
  • 비동기 프로그래밍을 더 간결하게 처리할 수 있는 키워드
async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}
Object.entries
  • 객체를 키-값 쌍의 배열로 변환 [ 객체 → 배열 ]
  • 반환 형태 : [ [key1, value1], [key2, value2], ... ]
  • 주요 용도
    • 객체를 배열 형태로 변환해 반복(iteration)을 쉽게 처리
    • Map 자료구조와 상호 변환 가능
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.entries(obj));
// [ ['a', 1], ['b', 2], ['c', 3] ]

// 반복문에서 사용
for (const [key, value] of Object.entries(obj)) {
  console.log(`${key}: ${value}`);
}
// a: 1
// b: 2
// c: 3
Object.values
  • 객체의 값만 배열 변환 [객체의 값 → 배열]
  • 반환 형태 : [value1, value2, value3, ...]
  • 주요 용도
    • 객체의 값만 필요한 경우에 유용
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.values(obj));
// [1, 2, 3]

 

문자열 패딩 (padStart, padEnd)
  • 문자열의 시작 또는 끝에 지정된 길이만큼 문자를 추가
console.log('123'.padStart(5, '0')); // '00123'
console.log('123'.padEnd(5, '0'));   // '12300'
console.log('12345'.padStart(5, '0')); // 12345
ECMAScript 2018 (ES9)
Rest/Spread 연산자 (객체에서의 지원)
  • 객체에 대한 분해와 병합을 더 간단하게 처리
  • ES6에서의 Rest와 Spread 연산자는 배열에서만 지원이 되었었지만, ES9부터는 객체에도 적용이 가능하게 됨
// 객체 복사
const obj1 = {
    name: '철수',
    age: 25
};
const obj2 = { ...obj1 }; // obj1의 복사본 생성
console.log(obj2); // { name: '철수', age: 25 }

// 객체 결합
const obj1 = {
	name: '철수'
};
const obj2 = {
	age: 25
};
const combined = { ...obj1, ...obj2 };
console.log(combined); // { name: '철수', age: 25 }

// 객체 속성 덮어쓰기
const obj1 = {
    name: '철수',
    age: 20
};
const obj2 = {
    age: 25,
    city: '서울'
};
const merged = { ...obj1, ...obj2 };
console.log(merged); // { name: '철수', age: 25, city: '서울' }

ES6와 ES9의 차이점 비교

특징 ES6의 Rest/Spread ES9의 Rest/Spread
도입된 버전 ES6(2015) ES9(2018)
사용 대상 배열
(Rest = 매개변수 수집, Spread = 펼치기)
객체
(Rest = 속성 제외, Spread = 복사/병합)
Rest 연산자 용도 함수의 인자를 배열로 수집 객체의 일부 속성을 분리하거나 추출
Spread 연산자 용도 배열을 병합하거나 함수 호출 시 인자 전달 객체를 복사하거나 병합

비동기 반복문 (for await...of)
  • 비동기 iterable 객체를 순회하는 반복문
async function* asyncGenerator() {
  yield 'a';
  yield 'b';
}

(async () => {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
})();
정규 표현식 개선
  • Named Capturing Groups (이름 있는 캡처 그룹)
  • ES9 이전 방식
    • 정규식의 캡처 그룹은 숫자 기반으로 참조했기 때문에 캡처된 값이 많아지면 코드의 가독성이 떨어졌음
    • 캡처된 값은 배열 인덱스로만 접근 가능했으며 어떤 그룹이 어떤 데이터를 나타내는지 파악하기 어려웠음
  • ES9 이후 방식
    • 캡처 그룹에 이름을 붙일 수 있어, 결과를 객체 형태로 반환받아 가독성과 유지보수가 용이
    • 그룹 이름은 ?<name> 형식으로 정의
    • groups는 예약된 속성명
      → regex.exec() 메서드의 결과로 반환되는 match 객체에 자동으로 추가되며, 이름 있는 캡처 그룹에서 정의한 이름들(?<year>, ?<month>, ?<day>)이 groups 객체의 속성으로 들어감
// ES9 이전 방식
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const match = regex.exec("2024-12-25");

console.log(match[0]); // 전체 매칭 결과: "2024-12-25"
console.log(match[1]); // 첫 번째 캡처 그룹: "2024"
console.log(match[2]); // 두 번째 캡처 그룹: "12"
console.log(match[3]); // 세 번째 캡처 그룹: "25"

// ES9 이후 방식
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
// const match = regex.exec("2024-12-25");
const { groups: { year, month, day } } = regex.exec('2024-12-25');

// console.log(match.groups.year);  // "2024"
// console.log(match.groups.month); // "12"
// console.log(match.groups.day);   // "25"
console.log(year, month, day); // 2024 12 25
  • Lookbehind Assertions (후방 탐색 어설션)
  • ES9 이전 방식
    • 후방 탐색이 지원되지 않았음
    • 문자열의 앞에 특정 패턴이 존재하는 경우를 확인하려면 우회 방법을 사용
      → 문자열을 순서를 반대로 뒤집고 lookahead를 사용하는 등의 복잡한 방식으로 처리
  • ES9 이후 방식
    • 후방 탐색을 통해 문자열 앞에 특정 패턴이 존재하는지 확인이 가능
    • (?<=...) : 긍정 후방 탐색. 지정한 패턴이 앞에 있을 때만 매칭
    • (?<!...) : 부정 후방 탐색. 지정한 패턴이 앞에 없을 때만 매칭
// ES9 이전 방식
const text = "price: $123";
const regex = /\$\d+/; // '$'로 시작하는 숫자
const match = regex.exec(text);

console.log(match[0]); // "$123"
// 특정 텍스트가 '$' 뒤에 오는지 확인하는 데 불편함이 많음

// ES9 이후 방식
// 긍정 후방 탐색
const regexPositive = /(?<=\$)\d+/; // '$' 뒤에 있는 숫자
const matchPositive = regexPositive.exec("price: $123"); // 123

console.log(matchPositive[0]); // "123"

// 부정 후방 탐색
const regexNegative = /(?<!\$)\d+/; // '$' 뒤에 오지 않는 숫자
const matchNegative = regexNegative.exec("price : $123"); // 23

console.log(matchNegative[0]); // "123"
ECMAScript 2019 (ES10)
Array.prototype.flat
  • 중첩된 배열 평평하게(1차원으로) 만듦
  • 기본적으로 한 단계만 평탄화하지만, 옵션으로 평탄화 깊이를 지정 가능
  • depth(선택) : 평탄화할 깊이를 나타내는 숫자 (기본값 1)
array.flat([depth]);
// 기본 사용
const arr = [1, 2, [3, 4, [5, 6]]];
console.log(arr.flat()); // [1, 2, 3, 4, [5, 6]]

// 여러 단계 평탄화
console.log(arr.flat(2)); // [1, 2, 3, 4, 5, 6]

// 무한 깊이 평탄화 (Infinity 사용)
const deeplyNested = [1, [2, [3, [4, [5]]]]];
console.log(deeplyNested.flat(Infinity)); // [1, 2, 3, 4, 5]

// 빈 항목 제거
const arrWithHoles = [1, 2, , 4, , , 7];
console.log(arrWithHoles.flat()); // [1, 2, 4, 7]
Array.prototype.flatMap
  • map() 메서드로 배열을 변환한 뒤 결과를 평탄화
    즉, 한 번에 변환(mapping)과 평탄화(flattening)를 수행
  • callback : 배열의 각 요소에 대해 실행할 함수
  • thisArg(선택) : callback 함수 내에서 this로 사용할 값
array.flatMap(callback(currentValue[, index[, array]])[, thisArg]);
// map()과 flat()을 함께 사용한 경우
const arr = [1, 2, 3, 4];
console.log(arr.map(x => [x, x * 2]).flat()); // [1, 2, 2, 4, 3, 6, 4, 8]

// flatMap()으로 간단히 표현
console.log(arr.flatMap(x => [x, x * 2])); // [1, 2, 2, 4, 3, 6, 4, 8]

// 텍스트 쪼개기와 평탄화
const sentences = ["hello world", "flatMap is great"];
console.log(sentences.flatMap(sentence => sentence.split(" ")));
// ["hello", "world", "flatMap", "is", "great"]

// 빈 배열 처리
const nested = [1, 2, , 4];
console.log(nested.flatMap(x => (x ? [x] : []))); // [1, 2, 4]

flat과 flatMap의 차이점 비교

특징 flat() flatMap()
기능 배열 평탄화 매핑 후 평탄화
평탄화 깊이 지정 가능 (기본값 : 1) 항상 1단계
조합 여부 flat()과 다른 메서드(map)를 조합해야 함 한 번에 변환 및 평탄화 수행
사용 사례 다중 배열 구조를 단순화 데이터 변환 후 평탄화

 

Object.fromEntries
  • 키-값 쌍의 배열 또는 이터러블 객체로 변환 [ 배열 → 객체 ]
  • 입력 형태 : [ [key1, value1], [key2, value2], ... ] 또는 이와 유사한 이터러블
  • 주요 용도
    • Object.entries의 반대 작업
    • 배열 데이터를 기반으로 객체를 동적으로 생성

 

const entries = [['a', 1], ['b', 2], ['c', 3]];
console.log(Object.fromEntries(entries));
// { a: 1, b: 2, c: 3 }

// Map -> Object 변환
const map = new Map([['a', 1], ['b', 2]]);
console.log(Object.fromEntries(map));
// { a: 1, b: 2 }

 

ES7의 Object.entries, Object.values와 ES10의 Object.fromEntries 차이점 정리

특징 Object.entries (ES7) Object.values (ES7) Object.fromEntries (ES10)
동작 객체 → 배열 (키-값 쌍) 객체 → 배열 (값만) 배열/이터러블 → 객체
반환 형태 [ [key1, value1], [key2, values2], ... ] [values1, values2, ...] { key1 : value1, key2 : value2, ... }
주요 용도 객체 반복 및 배열화 객체 값 추출 배열을 객체로 변환
응용 예시 Map과 상호 변환, 정렬 등 합계 계산 등 동적으로 객체 생성

 

Object.entries + Object.fromEntries 응용

const obj = { b: 2, a: 1, c: 3 };

// 1. 객체를 배열로 변환 후 정렬
const sortedEntries = Object.entries(obj).sort((a, b) => a[0].localeCompare(b[0]));
console.log(sortedEntries);
// [ ['a', 1], ['b', 2], ['c', 3] ]

// 2. 배열을 객체로 변환
const sortedObj = Object.fromEntries(sortedEntries);
console.log(sortedObj);
// { a: 1, b: 2, c: 3 }

String.prototype.trimStart와 trimEnd
  • 문자열 앞/뒤 공백 제거
'   hello   '.trimStart(); // 'hello   '
'   hello   '.trimEnd();   // '   hello'
Optional Catch Binding
  • catch에서 매개변수 생략 가능
try {
  throw new Error('error');
} catch {
  console.error('An error occurred.');
}
ECMAScript 2020 (ES11)
Optional Chaining (?.)
  • 객체가 null/undefined인지 확인하며 안전하게 접근
const obj = { a: { b: 2 } };
console.log(obj?.a?.b); // 2
console.log(obj?.c?.d); // undefined
Nullish Coalescing (??)

 

  • null 또는 undefined일 때만 기본값 사용하는 논리 연산자
  • Nullish Coalsescing Operator 작동 방식
    • A ?? BA가 null 또는 undefined인 경우 B를 반환
    • 만약 A가 null도 아니고 undefined도 아니면, A를 그대로 반환
    • 논리 연산자 ||와 유사하지만 false, 0, '' (falsy 값)은 기본값으로 간주하지 않는 점에서 차이를 보임
const value1 = null ?? 'default'; // A가 null이므로 B를 반환
console.log(value1); // "default"

const value2 = undefined ?? 'default'; // A가 undefined이므로 B를 반환
console.log(value2); // "default"

const value3 = 0 ?? 'default'; // A가 0 (falsy 값)지만 nullish 값이 아니므로 A를 반환
console.log(value3); // 0

const value4 = '' ?? 'default'; // A가 빈 문자열(falsy 값)지만 nullish 값이 아니므로 A를 반환
console.log(value4); // ""

 

  • 기본값 설정
function greet(name) {
  // name이 nullish 값(null, undefined)일 때만 기본값 사용
  const userName = name ?? 'Guest';
  console.log(`Hello, ${userName}!`);
}

greet('Alice'); // "Hello, Alice!"
greet(null);    // "Hello, Guest!"
greet();        // "Hello, Guest!"
  • 객체 속성 읽기
const config = {
  port: 0,
  host: undefined,
};

const port = config.port ?? 3000; // 0은 nullish 값이 아니므로 그대로 사용
const host = config.host ?? 'localhost'; // undefined이므로 기본값 사용

console.log(port); // 0
console.log(host); // "localhost"

falsy 값이란?
  • JavaScript에서 조건문에서 false로 평가되는 값
  • 이는 Boolean 컨텍스트(논리적 평가)에서 false처럼 작동
  • JavaScript에서 falsy로 간주되는 값들
    • false
    • 0 (숫자 0)
    • -0 (음수 0)
    • 0n (BigInt 0)
    • "" (빈 문자열)
    • null
    • undefined
    • NaN (Not-a-Number)
  • falsy가 아닌 모든 값은 truthy로 간주
if (0) {
  console.log("참입니다");
} else {
  console.log("거짓입니다");
}
// 출력: "거짓입니다"

if ("") {
  console.log("참입니다");
} else {
  console.log("거짓입니다");
}
// 출력: "거짓입니다"

console.log(Boolean(0));        // false
console.log(Boolean(""));       // false
console.log(Boolean(null));     // false
console.log(Boolean(undefined));// false
console.log(Boolean(NaN));      // false

 

BigInt
  • 큰 정수를 처리하는 데이터 타입
const big = 123456789012345678901234567890n;
console.log(big + 1n); // 123456789012345678901234567891n

 

 

Dynamic Imports
  • 동적으로 모듈을 로드
  • 기존 정적 import
    • 컴파일 시점에 모듈이 로드
    • 항상 상단에 선언
    • 모듈을 조건적으로 가져올 수 없음
import { add } from './math.js'; // 정적 import
console.log(add(2, 3));
  • 동적 import
    • 런타임 시점에 모듈을 로드
    • 필요할 때만 모듈을 로드 가능
    • 함수 형태로 호출되며, Promise를 반환
// 동적 import
import('./math.js').then((module) => {
  console.log(module.add(2, 3)); // 5
});
  • Dynamic Import 문법
import(modulePath)
  .then((module) => {
    // 모듈 사용
  })
  .catch((error) => {
    // 에러 처리
  });
  • 조건부 로드
    • 사용자의 언어나 환경에 따라 특정 모듈만 로드 가능
const loadLanguage = async (language) => {
  if (language === 'en') {
    const enModule = await import('./lang/en.js');
    console.log(enModule.default.greeting); // "Hello"
  } else if (language === 'ko') {
    const koModule = await import('./lang/ko.js');
    console.log(koModule.default.greeting); // "안녕하세요"
  }
};

loadLanguage('ko');
  • 코드 분할
    • 애플리케이션의 특정 기능이 자주 사용되지 않는다면 해당 기능을 동적으로 로드하여 초기 로딩 시간을 감소시킬 수 있음
document.getElementById('loadChart').addEventListener('click', async () => {
  const { renderChart } = await import('./chart.js');
  renderChart(); // 차트 렌더링 실행
});
  • Dynamic Import의 이점
    • 코드 스플리팅 : 필요한 시점에만 코드를 로드하여 초기 번들 크기를 줄임
    • 조건부 로드 : 특정 조건에 따라 모듈을 로드 가능
    • Lazy Loading : 사용자가 요청했을 때만 모듈을 로드하여 성능 최적화
    • ES 모듈과 호환 : Promise를 반환하므로 비동기 처리와 연계하기 쉬움
  • Dynamic Import의 제한사항
    • Dynamic Imports는 Promise 기반이므로 비동기 처리를 고려
    • 반드시 경로를 문자열로 제공해야 하며 상대 경로를 지정해야 함
    • Webpack 등의 번들러를 사용할 경우 코드 분할 지원이 필요

 

ECMAScript 2021 (ES12)
String.prototype.replaceAll
  • 모든 매칭된 문자열을 교체
'abcabc'.replaceAll('a', 'x'); // 'xbcxbc'
Logical Assignment Operators
  • 논리 연산과 할당을 결합한 연산자
  • OR 할당 연산자 → ||= : 좌항의 값이 falsy(거짓 같은 값)일 경우에만 우항의 값을 할당
// ES12 이전 방식
let a = 0;
if (!a) {
  a = 42;
}

// ES12 이후 방식
let a = 0;
a ||= 42; // a가 falsy 값이므로 42가 할당됨
console.log(a); // 42

let b = "Hello";
b ||= "World"; // b가 truthy 값이므로 기존 값을 유지
console.log(b); // "Hello"
  • AND 할당 연산자 &&= : 좌항의 값이 truthy(참 같은 값)일 경우에만 우항의 값을 할당
// ES12 이전 방식
let a = 1;
if (a) {
  a = 42;
}

// ES12 이후 방식
let a = 1;
a &&= 42; // a가 truthy 값이므로 42가 할당됨
console.log(a); // 42

let b = 0;
b &&= "World"; // b가 falsy 값이므로 기존 값을 유지
console.log(b); // 0
  • Nullish 할당 연산자 ??= : 좌항의 값이 null 또는 undefined일 경우에만 우항의 값을 할당
// ES12 이전 방식
let a = null;
if (a === null || a === undefined) {
  a = 42;
}

// ES12 이후 방식
let a = null;
a ??= 42; // a가 null이므로 42가 할당됨
console.log(a); // 42

let b = 0;
b ??= "default"; // b가 null 또는 undefined가 아니므로 기존 값을 유지
console.log(b); // 0
Numeric Separators
  • 숫자 리터럴에 _를 사용해 가독성 개선
const largeNumber = 1_000_000; // 1000000

 

Promise.any
  • Promise 객체를 처리하는 새로운 메서드로 여러 Promise 중 가장 첫 번째 성공한 Promise를 반환
  • 모든 Promise가 실패(reject)할 경우 모든 실패 이유를 포함한 AggregateError 객체를 반환
  • 기존 메서드와 비교
    • Promise.all
      • 모든 Promise가 성공해야 결과를 반환
      • 하나라도 실패하면 전체가 실패로 처리
    • Promise.race
      • 성공 여부와 관계없이 가장 빨리 처리된 Promise의 결과를 반환
    • Promise.any (ES12)
      • 첫 번째로 성공한 Promise의 결과를 반환
      • 모든 Promise가 실패하면 AggregateError 반환
Promise.all([p1, p2, p3])
  .then((result) => console.log(result))
  .catch((error) => console.error(error)); // 하나라도 실패하면 전체 실패

Promise.race([p1, p2, p3])
  .then((result) => console.log(result)) // 가장 빨리 처리된 Promise 반환 (성공, 실패 무관)
  .catch((error) => console.error(error));

Promise.any([p1, p2, p3])
  .then((result) => console.log(result)) // 가장 먼저 성공한 Promise 반환
  .catch((error) => console.error(error)); // 모든 Promise가 실패한 경우만 에러
  • 예제1) 첫 번째로 성공한 Promise 반환
    • Promise.any는 가장 먼저 성공한 p2의 결과 "Result 2"를 반환
    • p1이 실패했더라도 상관하지 않음
const p1 = Promise.reject("Error 1");
const p2 = new Promise((resolve) => setTimeout(() => resolve("Result 2"), 100));
const p3 = new Promise((resolve) => setTimeout(() => resolve("Result 3"), 200));

Promise.any([p1, p2, p3])
  .then((result) => console.log(result)) // "Result 2"
  .catch((error) => console.error(error));
  • 예제2) 모든 Promise가 실패한 경우
    • 모든 Promise가 실패했으므로 catch 블록에서 AggregateError가 반환
    • AggregateError.errors 속성을 통해 모든 실패 이유를 확인 가능
const p1 = Promise.reject("Error 1");
const p2 = Promise.reject("Error 2");

Promise.any([p1, p2])
  .then((result) => console.log(result))
  .catch((error) => {
    console.error(error.name); // "AggregateError"
    console.error(error.message); // "All Promises were rejected"
    console.error(error.errors); // ["Error 1", "Error 2"]
  });
  • 예제3) 빠른 응답을 위한 적용
    • Promise.any는 여러 소스에서 데이터를 받아오는 경우 가장 먼저 처리되는 결과를 사용하는 데 유용
    • 가장 빠르게 응답한 서버의 결과(fetchFromServer2)를 반환
    • 느린 서버 응답이나 실패한 요청은 무시
const fetchFromServer1 = new Promise((resolve) =>
  setTimeout(() => resolve("Server 1 Response"), 300)
);
const fetchFromServer2 = new Promise((resolve) =>
  setTimeout(() => resolve("Server 2 Response"), 100)
);
const fetchFromServer3 = Promise.reject("Server 3 Failed");

Promise.any([fetchFromServer1, fetchFromServer2, fetchFromServer3])
  .then((result) => console.log(result)) // "Server 2 Response"
  .catch((error) => console.error(error));

 

 

ECMAScript 2022 (ES13)
Class Fields
  • 클래스 필드를 선언하는 새로운 문법
  • ES13 이전의 클래스는 필드 선언이 없었고 필드를 선언하려면 반드시 생성자 내부에서 this 키워드를 통해 초기화하였으나
    필드 선언이 가능하게 되었고 선언된 필드는 기본적으로 public
  • 필드 앞에 #을 붙이게 되면 private로 선언되고 접근 시도시 SyntaxError 발생
class MyClass {
  field = 'value';
  #privateField = 'private';
}

 

Array.prototype.at
  • 배열의 마지막 또는 특정 인덱스를 간단히 접근
const arr = [1, 2, 3];
console.log(arr.at(-1)); // 3

 

 

728x90