목록 보기
자바스크립트의 새로운 기능들
기타

자바스크립트의 새로운 기능들

프론트엔드 개발자에게 자바스크립트는 떼려야 뗄 수 없는 언어입니다. 자바스크립트는 ECMAScript의 스펙을 준수하는 언어인데요. 2015년 이후로 매년 새로운 스펙이 발표된다는 사실을 알고 계셨나요? 지난 6월에도 어김없이 새로운 사양인 ES2022이 발표되었는데요. 이번 글을 통해서 어떠한 기능들이 ES2022에 새롭게 포함되었는지, 2023년에는 어떤 기능이 추가될지 미리 알아보는 시간을 가져보도록 하겠습니다. 1. Top-level await 비동기 처리를 위해 사용되는 async/await는 한 세트이기 때문에, await 혼자서는 동작이 불가능하다는 것을 다들 알고 계실 텐데요. ES2022부터는 모듈의 최상위 레벨에서 await를 사용할 수 있게 되었습니다. 간단한 예제를 통해 Top-level await에 대해 알아보도록 하겠습니다. // todoList.mjs let todoList

(async () => ( const response = await fetch("https://jsonplaceholder.typicode.com/todos/1") todoList = await response.json() ))()

export ( todoList )

// index.mjs import ( todoList ) from "./todoList.mjs"

console.log(todoList) // undefined todoList.mjs(이하 A모듈)는 IIAFE를 활용해 todoList를 조회한 후 결과를 내보내는 모듈이며, index.mjs(이하 B모듈)은 A모듈의 결과를 import하여 출력하는 모듈입니다. 하지만 B모듈에서 A모듈의 비동기 처리가 완료되지 않은 시점에 todoList로 접근이 가능하기 때문에 undefined를 출력하는 것을 확인할 수 있는데요. 이를 해결하기 위한 첫 번째 방법으로 A모듈의 비동기 처리가 완료될 때까지 일정 시간을 기다린 후 todoList로 접근하는 방법이 있습니다. setTimeout(() => ( console.log(todoList) // (userId: 1, id: 1, title: 'delectus aut autem', completed: false) ), 100) 결과는 잘 출력되고 있지만 아시다시피 이 방법은 A모듈의 비동기 처리가 완료됐다는 것을 보장해 주지 않습니다. 비동기 처리가 예상했던 100ms보다 늦게 끝날 수도 있기 때문이죠. 이를 해결하기 위한 다른 방법으로는 A모듈에서 Promise객체를 반환하는 방법이 있습니다. // todoList.mjs let todoList

export default (async () => ( const response = await fetch("https://jsonplaceholder.typicode.com/todos/1") todoList = await response.json()))() export ( todoList )

// index.mjs import promise, ( todoList ) from "./todoList.mjs"

promise.then(() => ( console.log(todoList) // (userId: 1, id: 1, title: 'delectus aut autem', completed: false))) 이제 B모듈에서는 Promise객체의 then 메서드를 사용하여 비동기 처리 직후 todoList에 접근 가능합니다. 하지만 이 코드는 여전히 복잡할 뿐만 아니라, promise를 사용하지 않아도 todoList로 접근이 가능하기 때문에 위험요소를 포함하고 있습니다. 마지막으로 Top-level await를 활용하여 해당 문제들을 해결해 보도록 하겠습니다. // todoList.mjs let todoList

const response = await fetch("https://jsonplaceholder.typicode.com/todos/1")todoList = await response.json() export ( todoList )

// index.mjs import ( todoList ) from "./todoList.mjs"

console.log(todoList) // (userId: 1, id: 1, title: 'delectus aut autem', completed: false) Top-level await를 사용한 A 모듈을 import 하는 B 모듈은, A 모듈의 await가 모두 실행되기 전(비동기 처리가 완료되기 전까지) 동작을 중지하게 됩니다. 마치 Top-level await를 사용한 모듈이 하나의 거대한 async 함수처럼 동작하게 됩니다. 따라서 B 모듈에서는 todoList로 바로 접근을 해도, 비동기 처리가 완료됐다는 것을 보장하기 때문에 원하던 결과를 얻을 수 있게 됩니다. +) Top-level await가 어떻게 사용될 수 있을지 궁금하신 분들은 링크를 참고해 주세요. 2. Array.prototype.at() 다음으로 소개해 드릴 기능은 배열에 Negative Indexing을 가능하게 해주는 at()입니다. 아마 파이썬을 사용해 보신 분들은 Negative Indexing을 통해 배열의 뒤에서부터 원소를 가져오는 방법이 익숙하실 텐데요. 지금까지 자바스크립트에서 해당 기능을 구현하기 위해서는 다음과 같은 방법을 사용해 왔습니다. const KakaoEnt = ["Melon", "KakaoPage", "KakaoWebtoon"]

console.log(KakaoEnt[KakaoEnt.length - 1]) // KakaoWebtoon console.log(KakaoEnt.slice(-1)[0]) // KakaoWebtoon console.log(KakaoEnt.reverse()[0]) // KakaoWebtoon ES2022부터는 아래와 같은 방법으로 간결하게 접근이 가능하며, 주어진 인덱스가 배열에 없다면 undefined를 반환합니다. console.log(KakaoEnt.at(-1)) // KakaoWebtoon console.log(KakaoEnt.at(-4)) // undefined 3. Object.hasOwn() Object.hasOwn() 은 Object.prototype.hasOwnProperty() 의 문제점을 해결하고 보다 간결하게 사용하기 위해 고안된 기능입니다. ES2022 이전에는 객체의 고유 프로퍼티를 확인하기 위해 hasOwnProperty을 사용해 왔습니다. const person = ( firstName: "east", )

person.hasOwnProperty("firstName") // return true person.hasOwnProperty("hasOwnProperty") // return false 하지만 hasOwnProperty에는 아래의 경우에 문제가 발생할 수 있습니다. Overriding에 의한 property shadowing이 발생할 경우 const person = ( firstName: "east", hasOwnProperty() ( return false ),)

person.hasOwnProperty("fistName") // return false Object.prototype을 상속받지 못할 경우 const person = Object.create(null)person.firstName = "east"

person.hasOwnProperty("fistName") // Uncaught TypeError: person.hasOwnProperty is not a function 이러한 문제를 해결하기 위해 아래의 방법을 사용했는데요. const hasOwnProperty = Object.prototype.hasOwnProperty

hasOwnProperty.call(person, "firstName") // return true ES2022 이후로는 더욱 간단하고 안전하게 객체의 고유 프로퍼티를 확인할 수 있게 되었습니다. Object.hasOwn(person, "firstName") // return true 4. Error Cause Error Cause는 에러의 원인을 파악하기 쉽도록 추가된 기능이며 사용방법은 다음과 같습니다. const newError = new Error(ErrorMessage, ( cause: ErrorCause /any type/ ))

console.log(newError.cause) // ErrorCause 간단한 예제를 통해 어떻게 사용할 수 있을지 알아보도록 하겠습니다. async function uploadAndDownloadImage(imgData) ( try ( const uploadImage = await fetch(//upload/image, ( method: "POST", body: imgData )) .catch(err => ( throw new Error("Fail to upload image") ))

const downloadImage = await fetch(`//download/image/$(uploadImage.id)`)
  .catch(err => (
    throw new Error("Fail to download image")
  ))

) catch (err) ( throw new Error("Fail to upload image or download image") ) )

try ( await uploadAndDownloadImage() ) catch (err) ( console.log(err) // Error: Fail to upload image or download image ) 위 코드는 이미지 업로드 후 해당 이미지를 다시 다운로드하는 코드입니다. (예제는 예제일 뿐!) uploadAndDownloadImage함수 내부에서 어떤 에러가 발생하는지와는 상관없이 Fail to upload image or download image라는 에러를 발생시킵니다. 즉, 어떤 문제에 의해 에러가 발생했는지 파악하기가 어려운데요. 이럴 경우 아래와 같이 코드를 수정하면 에러의 원인을 파악하여 에러 핸들링을 쉽게 할 수 있도록 도와줍니다. async function uploadAndDownloadImage(imgData) ( try ( const uploadImage = await fetch(//upload/image, ( method: "POST", body: imgData )) .catch(err => ( throw new Error("Fail to upload image") ))

const downloadImage = await fetch(`//download/image/$(uploadImage.id)`)
  .catch(err => (
    throw new Error("Fail to download image")
  ))

) catch (err) ( throw new Error("Fail to upload image or download image", (cause:err)) ) )

try ( await uploadAndDownloadImage() ) catch (err) ( console.log(err) console.log(Caused by $(err.cause)) // Caused by Error: Fail to upload image) 5. Regexp Match Indices 정규 표현식의 d flag를 사용하면, 패턴과 일치하는 문자의 시작과 끝 위치 정보를 얻을 수 있습니다. 기존에 특정 패턴과 일치하는 문자의 정보를 얻기 위해서 다음 예제와 같이RegExp.prototype.exect()메서드를 사용하였습니다. const target = "xaaaz" const regExp = /a+(?&ltZ>z)?/g

const m1 = regExp.exec(target)

console.log(m1) // ['aaaz', 'z', index: 1, input: 'xaaaz', groups: (Z: "z")] console.log(regExp.lastIndex) // 5 하지만 exec메서드는 다음과 같은 단점이 존재합니다. 일치하는 문자(aaaz)의 시작 인덱스 정보만을 제공합니다. 그룹 캡쳐(Z그룹)의 인덱스 정보를 제공하지 않습니다. 이러한 단점을 d flag를 사용하여 해결할 수 있으며, 일치하는 문자열에 대한 인덱스 정보는 .indices속성안에 포함하고 있습니다. const target = "xaaaz" const regExp = /a+(?&ltZ>z)?/dg // d flag 추가 const m1 = regExp.exec(target) console.log(m1) /* ['aaaz', 'z', index: 1, input: 'xaaaz', groups: (Z: "z"), indices: [[1,5],[4,5], groups: (Z: [4, 5])]]*/

const matchedStr = target.slice(...m1.indices[0])const matchedZGroupStr = target.slice(...m1.indices.groups["Z"]) console.log(matchedStr) // aaaz console.log(matchedZGroupStr) // z 6. Class Fields + Private fields check ES2022 스펙에서 가장 많은 변화가 있는 것이 클래스 필드인데요. 예제를 통해 어떤 기능이 추가되었는지 알아보도록 하겠습니다. Class Public Instance Fields class Person ( canFly = false #age

constructor(name, age, phoneNumber) ( this.name = name this.#age = age this.#phoneNumber = phoneNumber // Uncaught SyntaxError: Unexpected identifier

console.log(this.#age) // 28

) )

const me = new Person("east.lee", 28, "010-1234-5678")

console.log(me.canFly) // false console.log(me.name) // east.lee console.log(me.#age) // Uncaught SyntaxError: Private field '#age' must be declared in an enclosing class 인스턴스 프로퍼티를 constructor 외부, 즉 클래스 몸체에서도 선언할 수 있습니다. 만약 외부로부터 값을 주입받고 싶다면 기존 방식대로 constructor 내부에서 선언합니다. 기존에는 속성명 앞에 _를 붙여 private 속성임을 암묵적으로 표시해 주었지만, 앞으로는 #을 사용해 private 속성을 정의할 수 있습니다. 이때 private 속성의 값을 외부로부터 주입받고 싶다면, 클래스 몸체에서 우선 정의를 해주어야 합니다. Private instance methods and accessors class Person ( #ageValue

get #age() ( return this.#ageValue ) set #age(age) ( this.#ageValue = age )

constructor(age) ( this.#age = age ) ) 인스턴스 프로퍼티뿐만 아니라 메서드와 접근자 프로퍼티(getter/setter)도 private 속성으로 정의할 수 있습니다. Static class fields and private static methods class Circle ( static #PI = 3.14 static vertex = 0

static getPI() ( return this.#PI )

static #getVertex() ( return this.vertex ) )

console.log(Circle.getPI()) // 3.14 console.log(Circle.vertex) // 0 console.log(Circle.#PI) // SyntaxError console.log(Circle.#getVertex()) // SyntaxError 기존에는 클래스의 static 메서드는 클래스 몸체 밖에서 정의하였습니다. 앞으로는 static 키워드를 붙이는 것으로 간단하게 static 메서드를 정의할 수 있습니다. 물론 #을 붙이는 것으로 외부에서의 접근을 제어할 수 있습니다. Ergonomic brand checks for Private Fields class C ( #privateField1 #privateField2

static isC(obj) ( return #privateField1 in obj && #privateField2 in obj ) )

const c = new C()

C.isC(c) // true private 필드가 존재하는지 체크하기 위해 클래스 내부에서 in키워드를 사용합니다. 7. Array find from last 이번에 소개해 드릴 기능은 아직 ES2022 스펙에 추가된 기능은 아니고, 내년에 정식으로 추가될 가능성이 높은 TC39/stage_4_proposal에 추가된 내용입니다. 배열의 원소 중 특정 조건을 만족하는 원소를 찾기 위해 Array.prototype.find와 Array.prototype.findIndex를 사용합니다. 하지만 해당 메서드를 이용해 조건을 만족하는 마지막 원소를 찾기 위해서는 다음과 같이 불편한 방법을 사용하여야 했습니다. const arr = [1, 2, 3, 4]

arr.find(n => n % 2 === 1) // 1 arr.findIndex(n => n % 2 === 1) // 0

[...arr].reverse().find(n => n % 2 === 1) //3 arr.length - 1 - [...arr].reverse().findIndex(n => n % 2 === 1) //2 하지만 이번 제안을 통해 간단하게 원하는 동작을 구현할 수 있게 되었습니다. arr.findLast(n => n % 2 === 1) //3 arr.findLastIndex(n => n % 2 === 1) // 2 마무리 2022년 6월에 발표된 ES2022의 새로운 기능들과, 2023년에 정식 스펙으로 추가될 확률이 높은 TC39의 stage 4의 기능(22.7.28 기준)을 알아보았습니다. 아마 대부분의 기능들을 이미 사용하고 계신 개발자분들도 있으실 텐데요. 그런 개발자분들에게는 한번 정리하는 글이, 처음 접하는 분들에게는 새로운 기능을 학습할 수 있는 시간이 되었으면 좋겠습니다. 글의 내용 이외에도 앞으로 사용할 수도 있는 TC39의 다른 제안들도 살펴보는 것이 좋지 않을까 생각하여 링크 남기도록 하겠습니다! 긴 글 읽어주셔서 감사합니다 :) +) 본문에서 사용된 예제 코드는 최신 버전의 크롬에서 동작하며, 일부 브라우저에서는 동작하지 않을 수 있습니다. Reference TC39 finished-proposals Top-level await Array.prototype.at() RegExp.prototype.exec()

댓글 0

댓글을 작성하려면 로그인이 필요합니다.

댓글을 불러오는 중...