--- id: js-structured-clone title: JS Structured Clone & Deep Equality 함정 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [javascript, deep-clone, structured-clone, vibe-coding] tech_stack: { language: "JavaScript / TypeScript", applicable_to: ["Web", "Node"] } applied_in: [] aliases: [deep clone, JSON.parse hack, structuredClone, lodash isEqual] --- # Structured Clone & Deep Equality > `JSON.parse(JSON.stringify(x))` 가 deep clone 의 default 였던 시절은 끝. **`structuredClone()`** 가 native (Node 17+/모던 브라우저). 단 함수, DOM, class instance 일부는 여전히 못 함. ## 📖 핵심 개념 - structuredClone: HTML structured clone algorithm. 깊은 복사. circular ref OK. Map/Set/Date/RegExp/TypedArray 지원. 함수/Symbol/DOM 못 함. - JSON 방식: 함수/undefined/Date 손실, circular 폭사. 절대 production default X. - Lodash `_.cloneDeep`: 거의 모든 형식 지원. 외부 의존성. ## 💻 코드 패턴 ### 기본 deep clone ```ts const original = { a: 1, b: { c: [1, 2, 3] }, d: new Date() }; const clone = structuredClone(original); clone.b.c.push(4); // original.b.c === [1,2,3] 그대로 ``` ### 못 하는 것 ```ts const obj = { fn: () => 1, // ❌ throws DataCloneError el: document.body, // ❌ DOM cls: new MyClass(), // ❌ class instance (plain prototype 만) sym: Symbol('s'), // ❌ }; structuredClone(obj); // 폭사 ``` ### Class instance 클로닝 ```ts class User { constructor(public name: string, public meta: object) {} } // structuredClone 은 prototype 잃음 const u = new User('a', { x: 1 }); const c = structuredClone(u); // c instanceof User === false // 해결: clone 메서드 직접 class User { clone(): User { return new User(this.name, structuredClone(this.meta)); } } ``` ### Deep equality ```ts import { dequal } from 'dequal'; // 또는 lodash isEqual dequal({ a: 1, b: [2] }, { a: 1, b: [2] }); // true // Reference equality (===)는 빠르지만 객체 내부 같은지 모름 // JSON.stringify 비교는 키 순서 / NaN / Date 정확 X ``` ### Circular safe stringify ```ts import { stringify } from 'safe-stable-stringify'; // circular 있으면 [Circular] 마커 ``` ## 🤔 의사결정 기준 | 데이터 | 도구 | |---|---| | Plain object / array / Date / Map / Set | `structuredClone` | | 함수 / DOM / class instance | 직접 clone 메서드 + structuredClone 재귀 | | 비교 (deep eq) | `dequal` 또는 `lodash isEqual` | | 빠른 immutability + structural sharing | Immer / immutable.js | | 직렬화 (network, storage) | JSON or msgpack — 의도적 손실 OK | ## ❌ 안티패턴 - **JSON.parse(JSON.stringify(x))** 를 deep clone default: Date → string, undefined 손실, circular 폭사. 알면서 쓰는 경우만. - **`Object.assign({}, x)` 으로 deep clone 기대**: shallow. - **`{...x}` 로 deep clone 기대**: shallow. - **가변 객체에 structuredClone 매번**: 성능. 진짜 필요할 때만 clone. - **lodash isEqual 매 렌더**: 큰 객체 비교 비용. memo + selector. - **NaN === NaN false 예상 못함**: deep eq 라이브러리 사용. - **TypedArray byteOffset 바뀜**: structuredClone OK 지만 이후 backing buffer 다름. ## 🤖 LLM 활용 힌트 - "deep clone = structuredClone, deep eq = dequal" 디폴트. - 함수 / class instance 가 있으면 명시적 clone 메서드. ## 🔗 관련 문서 - [[Defensive_Copying]] - [[Pure_Functions_in_Practice]]