JavaScript ES13
ECMA 2022 살펴보기
들어가며
정보 통신에 대한 표준을 관리하는 비영리 기구 ECMA International이 2022년 6월 22일에 ES13을 최종 승인했습니다.
ECMAScript는 2015년 제 6판을 기점으로(ES6) 매년 새롭게 발표가 되고 있습니다.
이번 ES13은 ES2022으로도 불리며 자바스크립트는 ECMAScript를 준수하는 언어이기 때문에, JavaScript 사양의 변경으로도 해석할 수 있습니다.
표준이 되기 전에도 이미 대부분의 브라우저가 이를 지원하고 있었기에 여러분도 이미 해당 기능을 사용했을 수 있습니다.
tc39 공식 깃허브 방문하기
ECMA International 방문하기
새롭게 추가된 기능들
최상위 await(Top-level await) : 모듈의 최상단에서 await를 사용할 수 있습니다.
새로운 클래스 기능 : 접근 제어자 속성을 가진 필드와 메서드를 정의 할 수 있습니다.
클래스 내 정적 블록 : 클래스 별 정적 블록으로 초기화를 수행할 수 있습니다.
in 키워드의 기능 확장 : 객체에 private 인스턴스가 있는지 존재 여부를 확인할 수 있게 되었습니다.
/d/d 플래그를 통한 정규식 일치 인덱스 : 일치하는 하위 문자열에 시작 또는 종료 인덱스를 제공합니다.
Error 객체의 cause 속성 : 오류의 인과관계를 기록하는데 사용하는 Error 객체 속성을 추가할 수 있게 되었습니다.
.at() 키워드 추가 : 배열의 음수 인덱싱이 가능하게 되었습니다.
Object.hasOwn 키워드 추가 : 기존 Object.prototype.hasOwnProperty의 대안으로 추가되었습니다.
클래스 필드
클래스 필드와 관련된 개선입니다.
자바스크립트의 클래스 필드는 접근 제어자가 부실한 한계를 극복하고, 클래스 선언과 관련된 코드들의 가독성과 지역성을 높이기 위해 제안되었습니다.
ES6부터 class 키워드가 추가되었는데요.
자바스크립트 자체가 객체지향 패러다임을 위한 언어가 아니다 보니 최초 설계가 완벽하지는 않았습니다.
그래서 클래스 필드와 관련된 여러 부분의 개선이 필요했고 이들 중 일부가 표준으로 지정되었습니다.
클래스 필드에는 세 가지 세부 항목이 있습니다.
-
언어 자체에서 지원하는 Private 접근 제어자 추가
-
Public 필드 및 Static 필드 선언 방식 개선
-
Static 초기화 블록 추가
기존 방식의 클래스 선언 코드와 비교해보겠습니다.
class OldClass{
// 기존에는 private 접근 제어자 자체를 언어에서 지원하지 않았습니다.
constructor(){
this.publicField = 0;
// 변수 명 앞에 _를 붙이는 관행으로 private를 나타내었지만 실제로는 public 접근이 가능했습니다.
this._privateField = "is not private";
}
// 메서드는 클래스 바디에서 선언합니다.
publicMethod(){...}
// private 메서드로 선언한 것 같지만 실제로는 public 메서드입니다.
_privateMethod(){...}
}
// 기존의 정적 필드와 메서드는 클래스 외부에서 설정해야 했습니다.
OldClass.staticField = '정적 필드';
OldClass.staticMethod = function (){...}
class NewClass{
// 새로운 클래스 문법에서는 내부에 바로 public 필드를 선언할 수 있습니다.
publicField = 0;
publicMethod(){...}
// 새로운 문법에서는 언어 자체에서 private 접근 제어자를 지원합니다.
// 변수 또는 메서드 명 앞에 #(해시)를 붙이면 private로 접근이 가능합니다.
#privateField = 'private';
#privateMethod(){...}
// getter와 setter도 private하게 만들 수 있습니다.
get #privateGetter(){...}
set #privateSetter(value){...}
// static Field도 class 안에 작성할 수 있게 되었습니다.
static staticField = 'static field';
static staticMethod(){...}
// static 초기화 블록을 선언할 수도 있게 되었습니다.
static {
console.log('static 초기화 블록에서 실행되었습니다.');
window.getPrivateField = (newClass) => newClass.#privateField;
}
}
이제 ES13 표준으로 접근 제어자(Access Modifier)를 구현할 수 있게 되었습니다.
하지만 접근 제어자 기능은 TypeScript에서 지원하던 기능이였습니다.
또한 기존에는 Public 필드를 설정할 때는 생성자 내부에서 this 키워드를 사용해 필드를 생성해야 했고, 정적 필드를 설정할 때는 클래스 밖에서 설정해야 했습니다.
ES13에서는 모두 클래스 내부에서 바로 선언이 가능합니다.
in 연산자를 활용한 private 필드 체크하기
클래스에서 private 필드를 in 연산자로 체크하는 기능이 추가되었습니다.
기존에는 어떠한 특정 속성의 존재 여부를 검사하는 in 연산자가 있었지만 이번 ES13에서 클래스의 private 필드의 존재 여부를 체크할 수 있게 확장 되었습니다.
기존 방식은 try, catch로 존재 여부를 확인하였지만 in 연산자를 이용해 보다 가독성있는 문법으로 사용이 가능합니다.
class OldClass{
#privateField;
static checkInstance(oldClass){
try{
oldClass.#privateField;
return true;
}catch{
return false;
}
}
}
class NewClass{
#privateField;
static checkInstance(newClass){
return #privateField in newClass;
}
}
in 연산자는 존재 여부만 확인하며 명시적인 값을 반환하지 않습니다.
모듈내 최상위 레벨의 await 호출 기능 추가
최상위 레벨의 await 호출은, JavaScript 모듈 파일 내에서 async를 선언하지 않고도 await 키워드를 사용하여 비동기 호출을 가능하게 만드는 새로운 기능입니다.
기존의 모듈 내에서 비동기 함수를 최상위 레벨에서 호출하기 위해서는 async, await 함수를 만들거나 즉시 실행 함수를 만들어 호출해야 했습니다.
때문에 모듈을 호출할 때 값이 초기화되기 전에 변수에 접근이 가능하다는 문제가 있었습니다.
// module.mjs
// 우선 async 함수로 감싸지 않아도 됩니다.
// 비동기 로직 자체를 모듈 내에서 모두 처리하여 결과만 내보냅니다.
const response = await fetch('http://example.com/foo.json');
export const result = await response.json();
// another.mjs
import { result } from './module.mjs';
const messages = result;
console.log(messages)
// 위 코드는 아래와 같이 실행합니다.
// module.mjs
export let result;
export const promise = (async () => {
const response = await fetch('http://example.com/foo.json');
result = await response.json();
})();
// another.mjs
import { promise as promiseResult, result } from './module.mjs';
export const promise = (async () => {
// 모듈을 호출하는 쪽에서 비동기 코드를 `Promise.all()` 로 묶음으로서
// 이후의 로직이 실행되지 않게 합니다.
await Promise.all([promiseResult]);
const messages = result;
console.log(messages);
})();
추가적으로 최상위 레벨에서 await를 사용하거나 또 다른 비동기 모듈을 import하면 해당 모듈은 비동기 모듈이 됩니다.
또한 비동기 모듈을 호출할 때 아래와 같은 방법으로 응용이 가능하게 되었습니다.
// 동적 호출 가능
const strings = await import(`/i18n/${navigator.language}`);
// 의존성 폴백 관리
let redux;
try {
redux = await import('https://cdn-a.com/redux');
} catch {
redux = await import('https://cdn-b.com/redux');
}
음수 인덱싱 기능 추가
.at()
at() 메서드는 문자열, 배열등에서 음수 인덱싱(negative indexing)을 가능하게 해주는 메서드로 추가되었습니다.
const temp = [1, 2, 3];
// JavaScript에서 배열은 객체입니다.
typeof temp === 'object'; // true
// 기존 문법으로 마지막 인덱스의 값을 가져오는 방법
temp[temp.length - 1]; // 3
// at() 메서드를 활용한 새로운 방법
temp.at(-1); // 3
Object.hasOwn()
Object.hasOwn()는 객체의 특정 속성이 프로토타입을 거치지 않은 객체 그자신이 소유한 속성인지 여부를 반환하는 메서드입니다.
기존의 Object.hasOwnProperty() 메서드와 비슷한 역할을 하지만 이 함수가 새롭게 정의된 이유는 .hasOwnProperty() 자체가
Object의 Prototype에 종속된 메서드이기 때문에 프로토타입 속성이 없거나 재정의된 객체에서는 사용할 수 없었기 때문입니다.
// 기존에는 이런 방법을 주로 활용했습니다.
let hasOwnProperty = Object.prototype.hasOwnProperty
hasOwnProperty.call(object, "foo")
// 이렇게 굳이 메서드를 다시 꺼내고 변수에 저장해 쓴 이유는 프로토타입이 끊기면 메서드를 사용할 수 없었기 때문이였습니다.
Object.create(null).hasOwnProperty("someProp") // error
// Uncaught TypeError: Object.create(...).hasOwnProperty is not a function
새로 추가된 Object.hasOwn()는 정적 메서드로 구현되었기 때문에 특정 인스턴스의 Prototype 상속 관계에 구애받지 않고 사용 가능하다는 장점이 있습니다.
const proto = {
protoProp: '프로토타입 속성',
};
const object = {
__proto__: proto,
objProp: '객체 속성',
}
// in 연산자는 객체의 속성에 해당 값이 있는지 여부를 반환합니다.
console.log('protoProp' in object); // true
// hasOwn 메서드는 프로토타입 상속이 아닌 객체 고유의 속성인지 여부를 반환합니다.
console.log(Object.hasOwn(object, 'protoProp')); // false
console.log(Object.hasOwn(proto, 'protoProp')); // true
Error.prototype.cause
Error.prototype.cause는 에러 체이닝을 위해 추가된 속성입니다.
기존 Error 메세지보다 이번에 추가된 cause를 활용하면 발생한 오류를 한 번더 감싸서 추가적인 Context Message를 참조하게 만든 새 에러를 던지는 방식입니다.
function throwErrorMessage() {
throw new Error('Error Message', { cause: 'caused error' });
}
try {
throwErrorMessage();
} catch (e) {
console.log(e); // Error: Error Message
console.log(e.cause); // Error: caused error
}
또한 cause 속성을 사용해 원인 에러를 찾을 수 있게 되있습니다.
마치며...
JavaScript는 가장 핫한 언어로 그에 맞게 매년 새로운 기능이 추가되고 있습니다.
읽어보니 .at()이나 Error의 cause 속성은 자주 사용할 것 같아요.
감사합니다.