StructuredClone에 대해 꼭 알아두세요! JavaScript에서 깊은 복사가 가능해져요.

seonja kim·2021년 12월 20일
13
post-thumbnail
부제: 자바스크립트는 이제 깊은 복사를 위한 내장 함수인 StructuredClone()이 제공됩니다.

이 글은 Surma의 Deep-copying in JavaScript using structuredClone을 번역한 글이며 원문은 링크를 통해서 확인하실 수 있습니다. 오역은 알려주세요. 바로 고치도록 하겠습니다:)

우린 오랜 시간 동안 JavaScript 값의 깊은 복사본을 만들기 위해 여러 차선책들과 라이브러리에 의존해야 했습니다. 자바스크립트는 이제 structuredClone()이라는 깊은 복사를 위한 내장 기능이 제공됩니다.

이 글을 쓰는 시점에서 모든 브라우저 이 API를 구현했으며 Firefox는 Firefox 94에서 안정적인 버전으로 출시했습니다. 또한 Node 17 및 Deno 1.14도 이 API를 구현했습니다. 당신은 지금 바로 이 기능을 기분좋게 사용할 수 있습니다.

얕은 복사

JavaScript에서 값을 복사하는 것은 거의 항상 얕은 복사입니다. 즉, 깊이 중첩된 값에 대한 변경 사항은 복사본뿐만 아니라 원본에도 영향을 주게 됩니다.

JavaScript에서 전개 연산자 ...를 이용해 얕은 복사를 할 수 있습니다.

const myOriginal = {
  someProp: "with a string value",
  anotherProp: {
    withAnotherProp: 1,
    andAnotherProp: true
  }
};

const myShallowCopy = {...myOriginal};

단순 복사본에 속성을 직접 추가하거나 변경하면 원본이 아닌 복사본에만 영향을 줍니다.

myShallowCopy.aNewProp = "a new value";
console.log(myOriginal.aNewProp)
// ^ logs `undefined`

그러나 깊게 중첩된 속성을 추가하거나 변경 하면 복사본과 원본 모두에 영향을 줍니다.

myShallowCopy.anotherProp.aNewProp = "a new value";
console.log(myOriginal.anotherProp.aNewProp) 
// ^ logs `a new value`

표현식 {...myOriginal}은 전개 연산자를 사용하여 myOriginal의 (열거 가능한) 속성을 반복합니다. 속성 이름과 값을 사용하여 새로 생성된 빈 객체에 하나씩 할당합니다. 따라서 복사 결과 객체는 모양이 동일하지만 속성 및 값 목록을 독립적으로 가지게 됩니다. 값도 복사되지만 소위 기본형 데이터 타입은 참조형 데이터 타입과 다르게 처리됩니다. MDN 을 인용하면 :

자바스크립트에서 기본형(primitive value, primitive data type)은 객체가 아니고 메소드가 없는 데이터이다. string, number, bigint, boolean, undefined, symbol 및 null의 7가지 기본 데이터 유형이 있습니다.
MDN — Primitive

기본형이 아닌 값은 참조로 처리됩니다. 즉, 값을 복사하는 작업은 실제로 동일한 기본 객체에 대한 참조를 복사하는 것이므로 얕은 복사 동작이 발생합니다.

깊은 복사

얕은 복사의 반대는 깊은 복사입니다. 깊은 복사 알고리즘도 객체의 속성을 하나씩 복사하지만, 다른 객체에 대한 참조를 찾으면 자신을 재귀적으로 호출하여 해당 객체의 복사본도 생성합니다. 이때 복사된 객체가 원본과 내부의 중첩된 객체를 공유하거나 서로의 상태를 조작하지 않도록 깊은 복사를 구현하는 것이 매우 중요합니다.

이전에는 JavaScript에서 값의 깊은 복사본을 만드는 쉽고 좋은 방법이 없었습니다. 많은 사람들이 Lodash의 cloneDeep() 기능과 같은 라이브러리에 의존했습니다. 틀림없이 이 문제에 대한 가장 일반적인 솔루션은 JSON 기반 해킹이었습니다.

const myDeepCopy = JSON.parse(JSON.stringify(myOriginal));

사실 이것은 인기있는 해결법이라 V8는 공격적으로 JSON.parse()을 최적화하였고 특히나 위의 예시와 같은 패턴을 빠르게 동작하도록 구현하였습니다. 이 패턴은 속도는 빠르지만 몇 가지 단점이 있습니다.

  • 재귀 데이터 구조 : 재귀적인 데이터 구조로 JSON.stringify() 를 사용하면 예외 에러(throw)가 발생합니다. 이것은 연결 리스트나 트리 데이터구조를 작업할 때 자주 발생합니다.
  • 내장 함수 유형 : JSON.stringify()은 자바스크립트 내장 함수인 Map, Set, Date, RegExp, ArrayBuffer를 포함한 값이 있을 경우 예외 에러(throw)가 발생합니다.
  • 함수 : JSON.stringify()는 빠르게 함수를 폐기해 버립니다.

구조화된 복제

IndexedDB에 JS 값을 저장하려면 디스크에 저장하고 나중에 JS 값을 복원하기 위해 역직렬화할 수 있도록 일종의 직렬화가 필요한 상황과 같이 이미 여러곳에서 Javascript값의 깊은 복사본을 생성하는 기능은 요구되어 왔습니다. 마찬가지로 postMessage()를 통해 WebWorker에 메시지를 보내려면 JS값을 한 JS 영역에서 다른 영역으로 전송해야 합니다. 이를 위해 사용되는 알고리즘은 "Structured Clone"이라고 하며 최근까지 개발자가 쉽게 접근하기엔 어려웠습니다.

지금은 바뀌었습니다! HTML 사양은 structuredClone()함수를 활용할 수 있도록 수정되었습니다. 이 함수는 "Structured Clone"알고리즘을 이용해 깊은 복사본을 쉽게 만들 수 있습니다.

const myDeepCopy = structuredClone(myOriginal);

짜잔! 이것이 전체 API입니다. 세부 사항에 대해 더 자세히 알고 싶다면 MDN 기사 를 살펴보십시오.

기능 및 제한 사항

Structured cloning은 JSON.stringify()의 많은 (전부는 아니지만) 단점을 해결합니다. 구조적 복제는 순환되는 데이터 구조를 처리할 수 있고, 내장 데이터 타입을 지원할 수 있으며 일반적으로 더 강력하고 더 빠릅니다.

그러나 여전히 몇 가지 제한 사항이 있습니다.

  • 프로토타입 : structuredClone()를 클래스 인스턴스와 함께 사용 하는 경우, strucuted cloning이 객체의 프로토타입 체인을 폐기하므로 반환 값은 일반 객체가 됩니다.
  • 함수 : 객체에 함수가 포함되어 있으면 폐기됩니다.
  • 복제 불가 : 일부 값, 특히 Error와 DOM 노드는 구조화된 복제가 불가능합니다. 그것은 structuredClone() 예외 에러(throw)의 원인이 됩니다.

이러한 제한 사항이 당신이 사용하려는 상황에 해당한다면 Lodash와 같은 라이브러리는 여전히 다른 깊은 복사 알고리즘을 custom하여 적용할 수 있도록 제공하고 있습니다.

성능

나는 새로운 마이크로 벤치 마크 비교를 하지 않았던, 2018년 초 structuredClone()이 나오기 전에 깊은 복사 방식을 비교분석한 적이 있습니다. 당시에는 JSON.parse()가 작은 객체를 복사하기에 가장 빠른 옵션이었습니다. 그 부분은 아마 동일하게 유지될 것 입니다. Structured cloning에 의존하는 기술은 더 큰 객체를 복사할 때, (상당히) 더 빠릅니다. 새로운 structuredClone() API가 다른 API를 남용하는 오버헤드 없이 제공된다면 JSON.parse()보다 강력합니다. 깊은 복사본을 생성하기 위한 기본 접근 방식으로 정해놓아도 손색없습니다.

결론

JS에서 값의 깊은 복사본을 생성해야 하는 경우(불변 데이터 구조를 사용하거나 함수가 원본에 영향을 주지 않고 객체를 조작할 수 있도록 하려는 경우일 수 있음) 더 이상 차선책을 찾기 위해 노력하거나 라이브러리를 활용하지 않아도 됩니다. JS 생태계는 이제 structuredClone()가 있습니다. 야호!

profile
Adventurer

0개의 댓글