ECMAScript 2022

7분

7/10/2022

Thumbnail

ES2022

얼마 전 2022년 6월 22일에 ECMA International TC39는 ECMAScript 2022(EMCA-262 13판)를 승인했습니다.

현재 JavaScript는 처음에 나온 모습과 지금을 비교해보면 다른 언어라고 해도 믿을 정도로 달라진 점이 많습니다.

V8 등장과 함께 node.js의 시대를 열면서 JavaScript는 폭발적인 성장을 했습니다. 이젠 서버(node.js), 브라우저, 모바일 어플리케이션(react-native), 데스크탑 어플리케이션(electron, tauri) 등등 거의 모든 플랫폼에서 많이 사용되고 있습니다.

그래서 이런 표준, 명세화 작업이 더 중요하고 더 어려울 것 같습니다.

현재 이 JavaScript 표준과 명세를 담당하는 곳이 ECMA International(TC39)이고, ECMA-262라는 이름으로 발표됩니다.

다시 말해 JavaScript는 ECMA-262 명세서를 구현한 구현체라고 보면 됩니다. (실제로는 JavaScript 엔진(브라우저나 노드 등)에서 이와 같은 기능을 제공해주어야 사용할 수 있습니다.)

ECMAScript 2022에 포함된 새로운 기능과 발전을 보기에 앞서 이 ECMA라는 곳은 어떤 곳인지 살짝만 보고 넘어가도록 하겠습니다.

ECMA International

Ecma develops and publishes international standards for the information and communication industry.

Ecma는 정보 통신 산업을 위한 국제 표준을 개발하고 발표합니다. - ECMA International - mission

1959년 컴퓨터 사용이 증가하면서 프로그래밍, 입력 및 출력 코드와 같은 운영 기술의 표준화 필요성이 나타났습니다. 1961년 5월 17일 협회가 공식 출범했고, 첫 회의에 참석한 모든 회사가 회원이 되었습니다. 첫 번째 의회는 1961년 6월 17일에 개최되었다.

“European Computer Manufacturers Association”으로 시작하여 1994년 국제적인 조직이 되면서 ECMA International로 이름이 바뀌었고, "ECMA"는 역사적 이유로 유지되었습니다.

여기에서는 많은 국제 표준을 다루고 있습니다. ECMA-408(Dart 언어 명세), ECMA-334(C# 언어 명세), 그리고 ECMA-262(ECMAScript 언어 명세) 등이 있습니다.

JavaScript는 표준화, 명세화를 위해 ECMA Internaltional에 제출되었고, 1997년 ECMA-262 1판 이 발표되었습니다.

ES5, ES6를 많이 알고 계실텐데 그 이유는 1999년 ES3이 발표되고, 9년이 지나 2008년 발표될 ES4가 표준 합의에서 기각(하위호환성 문제 이슈, 그렇지만 나중에 많은 부분이 채택되었습니다)되고, 약 10년 만에 2009년 ES5가 발표되고, 다시 6년만에 ES6가 발표되면서 큰 변화를 가져왔기에 이 부분이 모던 JavaScript로 자리 잡힌게 크게 인식되는 것 같습니다.

현재는 TC39가 이 명세를 관리하고 최근에 발표된 ECMA-262 13판이 ECMAScript 2022로 불려집니다.

TC39

Technial Committee number 39

ECMAScript 언어 표준과 TC39

ecma-script-2022

ECMA International 기술 위원회 중 하나로 ECMA-262 명세를 관리하는 위원회입니다.

구성원은 Mozilla, Google, Apple, Microsoft 등 메이저 브라우저 벤더를 비롯해 Meta, Twitter 등의 다양한 단체로 이루어져 있습니다.

TC39에서 내리는 결정은 단순 다수결이 아닌 컨센서스 체제로 이루어집니다.

TC39의 프로세스는 다음 4단계를 통해서 합의를 이룹니다.

  • stage 0(strawman) 제안하는데 별다른 제약이 없기에 제안을 하게되면 stage 0으로 제안이 됩니다.
  • stage 1(proposal): stage 1로 오기 위해서는 챔피언(해당 제안을 책임지고 다음 단계로 끌고 나갈 TC39 구성원)을 구해야 합니다. 위원회에서 본격적으로 시간과 노력을 투자해 논의할 의사를 표시한 것으로 해석됩니다. image.png
  • stage 2(draft) stage 2로 오기 위해서는 명세서 초안이 필요합니다. 실제로 표준에 편입될 경우 사용할 명세의 초기 버전입니다. stage 2 이후로는 상대적으로 적은 변경만이 허용됩니다.
  • stage 3(candidate) stage 3는 제안이 완성에 가깝고, 명세를 마무리하는 단계입니다. 심각한 문제가 발견되지 않는 이상 변경이 허용되지 않습니다.
  • stage 4(finished) stage 4는 모든 단계를 마치고 다음 표준에 포함될 발표만을 기다리고 있는 단계입니다.

babel은 이러한 새로운 표준을 지원하지 않는 JavaScript엔진에도 동작할 수 있게 해주는 transpiler 입니다.

babel의 경우 이전에 babel preset을 보면 @babel/preset-stage-3과 같은 preset을 볼 수 있었는데 여러가지 이유, 현재는 deprecated 되었고, 플러그인 형태로 stage에 있는 기능들을 써볼 수 있습니다.

stage 3에 있는 제안들은 대부분의 경우 좋은 제안들이고 명세가 상당히 안정되고 많은 컨센선스를 이룬 이후이기에 많은 곳에서 먼저 사용되는 부분이 있는 것 같습니다.

ECMAScript 2022

2022년 6월 22일

(별개로 대부분의 브라우저에서는 이를 이미 사용할 수 있습니다.)

already-use-private

ECMAScript 2022에 포함된 명세는 다음과 같습니다.

Class Fields

https://github.com/tc39/proposal-class-fields

Class 필드 선언 방식

기존 ES5 문법에서 클래스 내부에 public 필드 선언을 위해서 생성자 내부에서 this를 사용하여 public 필드를 선언할 수 있었습니다.

class Counter {
  clicked() {
    this.x++;
  }

  constructor() {
    this.x = 0;
  }
}

이제는 다른 언어와 비슷한 syntax로 클래스 내부에서 바로 public 필드를 선언할 수 있습니다. (사실 원래 되는줄 알았는데 이번에 적용된것이라는 것에 살짝 놀랐습니당)

class Counter {
  x = 0;

  clicked() {
    this.x++;
  }

  constructor() {}
}

private 접근제어자 추가

# prefix를 필드명에 붙이면 private 필드로 사용할 수 있습니다.

class Counter {
  x = 0;
  #privateField = 'private Field';

  clicked() {
    this.x++;
  }

  constructor() {}
}

Static Class 필드와 private Static 메소드

ES5에서 static 필드, 메소드를 사용하기 위해서는 prototype 방식으로 선언해야 했습니다.

class Counter {
  x = 0;
  #privateField = 'private Field';

  clicked() {
    this.x++;
  }

  constructor() {}
}

Counter.staticField = 'static field';
Counter.staticMethod = function () {
  console.log('static method');
};

ES2022에서는 static 을 붙이면 정적으로 생성되어 사용할 수 있습니다.

class Counter {
  x = 0;
  #privateField = 'private Field';

  static staticField = 'static field';
  static staticMethod() {
    console.log('static method');
  }

  clicked() {
    this.x++;
  }

  constructor() {}
}

위 syntax를 모두 조합해서도 사용할 수 있습니다.

class Counter {
  x = 0;
  #privateField = 'private Field';

  static #staticField = 'static field';
  static #staticMethod() {
    console.log('static method');
  }

  clicked() {
    this.x++;
  }

  constructor() {}
}

Ergonomic brand checks for Private Fields

https://github.com/tc39/proposal-private-fields-in-in

private 필드와 관련이 있는 기능입니다. Class내에 필드가 private 필드인지 체크하는게 try/catch등을 이용해야 하고 그로 인해 복잡한 코드를 야기한다는 것이었습니다.

기존 in 연산자 를 통해 객체 내에 속성이 존재하는지를 체크할 수 있었는데 이를 이용해 private 필드 체크를 더 직관적으로 할 수 있는 기능입니다.

class C {
  #data = null; // populated later

  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; // oops! might have gotten here because `#getter` threw :-(
    }
  }
}

위 코드를 보면 직관적으로 에러가 발생한 원인을 찾기가 쉽지는 않습니다.

class C {
  #brand;

  #method() {}

  get #getter() {}

  static isC(obj) {
    return #brand in obj && #method in obj && #getter in obj;
  }
}

훨씬 더 직관적으로 private 필드를 체크할 수 있습니다.

Class Static Block

https://github.com/tc39/proposal-class-static-block

static 제안과 연관되어 있습니다.

현재 제안은 ClassDefinitionEvaluation 동안 클래스의 정적 측의 필드별 초기화를 수행하는 메커니즘을 제공하지만 쉽게 해결되지 않는 몇 가지 케이스가 있습니다.

예를 들어 초기화 중에 값을 선언해야 한다거나 단일 값에서 2개의 필드를 선언 할 때 클래스 외부에서 접근해서 처리해야 합니다.

// without static blocks:
class C {
  static x = ...;
  static y;
  static z;
}

try {
  const obj = doSomethingWith(C.x);
  C.y = obj.y
  C.z = obj.z;
}
catch {
  C.y = ...;
  C.z = ...;
}

위 처럼 클래스 외부에서 static 필드에 접근해서 처리해야 됩니다. 만약 private 필드라고 한다면 더 복잡한 방식을 사용해야 합니다.

// with static blocks:
class C {
  static x = ...;
  static y;
  static z;
  static {
    try {
      const obj = doSomethingWith(this.x);
      this.y = obj.y;
      this.z = obj.z;
    }
    catch {
      this.y = ...;
      this.z = ...;
    }
  }
}

이처럼 static block을 선언해두고 클래스의 정적 필드 접근을 허용한다면 private도 쉽게 처리할 수 있고 직관적으로 처리하기 쉽습니다.

이런 모티베이션으로 이번 제안이 채택되었습니다.

RegExp Match Indices

https://github.com/tc39/proposal-relative-indexing-method

기존에는 시작 인덱스 속성만 조회할 수 있었는데 ES2022에서는 시작과 끝의 인덱스 정보를 얻을 수 있게 되었습니다.

플래그 d 를 regex에 작성하면 exec, match 등을 사용하면 매칭되는 부분의 시작과 끝의 인덱스 정보를 얻을 수 있습니다.

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;

regexp-d

Top-level await

https://github.com/tc39/proposal-top-level-await

이젠 모듈 파일 내에서 await 함수를 사용하기 위해 IIFE 패턴 등을 사용하지 않고도 함수 그대로 사용할 수 있습니다.

const response = await fetch('http://example.com/foo.json');
export const result = await response.json();

top-level-await

이 기능을 통해 db connection이나 dynamic import 등 에서 편하게 사용할 수 있게 되었습니다.

.at()

https://github.com/tc39/proposal-relative-indexing-method

문자열, 배열 등에서 음수 인덱싱을 가능하게 해주는 메소드 입니다.

Python을 쓰다가 JavaScript을 쓰면서 아쉬웠던 기능 중에 하나였습니다.

Python의 경우 Array 인덱스로 음수 인덱싱을 통해 끝에서부터 인덱싱 처리를 해주었기 때문에 편리하게 Array를 다룰 수 있는 점이 좋았었는데 JavaScript에도 이와 같은 기능이 있으면 좋을 것 같다는 생각을 했는데 참 다행이라고 생각합니다.

JavaScript는 거의 모든 것이 객체이기에 Python 처럼 처리하기는 불가능합니다.

그래서 메소드 형태로 표준이 채택되었습니다.

const numbers = [1, 2, 3, 4, 5];
numbers.at(-1); // 5

at

Accessible Object.prototype.hasOwnProperty

https://github.com/tc39/proposal-accessible-object-hasownproperty

hasOwnProperty는 객체에 해당 속성을 가지는지에 대한 메소드입니다. 기존에도 존재했지만 반드시 객체를 프로토타이입을 통해서만 접근이 가능했습니다.

이번에 채택된 표준은 정적으로도 객체에 접근하여 사용할 수 있도록 하는 것입니다.

in 연산자와 차이점은 프로토타입 상속에 의한 속성인지, 객체 고유의 속성인지를 확인하는 차이가 있습니다.

has-own

Error Cause

https://github.com/tc39/proposal-error-cause

런타임 에러가 깊게 중첩된 경우에 발생했다면 정확히 어디서 발생한지 알지 못하면 처리가 어려울 수 있습니다.

Error.prototype.cause는 에러 진단을 돕기 위해서 오류 메시지, 오류 인스턴스 속성과 같은 컨텍스트 정보로 오류를 담아 에러를 처리할 수 있게 하는 속성으로 채택되었습니다.

error-cause.png

마무리하며

새로운 Syntax, 기능들이 추가되는게 기쁘면서도 한 편으로 부담되기도 합니다.

언제까지 배워야 하는걸까? 라는 생각이 조금은 들긴 합니다만 오히려 더 편하고 직관적으로 개발을 할 수 있게 되어서 기쁜 마음이 조금은 더 큰 것 같습니다.

이미 브라우저나 node.js는 이와 같은 기능들을 대부분 미리 제공하고 있습니다.

굳이 babel을 쓰지 않고도 기능을 제공할 수 있다면 무거운 트랜스파일링은 제거할 수 있지 않을까 하는 생각도 듭니다.

TypeScript와 문법이 조금씩 헷갈리는 구간(특히 Class)이 있긴한데... 뭐... 괜찮은 것 같습니다.

이번에 TC39 문서를 조금 자세하게 살펴보았는데, 그 전에는 언어 명세서를 직접 본다는 것에 부담감이 컸던 것 같은데, 이렇게라도 보니 조금은 그 문턱이 낮아진 것 같아서 다행입니다.

좋은 제안이 채택된 것 같아서 앞으로 자주 쓰면서 익혀나가야 할 것 같습니다.

reference

마지막 업데이트

7/10/2022


Avatar

JHSeo

배우는 것을 좋아하고 관심이 많은 웹 엔지니어 입니다. 느리더라도 꾸준하게 성장하려고 노력하는 개발자입니다.