ES6 이후의 변경점 총정리
2009년에 ES5가 등장하고 2015년에 ES6가 등장하며 큰 변화를 겪은 이후, ECMA는 매년 새로운 자바스크립트 표준을 발표합니다. ES6 이후 추가된 내용중 일부는 이미 사용하고 있지만, 자세히 알아본 적이 없는 것 같아서 정리하는 시간을 가져봤습니다. ECMAScript에 대한 자세한 설명은 생략하고, ES6(ES2015) 이후에 어떤 것들이 추가되었는지 나열해 보려고 합니다.
ES2016
ES2016에서는 두 가지 기능이 새롭게 도입되었습니다.
- Array.prototype.includes()
- 지수 연산자(**)
ES2016-1. Array.prototype.includes()
includes() 메서드는 배열에 특정 원소가 포함되어 있으면 true를 반환하고 그렇지 않으면 false를 반환합니다.
const numbers = [1, 2, 4, 8];
numbers.includes(2);
//true
numbers.includes(3);
//false
includes()에 두 번째 값으로 인덱스를 추가해서 원소를 검색할 수 있습니다. 기본값은 0이지만 음수를 전달할 수도 있습니다.
MDN - includes
ES2016-2. 지수 연산자
//ES2016 이전의 지수 계산
Math.pow(2, 2); // 4
Math.pow(2, 3); // 8
// 지수 연산자 **를 사용
2**2; // 4
2**3; // 8
ES2017
ES2017에서는 익히 잘 알고 계시는 async, await 등 많은 기능이 추가되었습니다. async를 제외한 나머지 기능을 알아보겠습니다.
ES2017-1. 문자열 패딩
문자열 끝 부분이나 시작 부분을 다른 문자열로 채워 주어진 길이를 만족하는 새로운 문자열을 만들어낼 수 있습니다. String.prototype.padStart(), String.prototype.padEnd()로 사용할 수 있습니다.
"hello".padStart(6); " hello"
"hello".padEnd(6); "hello "
"hello".padStart(3); "hello" // 문자열 길이보다 목표 문자열 길이가 짧다면 채워넣지 않고 그대로 반환
// 사용자가 지정한 값으로 채우는 것도 가능합니다.
"hello".padEnd(20, "*");
// "hello***************"
ES2017-2. Object.entries()와 Object.values()
객체 내부의 값에 접근하는 방법입니다.
const ppap = {
pplead: 'Ten Kim',
aplead: 'GJ Han',
pp: ['Widget Lee'],
ap: ['Someone Min']
}
Object.entries(ppap); // 객체의 키와 값을 포함하는 배열의 배열을 반환
[
["pplead", "Tem Kim"],
["aplead", "GJ Han"],
["pp", ["Widget Lee"]],
["ap", ["Someone Min"]]
]
Object.values(ppap); // 객체의 값이 담긴 배열을 반환
[
"Ten Kim",
"GJ Han",
[ "Widget Lee" ],
[ "Someone Min" ]
]
// 이런 식으로 객체를 map으로 쉽게 바꿀 수 있음
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baz: 42 }
MDN - Object.entries
MDN - Object.values
ES2017-3. Object.getOwnPropertyDescriptors()
객체가 소유한 모든 상속받지 않은 속성 설명자를 반환합니다.
const myObj = {
name: "Woongki",
age: '30 over',
get myname() {
return this.name;
}
}
Object.getOwnPropertyDescriptors(myObj);
{
"name": {
"value": "Woongki",
"writable": true,
"enumerable": true,
"configurable": true
},
"age": {
"value": "30 over",
"writable": true,
"enumerable": true,
"configurable": true
},
"myname": {
"enumerable": true,
"configurable": true,
"set": undefined,
"get": myname() {
return this.name;
}
}
}
MDN - Object.getOwnPropertyDescriptors
ES2017-4. Trailing Comma
배열 리터럴에서는 항상 trailing comma가 허용되었습니다. ES6에서 객체 리터럴에도 도입되었고, 함수에도 도입된 것이 ES2017입니다.
function f(p) {}
function f(p,) {}
(p) => {};
(p,) => {};
ES2017-5. Atomics, SharedArrayBuffer
- 멀티 스레드 환경을 지원하기 위한 기능들입니다. 이 부분은 저도 좀 더 심도있게 공부한 후에 더 자세히 서술할 수 있을 것 같아서 일단 간략히 설명하고 지나가겠습니다.
- SharedArrayBuffer는 고정된 길이의 바이너리 데이터 버퍼를 표현하는데 사용되며 컨텐츠는 0으로 초기화됩니다. 메인스레드 혹은 다른 worker와의 메모리 공유를 위해 쓰일 수 있습니다.
- 메모리가 공유되면 여러 스레드가 메모리에서 동일한 데이터를 읽고 쓸 수 있습니다. Atomics를 이용한 작업은 이러한 환경에서도 정확하게 값을 읽고 쓸 수 있게 해줍니다. 또 Atomics를 이용한 작업은 다음 작업이 시작되기 전에 완료되고, 중단되지 않는 것이 보장됩니다.
- 허나 SharedArrayBuffer는 Spectre 에 대한 대응으로 2018년 1월 이후 거의 대부분의 브라우저에서 비활성화가 되어있다가 크롬68, 엣지79, 파이어폭스79 이후 버전의 브라우저에서만 사용이 가능합니다.
MDN - SharedArrayBuffer
MDN - Atomics
ES2018
ES2018-1. Object rest/spread
ES6에서 배열의 spread, rest가 가능해졌고, ES2018에서는 객체에도 사용하는 것이 가능해졌습니다. 배열과 마찬가지로 객체도 쉽게 얕은 복사를 할 수 있게 되었습니다.
const myObj = {
a: 1,
b: 3,
c: 'cc',
d: 100
};
const {a, b, ...z} = myObj;
console.log(a); // 1
console.log(b); // 3
console.log(z); // { "c": "cc", "d": 100 }
const spread = {
...myObj,
a: 10,
e: 30,
};
console.log(spread);
/**
{
"a": 10,
"b": 3,
"c": "cc",
"d": 100,
"e": 30
}
*/
ES2018-2. Async iteration
제너레이터 함수와 for of 문에서 async를 사용할 수 있습니다.
(async() => {
const promises = [1, 2, 3].map((timer) => (
new Promise((resolve) => {
setTimeout(() => resolve(timer), timer*1000);
})
));
for await (const result of promises) {
console.log(result);
}
})();
ES2018-3. Promise.prototype.finally()
finally() 메소드는 Promise가 처리되면 충족되거나 거부되는지의 여부에는 상관없이 무조건 콜백 함수가 실행됩니다. finally() 또한 프로미스를 반환하고, then과 catch로 계속 연결할 수 있지만, finally가 반환한 값이 아니라 그 전의 프로미스가 반환한 값을 갖게 됩니다. 프로미스의 성공 여부와 관계가 없기 때문에 finally는 아무런 인자도 받지 않습니다.
const myPromise = new Promise((resolve, reject) => {
resolve();
});
myPromise.then(() =>{
console.log('1');
return 'still working at 2am'
}).finally(res => { // 아무런 인자도 받지 않기 때문에 undefined가 나옴
console.log(2, res)
return 'finally'
}).then(res => {
console.log(3, res);
});
// 1
// 2 undefined
// 3 still working at 2am
ES2018-4. 정규식 기능 추가
ES2018에는 네 가지 새로운 정규식 관련 기능이 추가되었습니다.
- s(dotAll) 플래그
-> . 표현식은 개행문자를 제외한 모든 문자였으나, s플래그를 달면 개행식도 포함하게 됩니다.
/foo.bar/s.test('foo\nbar'); // true
- 명명된 캡쳐 그룹
->
(?<name>)
구문을 사용하여 캡쳐 그룹에 원하는 이름을 붙일 수 있습니다.let regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u; let result = regex.exec('2021-06-21'); console.log(result.groups); { "year": "2021", "month": "06", "day": "21" }
- 룩비하인드 어서션
- 룩비하인드 어서션을 사용하면 패턴 앞에 다른 패턴이 있는지 여부를 확인할 수 있다고 합니다.
- 긍정 룩비하인드 어서션
const regex = /(?<=\$)\d+(\.\d*)?/; const result = regex.exec('$10.53'); [ "10.53", ".53" ] const result2 = regex.exec('&10.53'); // null
- 부정 룩비하인드 어서션 (?<!…)
- 유니코드 속성 이스케이프
\p{...}
(긍정),\P{...}
(부정) 형식으로 유니코드 속성 이스케이프를 사용할 수 있습니다.const sentence = 'A ticket to 大阪 costs ¥2000 👌.'; const regexpEmojiPresentation = /\p{Emoji_Presentation}/gu; console.log(sentence.match(regexpEmojiPresentation)); // expected output: Array ["👌"] const regexpNonLatin = /\P{Script_Extensions=Latin}+/gu; console.log(sentence.match(regexpNonLatin)); // expected output: Array [" ", " ", " 大阪 ", " ¥2000 👌."] const regexpCurrencyOrPunctuation = /\p{Sc}|\p{P}/gu; console.log(sentence.match(regexpCurrencyOrPunctuation)); // expected output: Array ["¥", "."]
ES2019
ES2019-1. Array.prototype.flat()과 Array.prototype.flatMap()
-
Array.prototype.flat()
Array.prototype.flat()은 지정한 깊이까지 배열을 재귀적으로 평면화합니다. 깊이 인수가 지정되지 않으면 1이 기본값이고,Infinity
로 지정하면 모든 중첩 배열을 평면화할 수 있습니다.const arr1 = [1, 2, [3, 4]]; arr1.flat(); // [1, 2, 3, 4] const arr2 = [1, 2, [3, 4, [5, 6]]]; arr2.flat(); // [1, 2, 3, 4, [5, 6]] const arr3 = [1, 2, [3, 4, [5, 6]]]; arr3.flat(2); // [1, 2, 3, 4, 5, 6] const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]]; arr4.flat(Infinity); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] // 배열의 빈 부분도 제거합니다 const arr5 = [1, 2, , 4, 5]; arr5.flat(); // [1, 2, 4, 5]
MDN - flat Node11부터 사용 가능하고 IE는 불가합니다. (참고로 오늘 소개한 모든 기능이 IE에선 불가합니다..)
-
Array.prototype.flatMap() 메서드는 먼저 매핑함수를 사용해 각 엘리먼트에 대해 map 수행 후, 결과를 새로운 배열로 평탄화합니다. 이는 깊이 1의 flat이 뒤따르는 map과 동일하지만,
flatMap
은 아주 유용하며 둘을 하나의 메소드로 병합할 때 조금 더 효율적입니다.let arr1 = ["it's Sunny in", "", "California"]; arr1.map(x=>x.split(" ")); // [["it's","Sunny","in"],[""],["California"]] arr1.flatMap(x => x.split(" ")); // ["it's","Sunny","in", "", "California"]
ES2019-2. Object.fromEntries()
Object.fromEntries는 키/값 쌍이 포함된 iterable을 객체로 변환합니다.(Map도 객체로 변환이 가능). Node12 이상 버전부터 가능합니다.
const keyVal = [
['key1', 'val1'],
['key2', 'val2']
];
const obj = Object.fromEntries(keyVal);
console.log(obj);
{
"key1": "val1",
"key2": "val2"
}
ES2019-3. String.prototype.trimStart()와 String.prototype.trimEnd()
trimStart는 문자열 시작 부분의 공백을 제거하고 trimEnd는 문자열 끝 부분의 공백을 제거합니다.
const greeting = ' Hello world! ';
console.log(greeting);
// expected output: " Hello world! ";
console.log(greeting.trimStart());
// expected output: "Hello world! ";
console.log(greeting.trimLeft());
// expected output: "Hello world! ";
console.log(greeting.trimEnd());
// expected output: " Hello world!";
console.log(greeting.trimRight());
// expected output: " Hello world!";
ES2019-4. 선택적 catch 할당
ES2019 이전에는 catch절에 항상 예외 변수를 포함해야 하지만, ES2019에서는 이를 생략할 수 있습니다.
try {
...
} catch {
...
}
ES2019-5. Function.prototype.toString()
함수 객체의 .toString() 메서드는 함수의 소스 코드를 나타내는 문자열을 반환합니다. ES2016까지는 소스 코드에서 주석이나 공백 문자를 제거했지만, ES2019에서 개정되어 문자열에 주석 등도 포함됩니다.
function sum(a, b) {
return a+b;
// 두 인자의 합을 리턴합니다.
}
console.log(sum.toString());
'function sum(a, b) { return a+b; // 두 인자의 합을 리턴합니다. }'
ES2019-6. Symbol.prototype.description
심벌 객체의 description은 해당 심벌 객체의 설명을 반환합니다.
Symbol('desc').toString(); // "Symbol(desc)"
Symbol('desc').description; // "desc"
ES2020
ES2020-1. BigInt
현재 정수의 최대값은 2**53-1
이며 Number.MAX_SAFE_INTEGER를 통해 확인할 수 있습니다. BigInt
는 정수 리터럴의 뒤에 n
을 붙이거나(10n
) 함수 BigInt()
를 호출해 생성할 수 있습니다.
BigInt
와 Number
는 어떤 면에서 비슷하지만 중요한 차이점이 있습니다. 예컨대 BigInt
는 내장 Math
객체의 메서드와 함께 사용할 수 없고, 연산에서 Number
와 혼합해 사용할 수 없습니다. 따라서 먼저 같은 자료형으로 변환해야 합니다. 그러나, BigInt
가 Number
로 바뀌면 정확성을 잃을 수 있으니 주의해야 합니다. 또한 bigDecimal이 아니기 때문에 소수점 이하는 언제나 버립니다.
const theBiggestInt = 9007199254740991n;
const bigintSum = theBiggestInt + 1n;
// 9007199254740992n
const alsoHuge = BigInt(9007199254740991);
// 9007199254740991n
typeof bigintSum
// "bigint"
ES2020-2. Dynamic import
ES2020부터는 필요할 때 모듈을 동적으로 가져올 수 있습니다.
if (condition1 && condition2) {
const module = await import('./path/to/module.js');
module.doSomething();
}
ES2020-3. 옵셔널 체이닝
연산자 ?.
는 체인의 각 참조가 유효한지 명시적으로 검증하지 않고, 연결된 객체 체인 내에 깊숙이 위치한 속성 값을 읽을 수 있습니다. ?.
연산자는 .
체이닝 연산자와 유사하게 작동하지만, 만약 참조가 nullish(null
또는 undefined
)이라면, 에러가 발생하는 것 대신에 표현식의 리턴 값은 undefined
로 단락됩니다.
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah'
}
};
const catName = adventurer.cat?.name;
console.log(catName);
// 'Dinah'
const dogName = adventurer.dog?.name;
console.log(dogName);
// undefined
MDN - optional chaining (Node v14부터 사용 가능)
ES2020-4. Promise.allSettled()
allSettled는 성공 실패 여부와 무관하게 모든 프로미스들이 완료될 떄까지 기다렸다가 각각의 결과를 설명하는 객체 배열을 반환합니다.
const promiseArr = [
new Promise((resolve, reject) => setTimeout(resolve, 1000, 'abc')),
new Promise((resolve, reject) => setTimeout(reject, 2000)),
new Promise((resolve, reject) => setTimeout(resolve, 3000)),
];
Promise.allSettled(promiseArr).then(data => console.log(data));
[
{
"status": "fulfilled",
"value": "abc"
},
{
"status": "rejected"
},
{
"status": "fulfilled"
}
]
MDN - allSettled (Node v12.9 이상)
ES2020-5. Null coalescing operator(null 병합 연산자)
null 병합 연산자는 왼쪽 피연산자가 null 또는 undefined일 때 오른쪽 연산자를 반환하고 그렇지 않으면 왼쪽 피연산자를 반환하는 논리 연산자입니다. 이는 왼쪽 피연산자가 falsy 값에 해당할 경우 오른쪽 피연산자를 반환하는 or(||
)와는 대조됩니다.
const foo = null ?? 'default string';
console.log(foo);
// expected output: "default string"
const baz = 0 ?? 42;
console.log(baz);
// expected output: 0
MDN - Nullish coalescing operator (Node v14 이상)
ES2020-6. String.prototype.matchAll()
matchAll 메서드는 지정된 정규식에 대해 문자열과 일치하는 모든 결과의 iterator를 반환하는 메서드입니다.(캡쳐링 그룹 포함) 정규 표현식 뒤에 g flag를 사용해주어야 합니다.
const regEx = /[a-d]/g;
const str = 'Lorem ipsum dolor sit amet';
const result = str.matchAll(regEx);
console.log(result.next());
{
"value": ["d"],
"done": false
}
console.log(result.next());
{
"value": ["a"],
"done": false
}
console.log(result.next());
{
"done": true
}
ES2020-7. 모듈 네임스페이스 export 문법
이제
export * as stuff from './test.mjs';
// 아래의 코드와 동일한 역할을 수행한다
export { stuff };
같이 선언할 수 있습니다… 의미는 잘 모르겠..
ES2020-8. import.meta
import.meta 객체는 URL등 모듈에 대한 정보를 노출합니다.
<script type="module" src="my-module.js">
console.log(import.meta);
{
url: "file:///home/user/my-module.js"
}
아래와 같은 응용이 가능!
<script type="module">
import './index.mjs?someURLInfo=5';
</script>
// index.mjs
new URL(import.meta.url).searchParams.get('someURLInfo'); // 5
ES2020-8. globalThis
ES2020 전에는 전역 객체에 접근하는 표준화된 방식이 없었습니다. 브라우저에서는 window, node에서는 global, 웹 워커의 경우 self 등을 사용해 전역 객체에 접근했었습니다. ES2020부터는 어떤 환경에서든 항상 전역 객체를 참조하는 globalThis
를 사용할 수 있습니다.(Node v12부터)
ES2021
아직 스펙이 정식으로 결정된 것은 아니지만 많은 제안 중에 4단계에 도달하여 다음 스펙에 포함될 예정인 것들을 몇 개 알아봅시다.
ES2021-1. String.prototype.replaceAll()
기존의 replace() 메서드는 문자열의 패턴을 다른 것으로 바꿀 수 있는 유용한 메서드였지만, 정규식을 쓰지 않으면 일치하는 첫 번째 항목만 교체가 가능했습니다. 그러나 replaceAll() 단순 문자열 패턴을 대체할 때도 일치하는 모든 문자열을 교체합니다.
const str = 'I like my dog, my dog is very cute.';
const newStr = str.replaceAll('dog', 'cat');
console.log(newStr);
//I like my cat, my cat is very cute.
MDN - replaceAll (chrome85, node v15 이상)
ES2021-2. Promise.any()
Promise.any()는 주어진 promise 중 하나라도 성공하면 실행을 완료하지만, 그렇지 않다면 모든 promise가 실패할 때 까지 계속됩니다. Promise.race()는 하나라도 성공 혹은 실패할 시에 종료되는 것과 차이가 있습니다.
const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve, reject) => setTimeout(resolve, 500, 'slow'));
const promise4 = new Promise((resolve, reject) => setTimeout(reject, 100, 'quick'));
const promise5 = new Promise((resolve, reject) => setTimeout(reject, 500, 'slow'));
const promises1 = [promise1, promise2, promise3]
const promises2 = [promise1, promise4, promise5];
Promise.any(promises1).then((value) => console.log(value)).catch(err => console.log(err));
// 'quick'
Promise.any(promises2).then((value) => console.log(value)).catch(err => console.log(err));
// AggregateError: All promises were rejected
MDN - Promise.any() (chrome85, Node v15 이상)
ES2021-3. 논리 연산자와 할당 표현식
ES2021부터는 논리 연산자와 할당 표현식을 결합할 수 있습니다.
const a = { duration: 50, title: ''};
a.duration ??= 10;
console.log(a.duration);
// 50
a.speed ??= 25;
console.log(a.speed);
// 25
a.duration &&= 60;
console.log(a.duration);
// 60
a.title ||= 'Logical or Assignment'
console.log(a.title);
// 'Logical or Assignment'
MDN - Logical nullish assignment (chrome85, Node v15 이상)
ES2021-4. 숫자 구분 기호
ES2021에는 숫자 구분 기호가 도입되었으며, 큰 자릿수 숫자를 구분하는데에 언더바를 사용하여 쉽게 표시할 수 있게 되었습니다.
const x = 100_000;
console.log(x)
// 100000;
MDN 문서를 찾지 못하여 proposal 명세서를 남깁니다. TC39 - proposal-numeric-separator
ES2021-5. WeakRef
WeakRef를 통해 특정 객체에 일반 참조가 없으면 약한 참조가 남아있어도 가비지 콜렉터가 해당 객체를 메모리에서 제거한다고 합니다. 이 부분도 조금 더 자세한 공부가 필요할 것 같아서 참조 링크를 남기고, 후에 다시 자세히 설명할 기회를 가지려고 합니다.
MDN - WeakRef
ES2021-6. Intl.ListFormat
각종 언어별로 목록 서식을 활성화하는 객체의 생성자라고 합니다. 직접 보는 것이 이해가 빠를 것 같습니다.
const list = ['Apple', 'Orange', 'Banana'];
console.log(new Intl.ListFormat('ko', {style:'long', type: 'conjunction'}).format(list));
// 'Apple, Orange 및 Banana'
console.log(new Intl.ListFormat('ko', {style:'long', type: 'disjunction'}).format(list));
// 'Apple, Orange 또는 Banana'
console.log(new Intl.ListFormat('en', {style:'long', type: 'conjunction'}).format(list));
// 'Apple, Orange, and Banana'
console.log(new Intl.ListFormat('en', {style:'long', type: 'disjunction'}).format(list));
// 'Apple, Orange, or Banana'
ES2021-7. Intl.DateTimeFormat의 dateStyle 및 timeStyle
직접 봅시다!
new Intl.DateTimeFormat("ko", {dateStyle: "short"}).format(Date.now());
// '21. 6. 21.'
new Intl.DateTimeFormat("ko", {dateStyle: "medium"}).format(Date.now());
// '2021. 6. 21.'
new Intl.DateTimeFormat("ko", {dateStyle: "long"}).format(Date.now());
// "2021년 6월 21일"
new Intl.DateTimeFormat("en", {dateStyle: "short"}).format(Date.now());
// '6/21/21'
new Intl.DateTimeFormat("en", {dateStyle: "medium"}).format(Date.now());
// 'Jun 21, 2021'
new Intl.DateTimeFormat("en", {dateStyle: "long"}).format(Date.now());
// "June 21, 2021"
new Intl.DateTimeFormat("en", {timeStyle:"short"}).format(Date.now());
// 1:11 PM
new Intl.DateTimeFormat("en", {timeStyle:"medium"}).format(Date.now());
// 1:11:50 PM
new Intl.DateTimeFormat("en", {timeStyle:"long"}).format(Date.now());
// "1:11:50 PM GMT+9"
new Intl.DateTimeFormat("ko", {timeStyle:"short"}).format(Date.now());
// 오후 1:12
new Intl.DateTimeFormat("ko", {timeStyle:"medium"}).format(Date.now());
// 오후 1:12:24
new Intl.DateTimeFormat("ko", {timeStyle:"long"}).format(Date.now());
// "오후 1시 12분 24초 GMT+9"
마무리하며
지금까지 ES6 이후에 새로이 추가된 스펙들에 대해서 간략하게 알아보았습니다. 이 글을 작성한 시점과 업로드하는 시점은 차이가 좀 있는데요, 그 사이에 ES2021 스펙이 어느 정도 확정이 된 것 같습니다. 여러 proposal에 대한 정보는 github의 tc39에서 볼 수 있으니 관심이 있으시면 보시는 것도 좋을 것 같습니다. 최대한 확인한다고 했지만 예제나 정보에 틀린 내용이 있을 수 있는데요, 언제든지 woongki@dable.io로 제보 주시면 좋겠습니다. 읽어주셔서 감사합니다.