원문 링크 : what's new in es2022?
반갑습니다! 아마 이 글을 읽는 당신은 가까운 미래에 우리가 쓸 수 있는 자바스크립트 기능에 대해 관심이 있다는 거겠죠. 하지만, 만약 당신이 TC39, ECMA, ECMAScript가 생소하다면 이 기사를 읽고 오시길 바랍니다. 이 글의 내용들을 이해하려면 이들에 대해서 알아야 하기 때문이죠
간략 설명
- ECMA : Ecma International. 정보와 통신 시스템을 위한 국제적 표준화 기구
- TC39 : ECMA의 여러 기술 위원회 중 ECMA-262(범용 목적의 스크립트 언어에 대한 명세를 규정한 하나의 기술 규격) 명세의 관리를 담당하는 위원회
- ECMAScript : ECMA-262 기술 규격에 의해 정의된 범용 스크립트 언어. 스크립트 언어가 준수해야 하는 규칙, 세부 사항 및 지침을 제공
TC39는 자바스크립트 언어를 더 낫게 만드려는 공통된 목표 아래 다양한 배경을 지닌 사람들이 모인 놀랍고 헌신적인 그룹입니다. 이를 위해, 그들은 "웹을 망치지 않는다(don't break the web)" 이라는 하나의 규칙을 갖고 있습니다. 언어에 대한 그들의 접근 방식은 마치 환자에게 의학적으로 접근하는 것과 같습니다. "먼저, 해를 끼치지 말라(First, do no harm" 모든 단계는 기존의 부분들을 망치지 않도록 신중하고 꼼꼼하게 계산됩니다.
스펙에 추가되기 위해서는 모든 기능들이 마치 임상 실험처럼 다음의 5단계를 거쳐야 합니다. 다음 단계로 진행하기 위해서는 각각의 특정 기준을 만족해야 합니다. ECMAScript 스펙에 추가되기 위해서는 적어도 4단계는 만족해야 합니다.
자바스크립트는 계속 진화하는 언어로, 우리가 현재 사용하는 몇몇 기능들은 비교적 최근에 추가되었습니다. 2015년 이후, TC39는 매 년 새로운 기능들을 추가하기로 결정하였습니다. 다음은 2015년 이후 새롭게 추가된 기능들입니다.
Array.protytype.includes()
Exponentation operator (**, **=)
: 거듭제곱 연산자Object.values / Object.entries
Trailing commas in function parameter lists and calls
: 함수 파라미터에 후행 ,
를 붙이는 것Async
: async / awaitObject.getOwnPropertyDescriptors()
String.prototype.padStart() / String.prototype.padEnd()
Promise.prototype.finally
Rest and spread operators(...)
Asynchronous iteration
Improvements on Regular Expressions
Array.prototype.flat()
Array.prototype.flatMap()
Object.fromEntries()
String.prototype.trimStart()
String.prototype.trimEnd()
Symbol.prototype.description
Optional catch binding
JSON.stringify()
, Function.prototype.toString()
, Array.sort()
String.prototype.matchAll()
dynamic imports
BigInt
Promise.allSettled()
globalThis
Optional Chaining Operator(?.)
Nullish coalescing operator(??)
String.prototype.replaceAll()
Promise.any()
Underscore as a numeric separator
Logical assignment operators(&&=, ||=, ??=)
WeakRefs and Finalizers
자세한 내용은 이 곳에서 확인할 수 있습니다
Class Public Instance Fields & Private Instance Fields
class ColorButton extends HTMLElement {
constructor() {
this.color = "red"
this._clicked = false
}
}
const button = new ColorButton()
// Public fields can be accessed and changed by anyone
button.color = "blue"
// Curse your sudden but inevitable betrayal
console.log(button._clicked) // Prints: false, can be accessed from the instance
button._clicked = true // Doesn't throw an error, can be read from the instance
이 기능의 첫번째 부분은 클래스 내의 필드를 좀 더 명확하게 정의할 수 있게 해줍니다. 생성자 내에 정의하는 대신, 클래스의 최상단 레벨에 정의할 수 있습니다.
class ColorButton extends HTMLElement {
color = "red"
_clicked = false
}
두번째 부분은, private 필드를 좀 더 안전하게 숨길 수 있습니다. 밑줄을 붙이는 기존의 방식과 달리 필드 이름 앞에 '#'을 붙여 외부의 액세스를 방지할 수 있습니다.
class ColorButton extends HTMLElement {
// All fields are public by default
color = "red"
// Private fields start with a #, can only be changed from inside the class
#clicked = false
}
const button = new ColorButton()
// Public fields can be accessed and changed by anyone
button.color = "blue"
// SyntaxError here
console.log(button.#clicked) // Cannot be read from outside
button.#clicked = true // Cannot be assigned a value from outside
Private instance methods and accessors
class Banner extends HTMLElement {
// Private variable that cannot be reached directly from outside, but can be modified by the methods inside:
#slogan = "Hello there!"
#counter = 0
// private getters and setters (accessors):
get #slogan() {return #slogan.toUpperCase()}
set #slogan(text) {this.#slogan = text.trim()}
get #counter() {return #counter}
set #counter(value) {this.#counter = value}
constructor() {
super();
this.onmouseover = this.#mouseover.bind(this);
}
// private method:
#mouseover() {
this.#counter = this.#counter++;
this.#slogan = `Hello there! You've been here ${this.#counter} times.`
}
}
Static class fields and private static methods
ES2015 이후로, 우리는 필드를 클래스 자체에 정의함으로서 정적 필드를 정의했습니다.
class Circle {}
Circle.PI = 3.14
이제 우리는 static
키워드를 통해 정적 필드를 클래스 내부에 정의할 수 있습니다.
class Circle {
static PI = 3.14
}
클래스 필드, 메소드에서 했던 것처럼 '#'을 붙여 private 하게 만들 수 있습니다. 이는 오직 클래스 내부에서만 액세스할 수 있도록 외부에서의 액세스를 방지합니다.
class Circle {
static #PI = 3.14
static #calculateArea(radius) {
return #PI * radius * radius
}
static calculateProperties(radius) {
return {
radius: radius,
area: #calculateArea(radius)
}
}
}
// Public static method, outputs {radius: 10, area: 314}
console.log(Circle.calculateProperties(10))
// SyntaxError - Private static field
console.log(Circle.PI)
// SyntaxError - Private static method
console.log(Circle.calculateArea(5))
public 필드에 대해, 클래스의 존재하지 않는 필드에 접근을 시도하면 undefined
가 반환됩니다. 반면에, private 필드는 undefined
대신 예외를 발생시킵니다. private 필드가 존재하는지 체크하는 방법은 클래스 내에서 접근했을 때 예외를 발생시키는지 여부를 확인하는 것입니다. 하지만, 이 방법은 큰 단점을 가집니다. 존재하는 필드에 대한 잘못된 접근자로 인한 이유처럼 다른 이유로 인해 예외가 발생할 수도 있습니다.
이를 방지하기 위해 in
키워드를 사용해 private 속성/메소드를 체크할 수 있습니다.
class VeryPrivate {
constructor() {
super()
}
#variable
#method() {}
get #getter() {}
set #setter(text) {
this.#variable = text
}
static isPrivate(obj) {
return (
#variable in obj && #method in obj && #getter in obj && #setter in obj
)
}
}
정규식을 사용해 문자열의 패턴을 찾을 수 있습니다.
Regexp.exec
과 String.matchAll
모두 match된 리스트를 결과로 보여줍니다. 전자의 경우, 결과를 하나하나 반환하기 때문에, null이 반환될 때까지 여러 번 실행해야 합니다.
반면에 후자의 경우, 모든 match에 대해 순회할 수 있도록 iterator를 반환합니다. 이러한 결과에는 일치하는 문자의 전체 문자열과 괄호화된 하위 문자열, 입력 문자열 및 일치 항목의 0 기반 인덱스가 모두 포함됩니다. 다음 예시를 확인하세요
const str = 'Ingredients: cocoa powder, cocoa butter, other stuff'
const regex = /(cocoa) ([a-z]+)/g
const matches = [...str.matchAll(regex)]
// 0: "cocoa powder", 1: "cocoa", 2: "powder"
// index: 13
// input: "Ingredients: cocoa powder, cocoa butter, other stuff"
console.log(matches[0])
// 0: "cocoa butter", 1: "cocoa", 2: "butter"
// index: 27
// input: "Ingredients: cocoa powder, cocoa butter, other stuff"
console.log(matches[1])
이러한 결과는 원본 입력에서 전체 일치의 위치에 대해 꽤 많은 정보를 제공하지만 하위 문자열 일치의 인덱스에 대한 정보는 부족합니다. 새로운 /d
를 사용하면, 일치한 그룹에 대해 시작, 끝 위치를 얻을 수 있습니다.
const str = 'Ingredients: cocoa powder, cocoa butter, other stuff'
const regex = /(cocoa) ([a-z]+)/gd
const matches = [...str.matchAll(regex)]
// 0: "cocoa powder", 1: "cocoa", 2: "powder"
// index: 13
// input: "Ingredients: cocoa powder, cocoa butter, other stuff"
// indices: [[13,25],[13,18],[19,25]]
console.log(matches[0])
// 0: "cocoa butter", 1: "cocoa", 2: "butter"
// index: 27
// input: "Ingredients: cocoa powder, cocoa butter, other stuff"
// indices: [[27,39],[27,32],[33,39]]
console.log(matches[1])
이 기능 이전에는, await은 오직 async 함수 내에서만 사용할 수 있었습니다. 이는 모듈 최상단에서 await을 쓸 수 없다는 문제가 존재합니다.
이제 await
은 모듈 최상단에서 사용할 수 있으며, import, fallback 등을 만들 때 매우 유용합니다.
// Before the top-level await, JavaScript would have given you a SyntaxError with this line of code, but that is no more
await Promise.resolve(console.log("🎉"))
awaited promise가 해결되기까지, 현재 모듈과 현재 모듈을 import하는 상위 모듈의 실행은 지원하지 않았으나, 형제 모듈은 동일한 순서로 실행될 수 있습니다.
양질의 글을 찾아서 번역해 보았다. 자세하게 코드를 다 훑진 않았으나 자바스크립트 생태계를 발전해 나가는 개발자들의 현황을 파악할 수 있는 매우 좋은 글이었다. 나중에 내 코드에서도 써봐야지