• 기술
  • JavaScript

ECMAScript 버전 훑어보기

작성자 프로필이미지문정민
··5분 읽기

무슨 글을 쓰려고 하나요?🔗

자바스크립트는 제가 가장 사랑하는 언어이자 모던 웹의 베이스입니다. 이 자바와 이름이 비슷하여 자바스크립트는 오해도 많이 받고, 브라우저 내에서만 사용되던 비운의 언어였지만, 현재는 그 어떤 언어보다 많은 사랑을 받고, 모든 플랫폼을 아우르는 언어로 성장했습니다.

그 배경에는 스크립트 구현체인 JavaScript의 표준이자 규칙인 ECMAScript의 발전이 있었기 때문입니다. 이 글에서는 ECMAScript의 역사를 간략하게 살펴보려 합니다. 과거 버전부터 최신 버전까지, 무엇이 변하고 어떻게 발전해왔는지 함께 살펴봅시다.

ES1, ES2, ES3🔗

탄생과 성장

1997년, JavaScript는 자신의 표준인 ES1을 세상에 선보였습니다. 최초의 ECMAScript는 자바스크립트의 기본 구조를 담고 있었지만, 아직 개선할 점이 많았습니다. 그러나 이 버전이 가진 중요한 의미는, 표준을 통해 프로그래밍 언어의 기반을 닦아준 점입니다.

ES2는 1998년에, ES3는 1999년에 각각 출시되었습니다. ES3에서는 정규 표현식과 try/catch 등 중요한 개념들이 도입되어, 오류 처리와 데이터 검증에 있어서 큰 발전이 있었습니다. 하지만 이런 중요한 추가사항에도 불구하고, 여전히 JavaScript는 더 많은 기능이 필요했습니다.

ES4🔗

야심차지만 빛을 보지못한 버전

ES4는 거창한 계획을 세웠지만, 결국 세상에 공개되지 못하였습니다. 이 버전에서는 타입, 모듈 등 많은 새로운 기능을 추가하려 했지만, 그러한 변화가 너무 컸기 때문에 결국 이는 취소되었습니다. 그러나 이 과정에서 나온 아이디어들은 이후 버전들에게 많은 영향을 끼쳤습니다.

ES5🔗

안정화와 기능의 보완

ECMAScript 5, 흔히 ES5로 알려져 있습니다,는 2009년에 승인되어 널리 사용되고 있으며, ECMAScript의 큰 표준 업데이트 중 하나입니다. 이 버전에서 몇 가지 중요한 기능이 추가되었으며, 아래에 주요 기능에 대해 상세히 설명하겠습니다.

'use strict'🔗

ES5에서 도입된 'use strict'는 코드에 더 엄격한 오류 검사를 적용하는 방법입니다. 이 모드는 JavaScript에 안전하지 않은 동작을 금지하므로, 실수로 전역 변수를 생성하거나, 삭제할 수 없는 속성을 삭제하려고 시도하거나, 변수 또는 매개변수 이름을 중복 사용하는 등의 실수를 방지할 수 있습니다.

JSON 지원🔗

ES5는 JSON 데이터를 직접적으로 처리할 수 있도록 JSON.stringify()와 JSON.parse() 메소드를 도입했습니다. 이전 버전에서는 JSON 데이터를 처리하려면 외부 라이브러리가 필요했지만, ES5에서는 기본적으로 지원하기 때문에 JSON 데이터의 인코딩과 디코딩이 훨씬 간단해졌습니다.

Array 메소드🔗

ES5는 배열 처리를 위한 많은 새로운 메소드를 도입했습니다. 예를 들어, .forEach(), .map(), .filter(), .reduce(), .every(), .some() 등의 메소드를 이용하면, 배열을 다루는 작업이 훨씬 간단해지고 코드의 가독성이 향상됩니다.

Object.defineProperty()🔗

Object.defineProperty()는 객체의 새로운 속성을 직접 정의하거나 이미 있는 객체의 속성을 수정할 수 있게 해주는 메소드입니다. 이 메소드를 이용하면 객체 속성의 get, set 액세서를 설정하거나, 속성을 열거할 수 없도록 설정하는 등의 세부적인 객체 속성 설정이 가능해졌습니다.

ES6/ES2015🔗

이 버전은 JavaScript의 가장 유명한 업데이트입니다. 이전 버전과 비교해서 혁신적인 변화가 많이 일어났고, 이 업데이트 덕분에 편하고 더 안전하게 프로그래밍할 수 있게 된 부분이 많습니다. 다음은 ES6에서 도입된 주요 기능들입니다.

let과 const🔗

ES6에서는 'let'과 'const' 두 가지 새로운 변수 선언 키워드가 도입되었습니다. 'var'와 달리 'let'과 'const'는 블록 스코프를 가지므로, 코드의 가독성과 예측 가능성을 향상시키고 실수를 줄이는데 큰 도움이 됩니다.

화살표 함수 (Arrow Functions)🔗

ES6는 또한 화살표 함수를 도입했습니다. 화살표 함수는 간결한 문법을 제공합니다. 화살표함수의 도입은 복잡한 'this' 개념을 단순화하여 this 바인딩에 대한 혼란을 줄일 수 있게 되었습니다. 일반 함수와 다르게, 화살표 함수는 자신만의 'this'를 생성하지 않으며, 상위 스코프의 'this'를 사용합니다.

클래스 (Classes)🔗

ES6에서 클래스는 객체 지향 패턴을 JavaScript에 더 쉽게 적용할 수 있도록 하는데 있어 핵심적인 역할을 합니다. 이전 버전에서는 복잡한 프로토타입 기반 상속 패턴을 사용해야 했지만, ES6의 클래스를 이용하면 보다 간결하고 직관적인 문법으로 객체 지향 프로그래밍을 구현할 수 있습니다.

Promise🔗

Promise는 비동기 처리를 보다 편리하게 할 수 있도록 도와주는 객체입니다. 이전 버전에서는 비동기 로직을 처리하기 위해 콜백 함수를 사용했지만, 이는 코드가 복잡해지고 가독성이 떨어지는 콜백 지옥을 만들 수 있습니다. ES6의 Promise를 이용하면 비동기 로직을 체인으로 연결하여 간결하고 가독성 좋은 코드를 작성할 수 있습니다.

ES7/ES2016🔗

ES7은 큰 변화를 가져오진 않았지만, 지수 연산자(**)가 추가되었고 Array.prototype.includes 라는 사랑받는 함수도 추가되었습니다.

예를 들어, 이전에는 배열에서 특정 요소의 존재를 확인하기 위해 indexOf를 사용했지만, 이것은 값이 배열에 없을 때 -1을 반환하는 하기 때문에 평가후 비교에 번거로움이 있었습니다. 하지만 이번 버전에서 도입된 includes 메소드를 사용하면, 특정 요소의 존재 여부를 true/false 값으로 간단하게 확인할 수 있게 되었습니다.

ES8/ES2017🔗

Async/Await의 등장

ES8에서는 async/await가 도입되어 비동기 처리를 더욱 편리하게 만들어주었습니다. ES6에서 도입된 Promise는 비동기 처리를 더욱 쉽게 만들어주었지만, 여전히 then 체이닝이 복잡해지고 에러 처리가 어려운 단점이 있었습니다. async/await는 이러한 문제를 해결하고, 비동기 코드를 마치 동기 코드처럼 간결하게 작성할 수 있도록 도와주었습니다.

ES9/ES2018🔗

Rest, Spread 연산자와 비동기 이터레이션

ES9에서는 여러가지 흥미로운 기능들이 추가되었습니다. 그 중에서도 restspread 연산자의 도입은 객체와 배열을 다루는 방법을 바꾸어 주었습니다.

spread 연산자를 사용하면 배열이나 객체의 요소를 쉽게 복사하거나 병합할 수 있습니다. 이전에는 Object.assign()이나 Array.concat()을 사용하여 객체나 배열을 복사하거나 병합해야 했지만, spread 연산자를 이용하면 간단하게 처리할 수 있게 되었습니다.

const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

비슷한 맥락에서 rest 연산자를 이용하면 함수의 인자를 배열로 쉽게 만들 수 있습니다. 이전에는 arguments 객체를 사용해야 했지만, 이 방법은 배열이 아니기 때문에 불편한 점이 있었습니다. rest 연산자를 이용하면 이러한 불편함이 사라집니다.

function sum(...nums) {
return nums.reduce((acc, num) => acc + num, 0);
}

sum(1, 2, 3, 4, 5); // 15
function sum(...nums) {
return nums.reduce((acc, num) => acc + num, 0);
}

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

또한, ES9에서는 비동기 이터레이션과 제너레이터를 도입하였습니다. 이는 비동기 데이터 스트림을 쉽게 다룰 수 있게 해주었습니다.

ES10/ES2019🔗

Array.prototype.flatMap, Object.fromEntries

ES10에서는 여러 유용한 기능들이 추가되었습니다. 그 중에서도 flatMap 메소드와 Object.fromEntries 함수는 개발자들에게 매우 유용한 도구로 인식되었습니다.

flatMap 메소드는 배열의 각 요소에 함수를 적용한 후, 결과를 새로운 배열로 평탄화합니다. 이는 map 메소드와 flat 메소드를 연결하는 것과 동일한 결과를 반환합니다.

const arr = [1, 2, 3, 4, 5];
const result = arr.flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]
const arr = [1, 2, 3, 4, 5];
const result = arr.flatMap(x => [x, x * 2]); // [1, 2, 2, 4, 3, 6, 4, 8, 5, 10]

Object.fromEntries 함수는 키-값 쌍의 배열을 객체로 변환합니다. 이는 Object.entries 함수와 반대의 작업을 합니다.

const entries = [['name', '정민'], ['age', 20]];
const obj = Object.fromEntries(entries); // { name: '정민', age: 20 }
const entries = [['name', '정민'], ['age', 20]];
const obj = Object.fromEntries(entries); // { name: '정민', age: 20 }

ES11/ES2020🔗

BigInt, Promise.allSettled, Nullish Coalescing

BigInt는 JavaScript에서 아주 큰 숫자를 안전하게 다룰 수 있는 새로운 프리미티브 형식입니다. JavaScript의 기존 Number 타입은 IEEE 754 표준을 따르기 때문에 안전하게 다룰 수 있는 가장 큰 정수는 2^53 - 1입니다. 그러나 BigInt를 사용하면 이 제한을 넘어 아주 큰 정수를 안전하게 다룰 수 있습니다.

const bigNum = 9007199254740991n; // n접미사는 BigInt임을 나타냅니다.
const bigNum = 9007199254740991n; // n접미사는 BigInt임을 나타냅니다.

Promise.allSettled 메소드는 모든 프로미스가 결정되었을 때 (즉, 성공하거나 실패하거나) 완료된 프로미스 배열을 반환합니다. 이전의 Promise.all 메소드는 하나라도 실패하면 즉시 실패하지만, allSettled 메소드는 모든 프로미스가 완료될 때까지 기다립니다.

const promises = [Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)];
Promise.allSettled(promises).then(results => console.log(results));

// [ { status: 'fulfilled', value: 1 }, { status: 'rejected', reason: 'error' }, { status: 'fulfilled', value: 2 } ]
const promises = [Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)];
Promise.allSettled(promises).then(results => console.log(results));

// [ { status: 'fulfilled', value: 1 }, { status: 'rejected', reason: 'error' }, { status: 'fulfilled', value: 2 } ]

Nullish Coalescing 연산자 ??는 좌측 피연산자가 null이나 undefined일 때만 우측 피연산자를 반환합니다. 이전에는 || 연산자를 사용하여 비슷한 효과를 낼 수 있었지만, || 연산자는 좌측 피연산자가 falsy한 값일 때도 우측 피연산자를 반환하므로 문제가 발생할 수 있습니다. ?? 연산자를 사용하면 이러한 문제를 피할 수 있습니다.

let num = 0;
console.log(num ?? 'default'); // 0, '0'은 falsy하지만 null이나 undefined가 아니므로 '0'이 반환됩니다.
let num = 0;
console.log(num ?? 'default'); // 0, '0'은 falsy하지만 null이나 undefined가 아니므로 '0'이 반환됩니다.