JS

JS, StructuredClone()

Hyeon_E 2024. 4. 26. 01:49

객체를 복사하려고 한다면 깊은 복사와 얕은 복사라는 개념을 알아야 함

JS에서 데이터는 두가지 주요 유형으로 나눌 수 있음. 바로 원시값과 참조값

 

원시값은 변수에 할당될 때 해당 변수에 직접 값이 저장됨

참조값은 객체로 새로운 변수에 원본 객체에 주소가 복사되어 두 변수가 같은 객체를 가리키게 됨

따라서 원본 객체를 변경하면 복사본도 영향을 받게 되는 것. 이것을 얕은 복사라고 함

복사본의 영향이 가지 않도록 만들기 위해 객체의 내용을 완전히 새로운 메모리에 저장하는 것을 깊은 복사라고 함

이렇게 깊은 복사로 복사된 객체는 원본 객체와는 별개의 독립적인 객체로 원본 객체의 영향을 끼치지 않음

 

객체를 복사할때 단순히 전개연산자(spread operator)를 사용하여 복사해 원본객체의 영향을 끼치지 않도록 복사할 수 있지만 만약 참조값 안에 참조값이 있다면 아주 복잡해짐 전개연산자를 사용해 복사할 경우 두 단계 이상의 depth부터는 깊은 복사가 이루어지지 않음

 

재귀함수를 직접 구현하는 방법으로 깊은 복사를 구현할 수 있겠지만 객체가 깊을수록 시간복잡도가 늘어난다는 단점이 있고 더 쉽게 할 수 있는 방법이 여러가지가 있음

 

바로 JSON.parse()와 _.cloneDeep()임

 

JSON.parse()

일종의 트릭으로, 직렬화가 가능하다면 JSON.stringify()로 객체를 JSON string으로 변환한 다음 다시 JSON.parse()로 변환하여 새로운 객체를 생성하는 방법

객체를 문자열로 변환 후, 다시 객체로 변환되었기에 이전 객체에 대한 주소 참조가 없어짐

 

const deepCopy = JSON.parse(JSON.stringify(original));

 

JSON의 문법 구조가 JS에 비하면 훨씬 단순하기 때문에, 높은 효율로 파싱이 가능하다는 점에서 최적화의 여지가 있어 종종 언급되는 트릭(특히 V8 엔진)

또한 함수는 물론 Symbol, DOM, Map, Set, Date, ArrayBuffer, RegExp(정규표현식) 등은 직렬화 과정에서 실패하거나 손실된다는 한계도 있음. 즉 깊은 복사가 불가능한 타입이 꽤 많다는 것

 

_.cloneDeep()

JSON.parse()를 사용해 깊은 복사를 하는 방법의 단점으로 인해 흔히 사용되는 lodash 라이브러리의 cloneDeep

 

const deepCopy = _.cloneDeep(original);

 

상대적으로 성능상 트레이드오프(tradeoff, 다른 측면에서 이득을 얻으면서 다른 측은 감소한다는 것)가 있긴 하지만, JSON 트릭보다 좀 더 의도에 부합하게(정확하게) 재귀적으로 깊은 복사를 수행할 수 있음 하지만 해당 라이브러리를 사용하고 있지 않다면 프로젝트의 파일 크기를 불필요하게 증가시키는 요소가 될 수 있으므로 고려해봐야 함

 

그리고 이번에 새롭게 알게된 StructuredClone()

 

StructuredClone()

네이티브로 깊은 복사를 수행할 수 있는 방법

MDN에 따르면 구조화된 복제 알고리즘을 사용하여 깊은 복사를 수행한다고 함

 

const deepCopy = structuredClone(original);

 

아쉽게도 함수, DOM 노드, 정규표현식,  Error 객체 등은 복사가 불가능하다고 함

또한 2022년 3월부터 최신 기기 및 브라우저 버전에서 작동함 따라서 이전 장치나 브러우저에서는 작동하지 않을 수 있음

하지만 mdn 호환성을 알아보니 현재의 상태에서 모든 주요 브라우저에서 지원 가능함으로 편하게 사용할 수 있음

 

😉🤔

내가 주로 사용했던 방법은 전개연산자를 사용하는 것인데 depth가 깊어지면 JSON.parse() 방법을 사용했음 _.cloneDeep()는 나의 선택지의 최종의 최종이었달까? 이유는 내가 공부하거나 프로젝트 할경우에는 JSON.parse()를 사용해서 복사가 불가능한 타입을 만난적이 없고 _.cloneDeep()는 lodash라이브러리를 사용해야 하기 때문에 프로젝트의 파일 크기를 불필요하게 증가시키는 이유이기 때문 최적화를 공부하면서 lodash라이브러리를 사용한 적이 있었는데 Bundle Analyzer로 확인해보니 lodash가 생각보다 훨씬 큰 사이즈로 자리잡고 있는것을 보았음 물론 lodash 모듈 용량을 최적화하는 방법을 사용해 크기를 줄일수 있지만 내가 코드로 구현할 수 있다면 최대한 라이브러리를 사용하지 않는것이 좋겠는데 라는 생각이 들어 기피하게 되었음

이번에 StructuredClone()를 새롭게 알게됨 복사가 불가능한 부분도 있지만 JSON.parse()보다 복사의 넓이가 넓음 성능이 궁금해서 여러 레퍼런스를 살펴보았는데 각 복사되는 영역이 다르고 어떤것을 복사하는지에 대해서도 몇개의 데이터인지 등에서 차이가 나 조금씩 이야기가 달랐는데 대체로 속도를 생각했을때 제일 빠른순으로 나열하면 JSON.parse() > StructuredClone() > _.cloneDeep() 순인듯 역시 속도에서는 JSON.parse()를 이길수 없군이라는 생각이 들면서 상황에 맞게 자신이 원하는 깊은복사 방법을 사용하면 될듯 개인적으로는 속도측면을 우선시 하기에 JSON.parse()를 1순위로 생각하고 복사의 대상이 아닌 경우에 StructuredClone()를 그리고도 안되면 _.cloneDeep()를 생각해볼듯