개발자

무심코 지나쳤을지도 모를 자바스크립트 신기능 8가지

Matthew Tyson | InfoWorld 2022.02.14
자바스크립트의 돌진은 멈추지 않는다. 매년 자바스크립트의 ‘사양’에 많은 기능이 도입되고, 그 후에는 브라우저, Node.js 등이 같은 기능을 채택한다. 그동안 알아차리지 못한 새로운 자바스크립트 기능도 상당히 많을 것이다.
 
ⓒ Getty Images Bank

아직 늦지 않았다. 이 글에서는 개발자가 무심코 지나쳤을지도 모를 ES11(ECMAScript 11 또는 ECMAScript 2020이라고도 함) 기능을 정리했다. 편의성을 위한 개선과 기타 현대적 기능 개선이 포함된다.


선택적 체이닝

선택적 체이닝은 일상을 조금 더 쉽게 만드는, 단순하지만 효과적인 기능이다. null 또는 undefined 값을 처리하기 위해 객체와 함수 체인을 간편하게 탐색할 수 있다.

선택적 체이닝은 중첩된 객체 데이터, 존재하지 않는 값을 반환하는 함수, 그리고 객체의 멤버 메서드로 존재하지 않는 함수 같은 간단한 사례에 사용된다. 따라서 예시 1과 같은 작업이 가능하다.

예시 1. 선택적 체이닝 예제

box = {
  innerBox: {},
  nullFunction: function() { return null; },
  // non-existent method foo() and members bar
}
// with optional chaining:
if (box?.innerBox?.foo){ }// navigate object graph safely
// old style without optional chaining: 
if (box.innerBox && box.innerBox.foo){ }
//also works for functions:
box.nullFunction()?.foo
// and nonexistent methods and members:
box?.foo() && box?.bar


이 코드는 원하는 것을 간결하게 표현한다. 원하는 것이 존재하면 그것으로 이동하고, 없으면 undefined를 반환한다.


globalThis

코어 언어에 추가된 또 다른 실용적인 요소인 globalThis는 현재 컨텍스트에서 사용할 수 있는 전역 객체의 추상화다. 가장 잘 알려지고 오래된 것은 브라우저에서 실행되는 자바스크립트의 창 객체다. Node.js, 웹 작업자와 같은 환경에는 이와 동일한 용도로 사용되는 자체 루트 객체인 global과 self가 있다.

globalThis는 코드가 어느 환경에서 실행되든 전역 루트 객체로 귀결되는 하나의 식별자에 모든 루트 객체를 래핑해 더 이식성이 높은 코드를 만들고 존재 여부 확인(existence check) 절차를 없앤다.


BigInt

ES11 이전에는 자바스크립트에서 안전하게 참조 가능한 가장 큰 정수가 9007199254740991(또는 2^53 - 1)이 되는 Number.MAX_SAFE_INTEGER였다. 별다른 문제가 되지 않는 경우도 있지만, 많은 애플리케이션에서 이 수는 너무 작아서 프로그래머가 big-integer와 같은 래퍼를 사용해야 했다(big-integer 라이브러리는 polyfill로 여전히 유용함).

전통적인 Number 형식을 사용해서 이 같은 큰 수를 표현하는 경우 예기치 못한 끝수처리가 발생하게 된다. 예를 들어 -2^53 -1과 같은 아주 작은 수에도 마찬가지로 적용된다.

ES11에는 이런 시나리오를 위해 BigInt 형식이 도입됐다. mySafeBigNumber = 9007199254740992n과 같이 숫자 끝에 n을 추가하면 이 형식을 정의할 수 있다. 

BigInt는 기존 Number 형식에 기교를 부린 것이 아닌 새로운 형식이다. typeof 12로는 number를 얻으며, typeof mySafeBigNumber로는 bigint를 얻는 것이다. 또한 mySafeBigNumber – 12와 같이 어떤 잘못을 저지르려고 하면 “BigInt와 다른 형식을 함께 사용할 수 없습니다. 명시적 변환을 사용하십시오.(Cannot mix BigInt and other types, use explicit conversions.)”라는 오류가 표시된다.

이 둘 사이에서 변환하려면 생성자를 사용해야 한다. 예를 들어 let myUnsfeBigNumber = Number(mySafeBigNumber)를 사용하는 방법이 있는데, 이 경우에는 안전하지 않은 큰 Number 객체가 생성되므로 이렇게 하면 안 된다. 따라서 BigInt가 MAX_SAFE_INTEGER 값보다 작게 축소됐을 경우에만 변환해야 한다.

비슷한 맥락에서, Number와 BigInt의 동일성을 비교하는 경우 형식이 다르므로 항상 false로 반환된다.

BigInt는 2진수, 10진수 및 16진수 표기법도 지원하며, 값을 Number로 변환하기 위한 용도의 단항 더하기 연산자를 제외한 모든 일반적인 수학 연산자를 지원한다. 이렇게 만들어진 이유는 명확하지 않지만, JS가 아닌 기존 asm 코드의 작동 중단을 유발하는 변화를 피하는 데 있다.

마지막으로, 이름 탓에 헷갈리기 쉽지만 BigInt는 정수를 나타낸다. x = 3n; x = x / 2n; 코드를 실행하면 x의 값 1n을 결과로 얻는다. BigInts가 숫자의 소수 부분을 버리기 때문이다.


null 병합

null 병합(nullish coalescing)은 ES11에서 가장 미묘한 이름을 가진 기능으로, 선택적 체이닝과 마찬가지로 null 값 처리에 사용된다. null 병합은 새로운 기호인 이중 물음표 ??이기도 하다. 논리적 OR 연산자(이중 파이프 기호 ||)와 비슷하게 동작하는 논리적 연산자다.

??와 ||의 차이점은 연산자가 null 값을 처리하느냐, false 값을 처리하느냐에 있다. 자바스크립트 개발자 대부분은 true/false 테스트에서 부울이 아닌 값을 처리하는 자바스크립트 언어 특유의 방식에 익숙하다. (짧게 정리하면 false, 0, null, 빈 문자열, undefined는 false로 간주되며 그 외의 모든 것은 true가 된다. 더 자세한 내용은 MDN 웹 문서에서 확인할 수 있다.) 이런 부분을 활용해서 개발자는 어떤 것의 존재 여부를 테스트하고 존재하지 않는 경우 다른 것을 사용하는 경우가 많다. 
 

let meaningOfLife = answer || 42;


이렇게 하면 answer에서 어떤 것의 존재 여부를 신속하게 테스트하면서 일종의 기본값을 설정할 수 있다. answer에 아무런 false 값이 설정되지 않은 경우 meaningOfLife의 기본값이 42가 되도록 설정할 때는 문제가 없다. 그러나 실제 null에 해당하는 값(null 또는 undefined)이 있을 때만 42가 되도록 하려면 어떻게 해야 할까?

다음과 같이 ??를 사용하면 간단하다.
 

let meaningOfLife = answer ?? 42;


명확한 이해를 돕기 위해 answer에 값으로 0을 설정하고, || 또는 ??를 사용해서 meaningOfLife에 값을 할당하는 방법에 대해 생각해 보자(예시 2 참조). 값이 설정되면 0을 유지하고, answer가 실제로 비어 있는 경우 42를 사용하고자 하는 경우다.

예시 2. null 병합의 실제 사용

let answer = 0;
let meaningOfLife = answer ?? 42; // meaningOfLife === 0 - what we want
let meaningOfLife = answer || 42; // meaningOfLife === 42 - not what we want
let answer = undefined;
let meaningOfLife = answer ?? 42; // meaningOfLife === 42 - what we want
let meaningOfLife = answer || 42; // meaningOfLife === 42 - also what we want


String.prototype.matchAll

ES11 사양에서는 String 프로토타입에 새 메서드인 matchAll이 추가됐다. 이 메서드는 String 인스턴스에 정규식을 적용하고 조건에 맞는 모든 반복자를 반환한다. 예를 들어 문자열에서 t 또는 T로 시작하는 모든 단어를 스캔하려는 경우 예시 3과 같이 해서 원하는 결과를 얻을 수 있다.

예시 3. matchAll 사용

let text = "The best time to plant a tree was 20 years ago. The second best time is now.";
let regex = /(?:^|\s)(t[a-z0-9]\w*)/gi; // matches words starting with t, case insensitive
let result = text.matchAll(regex);
for (match of result) {
  console.log(match[1]);
}


regex 구문의 본질적인 복잡함은 잠시 접어두고, 예시 3에 정의된 정규식이 t 또는 T로 시작하는 단어를 찾는다는 점에 주목하자. matchAll()은 이 정규식을 문자열에 적용해서 손쉽게 결과를 살펴보면서 일치 그룹에 액세스할 수 있게 해주는 반복자를 반환한다.


동적 가져오기

ES11에서 모듈을 가져오는 방법이 더 발전해 이제 비동기적으로 로드되는 가져오기를 임의로 배치할 수 있다. 이를 코드 분할이라고도 하는데, 사실 개발자가 빌드 툴을 통해 오래전부터 해오던 것이다. 동적 가져오기는 사양이 실제 관행을 따라가는 사례에 해당한다.

이 구문의 간단한 예는 예시 4에서 확인할 수 있다.

예시 4. 비동기 모듈 가져오기

let asyncModule = await import('/lib/my-module.ts');


이 같은 종류의 가져오기는 사용자 이벤트에 대한 응답을 포함해 JS 코드 어디에나 나타날 수 있다. 실제로 필요해질 때 손쉽게 모듈을 지연 로드(lazy-load)할 수 있다.


Promise.allSettled()

promise.allSettled() 메서드를 사용하면 프로미스의 결과(이행 또는 거부)를 관찰할 수 있다. 거부된 프로미스 또는 오류 비 프로미스(non-promise)로 종료되는 promise.all()과는 대비된다. promise.allSettled()는 각 프로미스의 결과를 기술하는 객체 배열을 반환한다.

이 기능은 서로 무관한 프로미스 그룹을 관찰할 때, 즉 일부가 중간에 실패하더라도 모든 프로미스의 결과를 알고자 할 때 유용하다. 예시 5를 확인해 보자.

예시 5. promise.allSettled()의 예

let promise1 = Promise.resolve("OK");
let promise2 = Promise.reject("Not OK");
let promise3 = Promise.resolve("After not ok");
Promise.allSettled([promise1, promise2, promise3])
    .then((results) => console.log(results))
    .catch((err) => console.log("error: " + err));


이 경우 catch 람다는 실행되지 않는다. (promise.all을 사용했다면 실행됨) 대신 then 절이 실행되고, 예시 6의 내용이 담긴 배열이 반환된다. 여기서 핵심은 세 번째 프로미스가 실행됐고, 그 전에 promise2가 실패했음에도 불구하고 결과를 볼 수 있다는 것이다.

예시 6. promise.allSettled() 결과

[
  {"status":"fulfilled","value":"OK"},
  {"status":"rejected","reason":"Not OK"},
  {"status":"fulfilled","value":"After not ok"}
]


* 구문 내보내기

이 기능은 모듈에서 export * 기능을 추가한다. 다른 모듈에서의 import *는 이미 가능했지만 이제 예시 7과 같이 동일한 구문으로 내보내기도 가능하다.

예시 7. export *의 예

Export * from '/dir/another-module.js'


일종의 모듈 체이닝으로, 현재 모듈 내에서 다른 모듈의 모든 것을 내보낼 수 있게 해준다. 예를 들면 다음과 같이 다른 모듈을 API로 통합하는 모듈을 구축할 때 유용하다.


for-in 순서의 표준화

자바스크립트에서 for-in 루프의 컬렉션에 대한 열거 순서는 사실상 모든 자바스크립트 환경(브라우저, Node.js 등)이 보장했는데, 공식적인 것은 아니었다. 이제 ‘사실상의 표준’이 자바스크립트의 기본 사양에 흡수됐다. 이론이 관행을 뒤따른 사례다.


진화하는 사양

소프트웨어 분야에서 가장 재미있는 부분은 다양한 진화를 지켜보는 것이다. 프로그래밍 언어는 모든 것의 가장 기본적인 표현으로, 컴퓨터과학의 철학이 일상적인 코딩 현실과 만나는 지점이다. 자바스크립트는 연간 릴리스 일정에 따라 다른 현존하는 언어와 마찬가지로 계속해서 성장하고 있다.

editor@itworld.co.kr
Sponsored

회사명 : 한국IDG | 제호: ITWorld | 주소 : 서울시 중구 세종대로 23, 4층 우)04512
| 등록번호 : 서울 아00743 등록발행일자 : 2009년 01월 19일

발행인 : 박형미 | 편집인 : 박재곤 | 청소년보호책임자 : 한정규
| 사업자 등록번호 : 214-87-22467 Tel : 02-558-6950

Copyright © 2024 International Data Group. All rights reserved.