본문으로 바로가기
반응형

자바스크립트(Javascript)를 이용하여 코딩을 하는 경우 상당히 자주 만나게 되는 경우가 객체 복사입니다. 단순하게 A = B와 같은 방식으로 할당하는 경우, 객체 A의 변경이 B에 영향을 주게 되기 때문에 객체 복사를 위해서는 조금 다르게 접근해야 합니다.

 

 

객체 복사에는 실로 다양한 방법이 있습니다만, 최근에는 Native 하게 "StructuredClone"이라는 Global Function이 브라우저마다 서서히 도입되고 있는 추세입니다. 이번 글에서는 javascript 객체 복사에 대해서 간단히 살펴보고, "structuredClone()" 메서드에 대해서 알아보겠습니다.

 

목차

     

    1. 객체는 참조 할당

    Javascript에서는 객체변수는 메모리 어딘가에 저장되어 있는 객체를 참조할 수 있는 값이 저장됩니다. 따라서 객체를 단순하게 변수 할당 방식으로 설정하는 경우 "clone" 변수는 original과 동일한 객체를 참조하며 사실상 동일한 하나의 "객체"를 가리키게 됩니다.

     

    아래의 코드를 볼까요?

    const original = {name: '철수', age: 12};
    const clone = original;
    console.log(clone === original) //true
    
    clone.name = '영희';
    console.log(original.name) //영희

     

    위와 같이 단순히 객체 변수를 할당한 경우에는 하나의 객체 프로퍼티의 값을 변경하면 다른 객체 변수에도 영향을 주게 됩니다. 대부분 이런 경우를 원하지는 않겠죠! 따라서 진정한 객체의 복사를 위해서 여러 가지 방법을 사용하게 됩니다.

     

    2. 기존의 객체 복사 방법

    우선 기존의 객체 복사 방법에 대해서 간단하게 살펴보겠습니다. 일반적으로 얕은 복사 (Shallow Copy)와 깊은 복사 (Deep Copy)로 나누어 볼 수 있습니다.

     

    이 글은 기존의 객체 복사 방법에 대해서 파헤치는 것이 목적은 아니므로 간단하게만 살펴보겠습니다.

     

    2.1 얕은 복사 (Shallow Copy)

    1차원적인 단순 객체의 경우 비교적 간단하게 복사할 수 있습니다. 대표적으로 Object.assign과 spread operator를 이용하는 방법을 많이 사용하는 것 같습니다.

     

    □ Object.assign()

    우선, 객체의 속성을 복사할 때 자주 사용하는 방식은 Object.assign(target, source)을 이용하는 것입니다. Object.assign은 두 번째 인자(source)의 객체 프로퍼티들을 첫 번째 인자(target)의 객체에 반영합니다.

     

    객체 복사에 이용하는 경우, 일반적으로 target을 빈 객체 { }로 설정하고 복사할 원본 객체를 source에 두는 방식으로 사용합니다. 예제 코드는 아래와 같습니다.

    const original = {name: '철수', age: 12};
    const clone = Object.assign({}, original);
    clone.name = '영희'
    
    console.log(original) // {name: '철수', age: 12}
    console.log(clone); // {name: '영희', age: 12}

     

    □ ES6 Spread Operator

    Spread Operator를 이용하면 코드가 깔끔해 보이는 장점이 있어서, 요즘 얕은 객체 복사에 있어서 많이 사용하고 있는 것 같습니다. 대략적인 사용방법은 다음과 같습니다.

    const original = {name: '철수', age: 12};
    const clone = { ...original};
    clone.name = '영희'
    
    console.log(original) // {name: '철수', age: 12}
    console.log(clone); // {name: '영희', age: 12}

     

     

    그런데, 위의 두 가지 객체 복사 방법은 얕은 복사만을 지원합니다. 객체 내부에 또 다른 객체가 있거나 배열이 존재하는 경우 등에서 해당 부분은 여전히 원본의 내용을 참조하게 됩니다. 다음의 경우를 볼까요?

    const original = {profile: {name:'철수', age:12}, grade: 'A'};
    const clone = { ...original };
    
    clone.profile.name = '영희';
    clone.grade = 'B';
    
    console.log(original) // {profile: {name: '영희', age: 12}, grade: 'A'}
    console.log(clone); // {profile: {name: '영희', age: 12}, grade: 'B'}

     

    위에서 grade 프로퍼티는 정상적으로 clone 부분만 변경되었지만, profile의 name 부분은 원본이 변경된 것을 알 수 있습니다. 이는 clone의 내부 객체인 profile은 original의 profile과 동일한 주소를 참조하고 있기 때문입니다. 따라서 clone의 내부 객체인 profile의 프로퍼티를 변경하는 경우, 원본에서도 변경이 발생합니다.

     

    이러한 복잡한 객체 구조에 대한 복사를 위해서는 깊은 복사 (Deep Copy) 방법을 사용해야 합니다.

     

    2.2 깊은 복사 (Deep Copy)

    복잡한 객체 구조를 복사하기 위해서 대표적으로 사용되는 방법은 JSON 메서드를 이용하는 방법과 외부 라이브러리를 활용하는 방법이 있습니다.

     

    □ JSON 메서드

    JSON.stringify()는 자바스크립트 객체를 JSON문자열로 변환시킵니다. 반대로 JSON.parse()는 JSON문자열을 자바스크립트 객체로 변환시킵니다. 이 두 가지 방법을 이용하여 "객체 → 스트링 → 객체"의 변환 과정을 통해서 깊은 복사를 수행할 수 있습니다.

    const original = {profile: {name:'철수', age:12}, grade: 'A'};
    const clone = JSON.parse(JSON.stringify(original));
    
    clone.profile.name = '영희';
    clone.grade = 'B';
    
    console.log(original) // {profile: {name: '철수', age: 12}, grade: 'A'}
    console.log(clone); // {profile: {name: '영희', age: 12}, grade: 'B'}

     

    위 방법으로 비교적 손쉽게 깊은 복사를 수행할 수 있습니다. 최근에는 JSON에 대한 속도 최적화가 어느 정도 되어 있다고 합니다만, 복잡한 객체에 대해서는 처리 속도가 느리다고 알려져 있습니다.

     

    ※ 다음의 항목에 대해서는 정상적인 복사가 되지 않습니다.

    • Recursive data structures
    • Built-in types (Date, Map, Set, Date, RegExp, ArrayBuffer...)
    • Functions

     

    □ Lodash의 deepclone 함수

    다양한 객체 및 배열 처리를 위해 많이 사용되어온 라이브러리로 underscore와 lodash가 있습니다. ES6가 일반화되고  다양한 메서드가 추가되면서 라이브러리에 대한 필요성이 점차 줄어들고는 있습니다만, 몇 가지 부분에서는 아직 유용한 기능을 제공하고 있습니다.

     

    그중 하나로 Deep Copy를 위한 기능을 포함하고 있는데요, 이와 관련된 메서드는 다음과 같습니다.

    _.cloneDeep(value)

     

    이와 관련한 자세한 내용은 아래 lodash document를 참고하세요!

     

    Lodash Documentation

    _(value) source Creates a lodash object which wraps value to enable implicit method chain sequences. Methods that operate on and return arrays, collections, and functions can be chained together. Methods that retrieve a single value or may return a primiti

    lodash.com

     

    그런데, deep copy 하나를 위해서 외부 라이브러리를 설치하기는 좀 그렇죠? 그런데 최근 StructuredClone()이라는 기능을 통해서 native 하게 deep copy를 지원하기 시작했습니다. 이에 대해서 알아보겠습니다.

     

     

    반응형

     

    3. StructuredClone

    HTML spec에서는 "structedClone()"이라는 깊은 복사를 위한 글로벌 메서드를 제공하고 있습니다. 최근 파이어폭스, 크롬 등의 브라우저에서 이 기능을 지원하기 시작하였습니다. 이 메서드의 기본 문법은 다음과 같습니다.

     

    structuredClone(value)
    structuredClone(value, { transfer })

     

    Parameter Description
    value 복사할 원본 객체
    transfer (Option) An array of transferable objects in value that will be moved rather than cloned to the returned object.

     

    출처 : https://developer.mozilla.org/en-US/docs/Web/API/structuredClone

     

    기본적인 사용법을 볼까요? 다음과 같이 아주 쉽게 deep clone을 수행할 수 있습니다.

    const original = {
      profile: {name:'철수', age:12}, 
      grade: 'A', 
      date: new Date('2022-01-01')
    };
    
    const clone = structuredClone(original);
    
    clone.profile.name = '영희';
    clone.grade = 'B';
    
    console.log(original) 
    // {profile: {name: '철수', age: 12}, 
    // grade: 'A', 
    // date: Sat Jan 01 2022 09:00:00 GMT+0900 (한국 표준시)}}
    
    console.log(clone); 
    // {profile: {name: '영희', age: 12}, 
    // grade: 'B', 
    // date: Sat Jan 01 2022 09:00:00 GMT+0900 (한국 표준시)}}

     

    JSON 메서드에서 지원하지 않는 Date 타입도 잘 복사되는 것을 알 수 있습니다. Structured Clone이 지원하는 type은 다음과 같습니다.

     

    https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

     

    ※ 일부 기능에 있어서는 여전히 제한이 있습니다.

    • Function 객체
    • DOM node
    • 객체들의 몇몇 파라미터들 (RegExp객체들의 liastIndex필드, Property descriptors, setters, getters, 프로토타입 체인)

    출처 : https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

     

     

    물론, 일반적인 객체 복사를 할 때에는 큰 문제는 없을 것 같습니다.

     

    ※ About Tranfer Option?

    Structured Clone의 "transfer" option을 사용하면 Transferable object는 복제되기보다는 이동할 수 있습니다. 자세한 내용 및 예제는 아래 링크를 참고하시기 바랍니다.

    https://developer.mozilla.org/en-US/docs/Glossary/Transferable_objects

     

     

    최근 크롬 98 버전부터 structuredClone() 기능을 지원하기 시작했습니다.

     

    출처:https://chromestatus.com/features/schedule

     

    크롬뿐만 아니라 크로미움 기반의 엣지, 웨일 브라우저 모두 지원하기 시작했습니다. 그 외에도 많은 브라우저에서 지원하기 시작한 것으로 보이니 조만간 마음껏 사용해도 되지 않을까 기대해봅니다.

     

    출처:https://caniuse.com/?search=structuredClone

     

    맺음말

    이번 글에서는 자바스크립트의 객체 복사와 새로 추가된 깊은 복사를 위한 Structured Clone에 대해서 알아보았습니다. 간단한 객체 복사라면 기존 방식을 이용하면 되겠지만, 조금 복잡한 구조의 복사가 필요하다면 StructuredClone()을 이용해 보는 것도 좋을 것 같습니다.

     

    반응형