BlogApplicationsGuestbook

Github | X (Twitter)

Copyright © 2024 OPNay - All Reserved | Privacy Policy

ES2022 살펴보기

2022.11.30 05:01

x

지난 22년 6월에 ECMA 스크립트의 13번째 표준안인 ES2022가 발표되었습니다. (링크) 이번 버전에서는 클래스 인스턴스 필드, static 필드, #name 형식의 private 필드, private 필드에 대한 in 키워드 지원 등 클래스의 필드에 대한 지원이 추가되었고, Error 내장 클래스의 cause 옵션 지원, RegExp의 d 옵션 지원 등 많은 부분이 추가되었습니다.

 

이번 포스트를 통해 다양한 변경 사항을 확인해보시면 좋겠습니다.


Class 필드 지원

ES6에서 추가되었던 클래스에서는 **“필드”**라는 개념이 없고 기존 prototype 방식의 “속성” 밖에 없었습니다. 이 때문에 표준으로 작성하게 되면 이런 클래스가 만들어지게 됩니다.

class Person { constructor () { this.name = '철수'; this.age = 13; } }
javascript

ES6 표준안대로 만든 클래스

이번에 추가된 필드에서는 다른 언어에서 사용하던 방식처럼 메서드를 작성한 클래스 블록에서 필드를 선언 할 수 있습니다. name = '철수'; 를 작성함으로 이 클래스의 인스턴스에는 name 이라는 필드가 생기게되고, 결과적으로 constructor를 생략하는게 가능해졌습니다!

class Person { name = '철수'; age = 13; }
javascript

ES2022 표준안으로 재작성한 Person 클래스

 

Class static 지원

JavaScript에는 static(정적) 선언 또한 없었습니다. 클래스 자체에서 필드 혹은 메소드를 접근하는 방법은 prototype 밖에 없었죠. 근데 이번에 추가되었습니다.

class Count { static num = 0; static increase() { return num += 1; } } Count.num // 0 Count.increase() // 1 Count.num // 1
javascript

ES2022의 static 예제

 

Class private 필드, 메서드

ES6에 추가된 클래스는 아직까지도 접근 제어자가 없었습니다. 이번에 private 필드 선언 방식이 추가되었는데, 이 방식이 저희가 Typescript나 다른언어에서 사용한 접근제어자 방식과는 다릅니다. 필드를 숨기고 싶으면, 이름 앞에 #을 붙이는걸로 private 필드로 만들어보세요.

class Person { name = '김철수'; #age = 13; } const person = new Person(); person.name // '김철수' person.#age // Uncaught SyntaxError: Private field '#age' must be declared in an enclosing class
javascript

ES2022의 private 필드 예제

메서드의 선언 또한 동일하게 할 수 있습니다.

class Count { #num = 0; get num() { return this.#num; } increase() { return this.#num += 1; } #decrease() { return this.#num -= 1; } } const counter = new Count(); counter.increase(); // 1 counter.num; // 1 counter.#decrease(); // Uncaught SyntaxError: Private field '#decrease' must be declared in an enclosing class
javascript

ES2022의 private 메서드 예제

Private 필드에 대한 in 키워드 지원

private 필드에 대한 논의가 있던 도중에 인스턴스에 private 필드 자체가 있는지 확인하려는 로직이 필요해졌습니다. 그때 나온 코드는 다음과 같았죠.

class C { #brand; static isC(obj) { try { obj.#brand; return true; } catch { return false; } } }
javascript

private 필드를 확인하는 로직

private 필드를 접근할때 해당 객체에 없다면, SyntaxError 가 나오던 오류를 try ... catch 로 잡아내 확인하는 로직이었죠. 엄청나게 불편한 코드입니다. 게다가 getter를 사용하게 되면 다음과 같이 의도치 않는 상황을 맞을 수 있습니다.

class C { #data = null; // 나중에 초기화 get #getter() { if (!this.#data) { throw new Error('no data yet!'); } return this.#data; } static isC(obj) { try { obj.#getter; return true; } catch { return false; // `#getter`는 존재하는데 throw를 던져 여기로 와버렸습니다. } } }
javascript

private 필드를 확인하는 로직 중 예외사항

이렇게 되면서 isC를 통해 C 클래스를 판별하는 메서드를 만들 수 있었지만, #getter 에서 throw 를 사용하는 로직 때문에 isC 메서드를 사용할 수 없게 되었습니다. 이를 해결하기 위해 실제 getter를 실행하지 않고 필드 자체를 확인하기 위해 in 이라는 키워드가 추가되었습니다. #privateField in object 라는 문법을 가지고 있고, 결과는 boolean입니다. 예제를 보시면 좀더 이해하기 편하실겁니다.

class C { #brand; #method() {} get #getter() {} static isC(obj) { return #brand in obj && #method in obj && #getter in obj; } }
javascript

ES2022에 추가된 private 필드에 대한 in 키워드

이로써 안전하게 클래스를 확인 할 수 있게 되었습니다!

 

Array.prototype.at

내장 indexable한 배열(Array, String, TypedArray)에 대해 at 이라는 함수가 추가되었습니다. Python 같은 언어에서는 음수 인덱싱으로 배열의 끝을 가져오는 것이 가능합니다만, JS에서는 그렇지 않죠. 이를 해결하기 위해 나온 함수입니다.

const arr = [1, 2, 3, 4]; a[-1] === undefined; a.at(-1) === 4;
javascript

Array.prototype.at 예제

 

Error.cause 추가

여러분이 Error에 대한 상세한 정보를 적으러면 어떻게하고 있나요? 제안 내용에서 보여준 예제를 가져왔습니다.

async function doJob() { const rawResource = await fetch('//domain/resource-a') .catch(err => { // How to wrap the error properly? // 1. throw new Error('Download raw resource failed: ' + err.message); // 2. const wrapErr = new Error('Download raw resource failed'); // wrapErr.cause = err; // throw wrapErr; // 3. class CustomError extends Error { // constructor(msg, cause) { // super(msg); // this.cause = cause; // } // } // throw new CustomError('Download raw resource failed', err); }) const jobResult = doComputationalHeavyJob(rawResource); await fetch('//domain/upload', { method: 'POST', body: jobResult }); } await doJob(); // => TypeError: Failed to fetch
javascript

제안서에 있는 throw Error에 대한 예제

아마 저 내용대로 메시지 자체레 추가를 하거나, 원인을 Error 객체에 원본 객체를 할당해서 사용을 하거나 별도의 Error클래스를 상속받아 사용하려 했을 겁니다. 이는 정규내용이 없어 제대로된 원인을 파악하기 힘들게 만드는 문제가 있습니다. 이에 따른 대안으로 Error클래스 생성자에 cause라는 옵션을 추가되어 보다 쉽고, 정규화되어 사용할 수 있게 되었습니다.

async function doJob(resource) { const rawResource = await fetch('//domain/resource-a') .catch(err => { throw new Error('Download raw resource failed', { cause: err }); }); const jobResult = doComputationalHeavyJob(rawResource); await fetch('//domain/upload', { method: 'POST', body: jobResult }) .catch(err => { throw new Error('Upload job result failed', { cause: err }); }); } try { await doJob(); } catch (e) { console.log('error:', e); console.log('cause:', e.cause); } // Error: Upload job result failed // Caused by TypeError: Failed to fetch
javascript

ES2022의 Error.cause 옵션 예제

 

RegExp의 d 플래그

정규표현식은 JS에서 생각보다 자주 쓰이고 있습니다. 문자열이 이메일인지, 비밀번호 조건에 부합한지, 특정 조건의 문자열을 찾을 때 등 다양한 곳에서 사용되고 있는데, 이번에 추가된 d 플래그는 문자열을 찾을 때 좀더 찾기 편하게 도와주는 옵션입니다. 예제를 보면 어떤 내용인지 이해가 되실겁니다.

const re1 = /a+(?<Z>z)?/d; // indices are relative to start of the input string: const s1 = "xaaaz"; const m1 = re1.exec(s1); m1.indices[0][0] === 1; m1.indices[0][1] === 5; s1.slice(...m1.indices[0]) === "aaaz"; m1.indices[1][0] === 4; m1.indices[1][1] === 5; s1.slice(...m1.indices[1]) === "z"; m1.indices.groups["Z"][0] === 4; m1.indices.groups["Z"][1] === 5; s1.slice(...m1.indices.groups["Z"]) === "z"; // capture groups that are not matched return `undefined`: const m2 = re1.exec("xaaay"); m2.indices[1] === undefined; m2.indices.groups["Z"] === undefined;
javascript

ES2022의 정규 표현식 d 플래그

정규식을 얘기하자면 a라는 문자열이 최소 1개 이상이고, Z라는 이름의 캡처 그룹을 만들어 그 그룹의 정규식은 z입니다. 이번에 사용된 캡처 그룹((?<name>))에 대한 내용은 .NET, MDN 문서에 잘 정리되어있으니 확인해보세요.

RegExp 클래스의 exec를 실행하게되면 나오게 되는 결과 배열 데이터 에서 indicies (index 복수형)의 이름이 추가되었습니다. 이 내용에는 정규식에 부합하는 문자열의 범위를 가지고 있습니다. 그와 함께 캡처 그룹에 대한 범위도 갖고있습니다. 좀더 쉬운 설명을 위해 타입스크립트 내부 타입을 가져와 봤습니다.

interface RegExpExecArray { indices?: RegExpIndicesArray; } interface RegExpIndicesArray extends Array<[number, number]> { groups?: { [key: string]: [number, number]; }; }
typescript

indicies가 들어있는 RegExp.prototype.exec 결과 인터페이스

indices 를 배열 접근하게 되면, 정규식 자체에 부합하는 내용들이 나오게되고, groups 속성을 통해 (?<name>) 으로 그룹화한 내용들 까지 나오게 되는거죠.

 

마무리

이번 ES2022에는 그동안 ES6에 머물러있던 클래스에 대한 제안들이 들어오며 어색했던 클래스에 대한 내용이 많이 보강되었습니다. 내용은 알차지만, 실제로 Typescript나 Babel을 쓰고 있었다면 이게 안되던거였어? 라는 내용이 많았죠. 둘다 staging에 대한 내용을 미리 사용 할 수 있도록 기능을 미리 추가해 놓기 때문입니다.

앞으로 새로운 내용이 들어와 좀더 사용하기 편한 언어로 발전했으면 하지만, 이번 내용은 아쉬움이 많이 남는 업데이트 였습니다.

 

ES2022 살펴보기

    GitHub - tc39/proposal-class-fields: Orthogonally-informed combination of public and private fields proposals

    Orthogonally-informed combination of public and private fields proposals - tc39/proposal-class-fields

    GitHub - tc39/proposal-class-fields: Orthogonally-informed combination of public and private fields proposalshttps://github.com/tc39/proposal-class-fields

    GitHub - tc39/proposal-class-fields: Orthogonally-informed combination of public and private fields proposals

    GitHub - tc39/proposal-static-class-features: The static parts of new class features, in a separate proposal

    The static parts of new class features, in a separate proposal - tc39/proposal-static-class-features

    GitHub - tc39/proposal-static-class-features: The static parts of new class features, in a separate proposalhttps://github.com/tc39/proposal-static-class-features/

    GitHub - tc39/proposal-static-class-features: The static parts of new class features, in a separate proposal

    GitHub - tc39/proposal-relative-indexing-method: A TC39 proposal to add an .at() method to all the basic indexable classes (Array, String, TypedArray)

    A TC39 proposal to add an .at() method to all the basic indexable classes (Array, String, TypedArray) - tc39/proposal-relative-indexing-method

    GitHub - tc39/proposal-relative-indexing-method: A TC39 proposal to add an .at() method to all the basic indexable classes (Array, String, TypedArray)https://github.com/tc39/proposal-relative-indexing-method/

    GitHub - tc39/proposal-relative-indexing-method: A TC39 proposal to add an .at() method to all the basic indexable classes (Array, String, TypedArray)

    GitHub - tc39/proposal-regexp-match-indices: ECMAScript RegExp Match Indices

    ECMAScript RegExp Match Indices. Contribute to tc39/proposal-regexp-match-indices development by creating an account on GitHub.

    GitHub - tc39/proposal-regexp-match-indices: ECMAScript RegExp Match Indiceshttps://github.com/tc39/proposal-regexp-match-indices

    GitHub - tc39/proposal-regexp-match-indices: ECMAScript RegExp Match Indices

    GitHub - tc39/proposal-private-fields-in-in: EcmaScript proposal to provide brand checks without exceptions

    EcmaScript proposal to provide brand checks without exceptions - tc39/proposal-private-fields-in-in

    GitHub - tc39/proposal-private-fields-in-in: EcmaScript proposal to provide brand checks without exceptionshttps://github.com/tc39/proposal-private-fields-in-in

    GitHub - tc39/proposal-private-fields-in-in: EcmaScript proposal to provide brand checks without exceptions

    GitHub - tc39/proposal-error-cause: TC39 proposal for accumulating errors

    TC39 proposal for accumulating errors. Contribute to tc39/proposal-error-cause development by creating an account on GitHub.

    GitHub - tc39/proposal-error-cause: TC39 proposal for accumulating errorshttps://github.com/tc39/proposal-error-cause

    GitHub - tc39/proposal-error-cause: TC39 proposal for accumulating errors