[javascript] structuredClone() 가이드: 깊은 복사부터 고급 활용까지
자바스크립트에서 객체를 복사하는 방법은 여러 가지가 있지만, 그중에서도 structuredClone()은 깊은 복사를 위한 강력한 도구입니다. 얕은 복사와 깊은 복사의 차이점부터, structuredClone()의 동작 방식, 장단점, 그리고 실제 개발에서 유용하게 사용할 수 있는 고급 활용법까지 자세히 알아보겠습니다.
1. 깊은 복사 vs 얕은 복사: 왜 structuredClone()이 필요할까요?
자바스크립트에서 객체를 복사할 때 얕은 복사와 깊은 복사라는 두 가지 방법이 있습니다.
- 얕은 복사: 객체의 참조만 복사합니다. 즉, 복사된 객체와 원래 객체가 같은 메모리 주소를 가리키므로, 복사된 객체를 변경하면 원래 객체도 함께 변경됩니다. Object.assign()이나 스프레드 연산자(...)가 대표적인 얕은 복사 방법입니다.
- 깊은 복사: 객체의 값 자체를 복사합니다. 복사된 객체는 완전히 새로운 객체이므로, 복사된 객체를 변경해도 원래 객체에는 아무런 영향을 주지 않습니다. structuredClone()은 깊은 복사를 수행하는 대표적인 방법입니다.
객체 내부의 중첩된 객체까지 독립적으로 복사해야 하는 경우, 깊은 복사가 필요하며, 이때 structuredClone()이 유용하게 사용됩니다.
2. structuredClone()이란 무엇인가?
structuredClone() 메서드는 구조화된 복제 알고리즘을 사용하여 주어진 값의 깊은 복사본을 생성하는 전역 메서드입니다. 이 메서드는 새로운 객체로 복사하는 대신 원래 값에서 전송 가능한 객체를 전송할 수도 있습니다. 전송된 객체는 원래 객체로부터 분리되고 새 객체로 연결되며, 더 이상 원래 객체에 접근할 수 없게 됩니다.
구문:
structuredClone(value);
structuredClone(value, options);
- value: 복제할 객체입니다. 구조화된 복제가 가능한 모든 유형이 될 수 있습니다.
- options: 선택적 매개변수로, 다음 속성을 가진 객체입니다:
- transfer: 반환된 객체로 복제되지 않고 이동될 값에서 전송 가능한 객체의 배열입니다. (주로 ArrayBuffer와 함께 사용)
사용 예제:
const original = {
user: {
profile: {
nickname: "철수",
age: 25,
hobbies: ["독서", "게임"]
},
settings: {
theme: "dark",
notifications: true
}
},
lastLogin: new Date("2024-03-20"),
posts: [
{ id: 1, title: "첫 번째 글" },
{ id: 2, title: "두 번째 글" }
]
};
const clone = structuredClone(original);
// 깊은 복사된 객체 수정
clone.user.profile.nickname = "영희";
clone.user.profile.hobbies.push("여행");
clone.user.settings.theme = "light";
clone.posts[0].title = "수정된 첫 글";
console.log(original);
// {
// user: {
// profile: {
// nickname: "철수",
// age: 25,
// hobbies: ["독서", "게임"]
// },
// settings: {
// theme: "dark",
// notifications: true
// }
// },
// lastLogin: Wed Mar 20 2024 09:00:00 GMT+0900,
// posts: [
// { id: 1, title: "첫 번째 글" },
// { id: 2, title: "두 번째 글" }
// ]
// }
console.log(clone);
// {
// user: {
// profile: {
// nickname: "영희",
// age: 25,
// hobbies: ["독서", "게임", "여행"]
// },
// settings: {
// theme: "light",
// notifications: true
// }
// },
// lastLogin: Wed Mar 20 2024 09:00:00 GMT+0900,
// posts: [
// { id: 1, title: "수정된 첫 글" },
// { id: 2, title: "두 번째 글" }
// ]
// }
3. structuredClone()의 장점과 단점
장점:
- 깊은 복사: 객체 내부의 중첩된 객체까지 모두 새롭게 복사합니다.
- 다양한 데이터 타입 지원: Date, RegExp, Map, Set, ArrayBuffer, ImageData 등 다양한 내장 객체 타입과 사용자 정의 객체도 지원합니다.
- 순환 참조 지원: 값과 스스로를 순환 참조하는 객체도 지원합니다.
- 성능: JSON.parse(JSON.stringify(object))보다 성능이 좋습니다. (다만, 객체의 크기가 매우 클 경우 성능 차이가 미미할 수 있습니다.)
단점:
- 오래된 브라우저 지원: 2022년 3월부터 익스플로러를 제외한 모든 브라우저에서 지원됩니다. (폴리필을 사용하면 이전 브라우저에서도 사용 가능합니다.)
- 함수 속성: 함수 속성은 복사되지 않고 undefined로 설정됩니다. (보안 및 직렬화 문제 때문)
- DOM 노드: DOM 노드는 복사되지 않거나 오류를 발생시킬 수 있습니다.
- 프로토타입 체인: 객체의 프로토타입 체인을 유지하지 않습니다.
- Error 객체: Error 객체를 복사할 수 없습니다.
4. structuredClone() vs JSON.parse(JSON.stringify(object)): 무엇을 선택해야 할까요?
JSON.parse(JSON.stringify(object))는 structuredClone()이 나오기 전에 깊은 복사를 위해 일반적으로 사용되던 JSON 기반의 해결책이었습니다.
하지만 다음과 같은 이유로 structuredClone()이 더 나은 선택입니다.
기능 | structuredClone() | JSON.parse(JSON.stringify(object)) |
깊은 복사 | Yes | Yes |
데이터 타입 지원 | 광범위함 | 제한적 (Date, RegExp 등 지원 X) |
성능 | 빠름 | 느림 |
순환 참조 지원 | Yes | No (오류 발생) |
JSON.parse(JSON.stringify(object))는 JSON으로 변환할 수 없는 객체 (Date, RegExp 등)를 제대로 복사하지 못하고, 순환 참조가 있는 객체는 오류를 발생시킵니다. 또한, 성능 면에서도 structuredClone()이 더 우수합니다.
성능 비교:
일반적으로 structuredClone()이 JSON.parse(JSON.stringify())보다 빠르지만, 객체의 크기나 구조에 따라 결과가 달라질 수 있습니다.
- 작은 객체: structuredClone()이 JSON.parse(JSON.stringify())보다 느릴 수 있습니다.
- 큰 객체: structuredClone()이 JSON.parse(JSON.stringify())보다 훨씬 빠릅니다.
- 프로퍼티가 많은 단일 객체: JSON.parse(JSON.stringify())이 structuredClone()보다 빠를 수 있습니다2.
이 블로그의 성능 측정 결과에 따르면, 객체 하나를 복사했을 때는 JSON.parse(JSON.stringify())가 가장 빠른 속도를 보였고, 데이터가 많아질수록 structuredClone()의 성능이 향상되는 것을 확인할 수 있습니다.
5. structuredClone() 고급 활용법
structuredClone()은 단순한 객체 복사를 넘어, 다양한 시나리오에서 활용될 수 있습니다.
1. 웹 워커(Web Worker)와의 데이터 전송:
웹 워커는 메인 스레드와 독립적으로 실행되는 자바스크립트 스레드입니다. 메인 스레드와 웹 워커 간에 데이터를 안전하게 전송하기 위해 structuredClone()을 사용할 수 있습니다. 특히 transfer 옵션을 활용하면, 객체의 소유권을 완전히 넘겨 복사 오버헤드를 줄이고 성능을 향상시킬 수 있습니다.
// 메인 스레드
const data = { message: "안녕하세요!", buffer: new ArrayBuffer(1024) };
worker.postMessage(data, [data.buffer]); // buffer를 전송
// 웹 워커
self.onmessage = function(event) {
const data = event.data;
console.log(data.message); // "안녕하세요!"
console.log(data.buffer); // ArrayBuffer
};
transfer 옵션을 사용하면 data.buffer는 메인 스레드에서 사용할 수 없게 됩니다.
2. Undo/Redo 기능 구현:
애플리케이션에서 Undo/Redo 기능을 구현할 때, 이전 상태를 깊이 복사하여 저장해야 합니다. structuredClone()은 이러한 이전 상태를 안전하게 보관하는 데 유용하게 사용될 수 있습니다.
let state = { data: "초기 상태" };
const history = [structuredClone(state)]; // 초기 상태 저장
// 상태 변경
state.data = "변경된 상태";
history.push(structuredClone(state)); // 변경된 상태 저장
// Undo
state = history.pop(); // 이전 상태로 복원
3. 불변성 유지:
리액트(React)나 리덕스(Redux)와 같은 프레임워크/라이브러리에서 불변성 유지는 매우 중요합니다. structuredClone()을 사용하여 객체를 복사하면, 기존 객체를 변경하지 않고 새로운 객체를 생성할 수 있습니다.
const original = { name: "Jone Doe" };
const updated = structuredClone(original);
updated.name = "Jain Doe";
console.log(original.name); // "Jone Doe"
console.log(updated.name); // "Jain Doe"
React나 Vue와 같은 프레임워크에서 상태 업데이트 시 불변성을 유지하기 위해 structuredClone() 을 사용할 수 있습니다. 이전 상태를 깊이 복사하여 새로운 상태를 만들고, 이를 통해 UI를 업데이트하면 예측 가능하고 효율적인 애플리케이션을 만들 수 있습니다.
6. 대안적인 방법들과의 비교
structuredClone() 외에도 깊은 복사를 수행할 수 있는 다양한 방법들이 있습니다. 각각의 장단점을 비교하여 상황에 맞는 최적의 방법을 선택해야 합니다.
방법 | 장점 | 단점 |
structuredClone() | 내장 함수, 다양한 타입 지원, 순환 참조 지원 JSON.parse(JSON.stringify())보다 빠름 |
구형 브라우저 지원 X, 함수/DOM 노드 복사 불가, 프로토타입 체인 유지 X |
JSON.parse(JSON.stringify()) | 간단한 객체 복사에 적합 | Date/RegExp 등 특정 타입 지원 X, 순환 참조 지원 X structuredClone()보다 느림 |
Lodash의 cloneDeep() | 다양한 타입 지원, 순환 참조 지원, 프로토타입 체인 유지 |
외부 라이브러리 의존성 structuredClone()보다 느릴 수 있음 |
Immer | 불변성 관리에 특화, 복잡한 객체 구조에서 효율적인 업데이트 가능 |
외부 라이브러리 의존성, 학습 비용 |
Object.assign()/전개 연산자 | 얕은 복사 | 깊은 복사에는 부적합, 중첩된 객체는 참조만 복사됨 |
언제 어떤 방법을 사용해야 할까요?
- 간단한 객체 복사: JSON.parse(JSON.stringify()) 또는 스프레드 연산자
- 다양한 타입 지원 및 순환 참조: structuredClone() 또는 Lodash의 cloneDeep()
- 불변성 유지 및 복잡한 상태 관리: Immer
- 구형 브라우저 지원: 폴리필 사용 또는 Lodash의 cloneDeep()
❗ 브라우저 호환성 및 폴리필
structuredClone()은 최신 브라우저에서는 대부분 지원되지만, 구형 브라우저에서는 지원하지 않을 수 있습니다 Caniuse.com에서 브라우저 지원 정보를 확인할 수 있습니다.
구형 브라우저를 지원해야 하는 경우, 폴리필을 사용하여 structuredClone() 기능을 구현할 수 있습니다.
core-js 라이브러리에서 structuredClone 폴리필을 제공합니다.
if (typeof structuredClone === 'undefined') {
// 폴리필 로드 또는 직접 구현
}
Babel과 함께 @babel/preset-env를 사용하면, 대상 브라우저에 필요한 폴리필을 자동으로 적용할 수 있습니다
결론
structuredClone()은 자바스크립트에서 객체를 깊은 복사하는 데 사용할 수 있는 강력한 도구입니다. 이 메서드는 성능이 뛰어나고 다양한 데이터 타입을 지원하며 순환 참조도 지원합니다. 따라서 깊은 복사가 필요한 경우 structuredClone() 메서드를 사용하는 것이 좋습니다. 다만, 함수 속성이 복사되지 않거나 DOM 노드를 복사할 수 없는 등의 제한 사항을 인지하고, 상황에 맞게 적절히 활용해야 합니다. 웹 워커와의 데이터 전송, Undo/Redo 기능 구현, 불변성 유지와 같은 고급 활용법을 통해, 더 안전하고 효율적인 자바스크립트 코드를 작성해 보세요. 또한, 다양한 대안적인 방법들과의 비교를 통해 프로젝트의 요구사항에 맞는 최적의 방법을 선택하는 것이 중요합니다.