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