ECMAScript ES2016~ES2020 정리

2020년을 맞아 이미 많이 알고 있는 ES2015(ES6) 다음부터인 ES2016부터 ES2020까지 새로 나온 자바스크립트 기능들을 살펴보려고 합니다.

모두 소개하는 건 아니고 비교적 도움이 되는, 대표적인 것들을 추려 소개합니다.
또한 기초적인 사전 지식의 설명은 생략될 수 있습니다.


ES2016

Array.prototype.includes

배열 내장 함수 includes가 추가되었습니다.

['a', 'b', 'c'].includes('a') // true

코드를 보면 쉽게 역할을 알 수 있습니다. 배열에 아이템이 존재하는지 Boolean 값을 반환합니다.

이 함수로 이제 ['a','b','c'].indexOf('a') > -1 대신 좀 더 간결한 코드가 가능합니다.

Exponentiation operator

제곱 연산자. 기존에 제곱은 Math의 pow 함수를 이용했을 것입니다. 이제는 간단하게 ** 연산자로 가능하게 되었습니다.

let num = 2;

Math.pow(num, 10); // 1024
2 ** 10; // 1024

num **= 10;
num; // 1024

ES2017

String padding

문자열에 여백을 주기 위해 등장한 기능입니다. 여백 뿐만 아니라 그 여백에 보충 문자를 추가할 수 있습니다.

여백을 앞에 뒤에 추가할 수 있게 됩니다.

padStart(targetLength [, padString])
padEnd(targetLength [, padString])

간단한 예를 통해 살펴보겠습니다. start와 end의 차이는 앞뒤의 차이밖에 없으니 start만 살펴봅니다.

1월01월 과 같이 길이를 맞춰야 할 경우가 있다 이럴때 사용 할 수 있습니다.

`1월`.padStart(1);        // "1월"
`1월`.padStart(3);        // " 1월"
`1월`.padStart(3, "0");   // "01월"
`1월`.padStart(7, "0AB"); // "0AB0A1월"

1번째 라인처럼 문자열보다 작은 수를 지정하더라도 문자열이 잘리거나 하진 않습니다

2번째 라인은 문자열의 길이보다 하나 긴 3을 넣어 하나의 여백이 추가된 모습입니다.

3번째 라인이 원하는 의도인 코드입니다.

4번째 라인처럼 보충 문자를 배열로 작성하면 배열을 순회 반복하며 여백을 채우며 0, A, B를 채우고 다음으로 0, A까지 채워진 모습을 볼 수 있습니다.

Object.values()

Object에 추가된 values 메소드는 객체의 key:value 에서 value 값들을 배열으로 반환합니다.

const obj1 = {name: "Jhon", age: 24};
Object.values(obj1); // ["Jhon", 24]

Object.entries()

객체 안의 모든 속성을 각각 key, value가 담긴 배열로 하나의 배열에 담습니다. [[key, value], [key, value], [key, value] ...]

const obj1 = {name: "Jhon", age: 24};

Object.keys(obj1);    // ["name", "age"]
Object.values(obj1);  // ["Jhon", 24]

Object.entries(obj1); // [["name", "Jhone"], ["age", 24]]

Object.getOwnPropertyDescriptors()

Object에 기존에 있던 getOwnPropertyDescriptor에 이어 복수형 s가 붙은 getOwnPropertyDescriptors 메소드가 추가되었습니다.

해당 메소드를 이해하기 위해서는 선행 지식으로 getOwnPropertyDescriptor가 무엇인지 알아야하며 속성 설명자의 네가지 속성을 알아야합니다. (링크 참조)

또한 Object.defineProperties() 같은 메소드를 알고 있어야 유용하게 활용할 방법이 생깁니다. 여기서는 자세한 설명없이 해당 메소드에 대해서만 설명합니다.

Object.getOwnPropertyDescriptor(obj, prop)

기존의 getOwnPropertyDescriptor는 인자로 객체와 속성명을 전달해 해당 속성의 속성 설명자를 반환하는 메소드입니다.

이제 여기서 설명하는 getOwnPropertyDescriptors는 속성명을 전달하지 않고 객체만 전달함으로 객체내의 모든 속성에 대한 속성 설명자를 반환합니다.

Object.getOwnPropertyDescriptor(obj)

두개의 예제입니다.

console.log(Object.getOwnPropertyDescriptor(obj1, "name"));
// Object {value: "Jhon", writable: true, enumerable: true, configurable: true}

console.log(Object.getOwnPropertyDescriptors(obj1));
// Object {
//   name: {value: "Jhon", writable: true, enumerable: true, configurable: true}, 
//   age: {value: 24, writable: true, enumerable: true, configurable: true}
// }

Trailing commas

함수의 마지막 매개변수와 인자에도 콤마를 넣을 수 있습니다.

아래와 같이 말이죠. 해당 기능이 왜 추가되었는지는 다양한 의견이 있습니다. 흥미가 있는 분들은 한번 검색해보세요.

const foo = (a, b, c,) => {}

async/await

사실상 ES2017의 핵심이라고 볼 수 있습니다.

새로운 비동기 처리 함수로 기존의 Promise 보다 가독성이 좋은 코드를 작성할 수 있게 되었습니다. 선행적으로 비동기 처리에 대한 지식과 Promise의 이해가 필요합니다.

해당 내용은 여기서 간단히 다루기에는 중요하고 내용이 길어 따로 포스팅을 할 예정입니다.

아래 링크는 async/await를 설명한 캡틴판교님의 포스트입니다.

CAPTAIN PANGYO - 자바스크립트 async와 await


ES2018

Rest/Spread Properties

기존의 배열에서 사용하던 rest/spread를 객체에서도 사용가능하게 되었습니다.

// Rest
const { one, two, ...others } = { one: 1, two: 2, three: 3, four: 4, five: 5 }
console.log(one, two, others); // 1 2 {three: 3, four: 4, five: 5}

// Spread
const obj1 = {one, two, ... others};
console.log(obj); // {one: 1, two: 2, three: 3, four: 4, five: 5}

const obj2 = { one: 100, five: 500 };
const obj3 = { five: 5000 };
const obj = { ...obj1, ...obj2, ...obj3};
console.log(obj); // {one: 100, two: 2, three: 3, four: 4, five: 5000}

마지막 라인의 obj의 출력을 보면 같은 속성 이름에 대해서는 앞의 것을 뒤의 것이 덮어쓴다는 것을 알 수 있다.

Promise.prototype.finally()

then, catch, finally에서 Promise는 기존에 then과 catch만 가능했으나 이제 finally도 추가되었습니다.

Promise.resolve('reslove')
.then((res) => console.log('success'))
.catch((err) => console.log('fail'))
.finally(() => console.log('finally'))

Asynchronous iteration

비동기 이터러블 객체를 순회하는 것이 가능해졌습니다.

이해를 위해서는 Promise, async/await의 이해가 선행됩니다.

for await (const req of requests) {
console.log(req)
}

ES2019

String.trimStart() & trimEnd()

문자열의 앞이나 뒤의 공백을 제거한다.

앞을 제거하는 trimStart와 뒤를 제거하는 trimEnd가 있다.

아래 예제를 보면 쉽게 이해할 수 있다.

const s = "     hello world";
const e = "!     ";

console.log(s + e + ';');
// "     hello world!     ;"

console.log(s.trimStart() + e.trimEnd() + ';');
// "hello world!;"

Optional Catch Binding

catch 매개변수 없이도 catch 블록을 사용할 수 있습니다.

try {
// some code
}
catch (err) {
// error handling code
}

위와 같이 catch(err){} 또는 catch(){} 와 같이 사용하던 것을 아래와 같이 사용할 수 있습니다.

try {
// some code
}
catch {
// error handling code
}

Object.fromEntries()

위에서 설명한 Object.entries() 의 정반대입니다.

객체를 entries로 배열로 만들었다면 fromEntries로 다시 객체로 만들 수 있다는 이야기입니다. entires를 이해했다면 간단하게 아래 예제를 통해 알 수 있습니다.

const obj1 = {name: "Jhon", age: 24};

const entries = Object.entries(obj1); 
console.log(entries); // [["name", "Jhone"], ["age", 24]]

const fromEntries = Object.fromEntries(entries);
console.log(fromEntries); // {name: "Jhon", age: 24}

Array.flat() & flatMap()

flat 메소드는 배열안의 배열을 쉽게 합칠 수 있게 됩니다. 예제를 통해 쉽게 이해할 수 있습니다.

const arr = [1, , 2, [3, 4, [5, 6]]];

console.log(arr.flat());  // [1, 2, 3, 4, [5, 6]]
console.log(arr.flat(1)); // [1, 2, 3, 4, [5, 6]]

console.log(arr.flat(2)); // [1, 2, 3, 4, 5, 6]

flat의 인자로는 배열의 깊이를 넘겨줄 수 있으며, 전달된 인자가 없을 경우 default는 1이다. 또한 빈 배열은 무시된다.

flatMap 메소드는 map()flat()이 합쳐진 것으로 볼 수 있다. 우선 map을 통해 새로운 배열을 만들고 flat을 통해 배열이 합쳐진다. (flatMap의 깊이는 1입니다)

const arr = [1,2,3];

const map = arr.map(v => [v]);
const flatMap = arr.flatMap(v=> [v]);

console.log(map);         // [[1], [2], [3]]
console.log(map.flat());  // [1, 2, 3]

console.log(flatMap);     // [1, 2, 3]

ES2020

Dynamic Import

파일 import를 동적으로 할 수 있게 되었습니다.

아래 예제는 if문에 따라 import한 파일의 사용 유무가 달라지는데 조건에 맞지 않더라도 일단은 최상단의 import를 통해 파일을 불러옵니다. 그러나 이제는 두번째 코드 예제와 같이 Dynamic Import로 인해 불필요한 동작을 줄일 수 있게 되었습니다.

import config from './config.js';

if(response) {
age = config.age;
}

if(response) {
import('./config.js')
.then(config => {
  age = config.age;
  console.log(config);
}
}

참고로 Dynamic import 예제인 두번째 코드에서 console.log의 출력 결과는 {age: 24, skills: ["react", "webpack"], default: "Jhon"} 입니다. 그렇다면 config 파일의 내용은 아래와 같습니다.

export const age = 24;

export const skills = ["react", "webpack"];

export default "Jhon";

default로 export를 한 것은 default라는 속성 이름을 가지는 것을 알아두세요.

BigInt

BigInt는 2^53 보다 큰 정수를 취급하기 위해 등장했습니다.

숫자 뒤에 n이 붙는 특징을 가지고 있습니다. 예로 10n 은 숫자 10을 뜻한다는 의미입니다.

자세한 예와 설명은 아래 예제로 설명하겠습니다.

const int1 = Number.MAX_SAFE_INTEGER + 1; 
// 9007199254740992

const int2 = Number.MAX_SAFE_INTEGER + 2; 
// 9007199254740992  <= ~993이 아니다.

const bigInt2 = BigInt(Number.MAX_SAFE_INTEGER) + 2n; 
// 9007199254740993n <= ~993이 되었다.

console.log(typeof int1, typeof int2, typeof bigInt2); 
// number number bigint

console.log(typeof 9007199254740993n); // bigint
console.log(9007199254740993n === bigInt2); // true
// 일반적인 숫자 뒤에 n을 붙이는 것으로 bitint 타입을 가진다는 것을 알 수 있습니다.

console.log(BigInt(10), BigInt(10n));
// 10n 10n <= BitInt의 인자로는 n의 여부가 중요하지 않다.

console.log(10 === BigInt(10)); // false
console.log(10 == BigInt(10)); // true

// console.log(9007199254740993n + 1);
// 위 코드는 에러가 발생한다. bigint를 연산할때는 number 타입과 섞어쓰는 것이 안된다.

Promise.allSettled

Promise.all()은 모든 작업이 성공(reslove)해야 실행되는 특징과 달리 Promise.allSettled()은 도중에 실패(reject)되더라도 모든 실행을 할 수 있습니다.

아래 예제를 통해 코드와 실행 결과를 살펴봅시다.

const p1 = new Promise((resolve, reject) => resolve("p1, resolved"));
const p2 = new Promise((resolve, reject) => resolve("p2, resolved"));
const p3 = new Promise((resolve, reject) => reject("p3, rejected"));

Promise.all([p1, p2, p3])
.then(response => console.log(response))
.catch(err => {
console.log(err);
});
/*
console.log(response)
{status: "fulfilled", value: "p1, resolved"}
{status: "fulfilled", value: "p2, resolved"}
{status: "rejected", reason: "p3, rejected"}
*/

Promise.allSettled([p1, p2, p3])
.then(response => console.log(response))
.catch(err => {
console.log(err);
});
/*
console.log(err);
p3, rejected
*/

Nullish Coalescing Operator

a ?? b 로 나타내는 연산자입니다. null이나 undefined일 때만 b를 반환합니다.

0은 false를 의미하기도 합니다. 따라서 0 || 'A'A 가 될겁니다. 하지만 0이 의미있는 값이면 0 또한 사용해야 할 것입니다. 또한 삼항 연산자를 단축시켜 사용할 수 있는 장점도 있습니다.

예제를 통해 살펴봅시다.

0 || 'A'    // A
0 ?? 'A'    // 0

0 ? 0 : 'A' // A
0 ?? 'A'    // 0

Optional Chaining

foo.a.b 에 접근한다고 했을 때, a가 없다면 오류가 발생하게 됩니다.

따라서 오류를 방지하기 위해 if(foo.a && foo.a.b) 와 같이 사용하곤 합니다.

하지만 Optional Chaining을 이용하면 오류가 발생하지 않고 undefined를 반환받습니다.

Optional Chaining은 foo.a?.b 와 같이 표현합니다.

const foo = {
a: {
b: true
}
};

console.log(foo.a.b); // true
console.log(foo.b?.b); // undefined

console.log(foo.b.aa); // TypeError: Cannot read property 'aa' of undefined