본문 바로가기
Script/Javascript

자바스크립트 깊은 복사, structuredClone vs JSON.parse(JSON.stringify) vs 스프레드 비교 (Node 26 실행 결과)

by 애플 로그 2026. 10. 18.
반응형

structuredClone 실행 결과 터미널 화면, Node v26.5.0

자바스크립트에서 객체를 통째로 복사하려면 Date, Map, Set이 들어 있을 때는 structuredClone, 숫자와 문자열만 있는 단순한 데이터면 JSON.parse(JSON.stringify())를 쓰면 됩니다. 스프레드 문법({ ...obj })은 바깥 한 겹만 복사하는 얕은 복사라서 안쪽 객체를 고치면 원본까지 바뀌어요.

말로만 들으면 헷갈려서 같은 객체 하나를 세 방법으로 복사해 보고, 무엇이 남고 무엇이 사라지는지 직접 돌려 봤습니다.

테스트 환경: macOS 26.6.2, Node.js v26.5.0

한눈에 비교

확인한 것 스프레드 { ...obj } JSON.parse(JSON.stringify()) structuredClone()
안쪽 객체까지 복사 안 됨(원본도 바뀜) 됨 됨
Date 그대로(같은 객체 공유) 문자열로 바뀜 Date 그대로
Map, Set 그대로(같은 객체 공유) 빈 객체 {} 그대로
값이 undefined인 키 남음 키가 사라짐 남음
함수(메서드) 남음 키가 사라짐 DataCloneError 오류
순환 참조 상관없음 TypeError 오류 복사됨
클래스 인스턴스 프로토타입 안 따라옴 프로토타입 안 따라옴 프로토타입 안 따라옴

아래부터는 이 표를 만든 실제 코드와 출력이에요.

실험에 쓴 객체

이름, 가입일(Date), 권한(Map), 아이디 목록(Set), 값이 없는 별명, 메서드, 주소(안쪽 객체)를 한 객체에 다 넣었습니다.

const original = {
  name: '홍길동',
  joined: new Date('2026-10-18T00:00:00Z'),
  tags: new Map([['role', 'admin']]),
  ids: new Set([1, 2]),
  nickname: undefined,
  greet() { return 'hi'; },
  address: { city: '서울' },
};

const spread = { ...original };
const json = JSON.parse(JSON.stringify(original));
let sc;
try { sc = structuredClone(original); } catch (e) { console.log('structuredClone 오류:', e.name, '-', e.message); }

spread.address.city = '부산';
console.log('스프레드 후 원본 city:', original.address.city);
original.address.city = '서울';

console.log('JSON joined 타입:', typeof json.joined, json.joined);
console.log('JSON tags:', JSON.stringify(json.tags), '/ ids:', JSON.stringify(json.ids));
console.log('JSON nickname 키 있음?', 'nickname' in json, '/ greet 키 있음?', 'greet' in json);

실행 결과는 이렇게 나왔어요.

$ node a.mjs
structuredClone 오류: DataCloneError - greet() { return 'hi'; } could not be cloned.
스프레드 후 원본 city: 부산
JSON joined 타입: string 2026-10-18T00:00:00.000Z
JSON tags: {} / ids: {}
JSON nickname 키 있음? false / greet 키 있음? false

스프레드로 복사한 객체의 주소를 부산으로 바꿨더니 원본도 부산이 됐어요. 안쪽 address 객체는 복사된 게 아니라 같은 객체를 같이 가리키고 있었던 거예요.

JSON 방식은 안쪽까지 새로 만들어 주지만 Date는 문자열이 되고, Map과 Set은 빈 객체가 됐습니다. 값이 undefined인 키와 메서드는 아예 없어졌어요.

structuredClone은 메서드가 하나라도 들어 있으면 복사 자체를 거부하고 DataCloneError를 던졌습니다. MDN 문서에도 함수는 structured clone으로 복사할 수 없다고 나와 있어요.

메서드를 빼고 structuredClone을 다시 돌려 보니

greet 메서드만 빼고 같은 객체로 다시 해 봤습니다. 순환 참조와 클래스 인스턴스도 같이 넣었어요.

$ node b.mjs
structuredClone 후 원본 city: 서울
joined 는 Date? true 2026-10-18T00:00:00.000Z
tags: admin / ids 크기: 2
nickname 키 있음? true
순환 참조 복사 성공: true / 원본과 같은 객체? false
JSON 순환 참조: TypeError - Converting circular structure to JSON
클래스 인스턴스 유지? false / hello 메서드: undefined

이번에는 복사본을 고쳐도 원본은 서울 그대로였고, Date, Map, Set, undefined 키가 전부 살아 있었어요. 자기 자신을 가리키는 순환 참조 객체도 문제없이 복사됐는데, 같은 객체를 JSON.stringify에 넣으면 TypeError가 납니다.

마지막 줄을 보면 클래스로 만든 객체는 복사본이 더 이상 User가 아니고 hello 메서드도 없어요. MDN 설명대로 프로토타입 체인은 복사하지 않기 때문이에요. 클래스 인스턴스를 복사해야 한다면 클래스에 직접 clone 메서드를 만드는 편이 낫습니다.

속도는 JSON 방식이 더 빨랐어요

항목 100개짜리 배열이 들어 있는 단순한 객체를 2만 번씩 복사해 봤습니다. 두 번 돌렸는데 결과가 거의 같았어요.

$ node c.mjs
JSON 방식    : 239 ms
structuredClone: 466 ms
$ node c.mjs
JSON 방식    : 235 ms
structuredClone: 466 ms

이 PC 기준으로 숫자와 문자열만 있는 데이터는 JSON 방식이 두 배쯤 빨랐어요. 컴퓨터와 데이터 모양에 따라 달라질 수 있으니 차이가 중요하면 자기 데이터로 한 번 재 보는 게 좋습니다.

그래서 언제 무엇을 쓰나

서버에서 받은 JSON 응답처럼 숫자, 문자열, 배열만 있는 데이터는 JSON.parse(JSON.stringify())로 충분하고 빠릅니다.

Date, Map, Set이 섞여 있거나 순환 참조가 생길 수 있는 데이터는 structuredClone을 쓰세요. 대신 메서드가 들어 있으면 오류가 나니 데이터만 담긴 객체에 써야 해요.

객체 한 겹만 바꿔서 새 객체를 만들 때, 예를 들어 리액트 상태에서 이름 하나만 바꿀 때는 스프레드가 가장 간단합니다. 안쪽 객체까지 바꿀 거라면 그 안쪽도 같이 펼쳐 줘야 원본이 안 바뀌어요.

관련 글: SyntaxError: Cannot use import statement outside a module 해결, Node 26에서 4가지 경우를 직접 돌려 봤어요

관련 글: javascript 문자열이 유효한지 체크하는 함수 ( validation )

이 글의 코드와 명령어는 직접 실행해 확인했고, 정리에는 AI 도구를 활용했습니다.

출처: 직접 실행(Node.js v26.5.0, 2026년 10월 18일), MDN "The structured clone algorithm" (2026년 10월 18일 확인)

댓글