자바스크립트 얕은 복사와 깊은 복사 완벽 이해하기

    반응형

    얕은 복사 깊은 복사

     

    자바스크립트를 배우면서 중요한 개념이라고 할 수 있는 얕은 복사와 깊은 복사에 대해 이해하고 활용할 수 있도록 공부하려고 한다. 특히나 리액트 같은 경우는 원본 데이터를 변경하지 않고 데이터를 복사해서 새롭게 사용하기 때문에 이 개념에 대해 정확하게 알고 있어야 한다고 생각한다.

     

    얕은 복사와 깊은 복사


     

    복사는 그냥  해서 할당하면 되는 거 아닌가?라고 생각했었던 사람으로 얕은 복사와 깊은 복사는 많은 차이를 가지고 있다. 얕은 복사는 최상위 에서만 복제가 되는 것을 말하고 깊은 복사는 객체의 끝까지 모두 복사가 되는 것을 말한다. 예시 코드를 보면서 더 자세하게 살펴보자

     

    const bookshelf1 = {
        책1: "해리포터",
        책컬렉션: {
            소설1: "반지의제왕",
            소설2: "나의라임오렌지나무"
        }
    };
    
    const bookshelf2 = {...bookshelf1}; // 얕은 복사
    
    
    console.log(bookshelf2.책1 === bookshelf1.책1) // true
    bookshelf2.책1 = '오징어 게임'
    console.log(bookshelf1.책1); // "해리포터" 그대로 유지
    console.log(bookshelf2.책1) // "오징어 게임" 출력

     

    얕은 복사는 최상위 에서만 복제가 된다고 했다. bookshelf1라는 하나의 책꽂이가 있다고 가정해 보자. 최상에서만 복제가 되니 책1을 복제를 성공했다. 책컬렉션도 복사해야 하는데 책 컬렉션은 또 다른 객체이다. 그렇기 때문에 책 컬렉션이 가지고 있던 참조를 그대로 참조하게 된다. 결과를 보면 bookshelf2의 책1을 변경해도 bookshelf1의 책1에는 아무런 변화가 없다. 하지만 아래의 코드를 보자. 

     

    const bookshelf1 = {
        책1: "해리포터",
        책컬렉션: {
            소설1: "반지의제왕",
            소설2: "나의라임오렌지나무"
        }
    };
    
    const bookshelf2 = {...bookshelf1}; // 얕은 복사
    
    bookshelf2.책컬렉션.소설1 = '바다의 왕자'
    
    console.log(bookshelf1.책컬렉션.소설1) // 바다의 왕자
    console.log(bookshelf2.책컬렉션.소설1) // 바다의 왕자

     

    bookshelf2의 책컬렉션의 소설1번을 변경하니 bookshelf1의 책컬렉션 소설1도 함께 변경되었다. 즉 bookshelf1의 책컬렉션의 참조를 bookshelf2가 그대로 참조하고 있었다는 것을 의미한다. 이렇게 최상위만 복제되는 것을 얕은 복사라고 한다.

     

    얕은 복사의 장점

    얕은 복사를 하면 데이터를 한 곳에서 변경이 가능하고 메모리를 많이 차지하지 않기 때문에 관리를 효율적으로 할 수 있다는 장점이 있다.

     

    얕은 복사 방법

    • 스프레드 연산자
      대표적인 방법으로 쉽게 얕은 복사를 할 수 있다. 코드가 간결해지고 읽기 쉽기 때문에 많이들 사용하는 방법이다.
    // 배열 얕은 복사
    const arr = [1, 2, 3, 4, 5]
    const arr1 = [...arr]
    
    // 객체 얕은 복사
    const obj = {
      name: "minhoo",
      age: 30,
    };
    
    const obj1 = { ...obj };

     

    • Object.assign( )
      오랫동안 객체를 복사하기 위해 사용되었던 함수로 첫 번째 인자로 객체, 두 번째 인자로 복사할 객체를 받는다. 
    const obj = {
      name: "minhoo",
      age: 30,
    };
    
    const obj1 = Object.assgin({}, obj)

     

    얕은 복사를 할 수 있는 대표적인 방법들을 알아보았다. 얕은 복사를 하면 좋은 점도 있지만 현재의 컴퓨터의 메모리는 메모리를 얼마나 차지하는지 걱정하지 않아도 될 정도이다. 그렇다면 깊은 복사는 어떠할까?

    const person = {
      name: "minhoo",
      age: 30,
      address: {
          city: "서울",
          district: "강남구"
      },
    };
    
    const person2 = JSON.parse(JSON.stringify(person));
    
    person2.address.city = '제주도'
    
    console.log(person2.address.city) // 제주도
    console.log(person.address.city) // 서울

     

    깊은 복사는 객체의 끝까지 복사된다고 말했다. 깊은 복사는 데이터를 변경하게 되었을 때 원본 데이터에 영향을 미치지 않는 것을 확인할 수 있다. 

     

    깊은 복사의 장점

    깊은 복사를 하게 되면 원본 데이터의 영향을 주지 않고 데이터를 변경할 수 있기 때문에 생각하지 못했던 데이터의 변경을 막을 수 있다. 하지만 새롭게 만들기 때문에 메모리를 차지하게 된다는 단점도 있다.

     

    깊은 복사 방법

    • JSON.parse( ), JSON.stringify( )
      JSON의 메서드를 사용해서 깊은 복사를 하는 방법이다. 하지만 이 방법은 문제가 있는데 바로 함수형 데이터는 undefined가 발생한다는 것이다. 
    const person = {
      name: "minhoo",
      age: 30,
      address: {
          city: "서울",
          district: "강남구"
      },
      getAddress: function() {
          console.log("이건 누락")
      }
    };
    
    const person2 = JSON.parse(JSON.stringify(person));
    
    console.log(person) // {name: 'minhoo', age: 30, address: {…}}
    console.log(person) // {name: 'minhoo', age: 30, address: {…}, getAddress: ƒ}

     

    • Lodash의 cloneDeep( ) 활용
      많은 개발자들이 깊은 복사를 효율적으로 사용하기 위해서 등장한 라이브러리의 메서드이다. 오랜 기간 사용되어왔고 많은 개발자들의 검증이 있었기 때문에 효율적으로 사용이 가능하다.

    • structuredClone( ) 함수
      JSON메서드를 사용하는 방법보다 보완 된 웹 API로 많은 데이터 타입을 복사할 수 있다는 장점이 있지만 아쉽게도 JSO메서드를 사용하는 것과 같이 함수를 복사할 수 없다. 함수가 있다면 에러가 발생한다.
    const person = {
      name: "minhoo",
      age: 30,
      address: {
          city: "서울",
          district: "강남구"
      },
      getAddress: function() {
          console.log("이건 누락")
      }
    };
    
    const person2 = structuredClone(person)
    
    console.log(person) 
    console.log(person2) 
    
    //DataCloneError: Failed to execute 'structuredClone' on 'Window': function() {
          console.log("이건 누락")
      } could not be cloned.

     

    • 재귀 함수를 직접 구현
    function deepCopy(obj) {
        if (obj === null || typeof obj !== "object") {
            return obj;
        }
    
        let copy = Array.isArray(obj) ? [] : {};
    
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                copy[key] = deepCopy(obj[key]);
            }
        }
    
        return copy;
    }

     

    재귀 함수를 직접 구현해서 하나하나 복사해 주는 방식을 사용할 수 있다. 하지만 성능적인 문제 혹은 많은 데이터 타입 등의 이유로 잘 사용되지는 않는다.

     

    필요한 상황에 알맞게 데이터를 복사하는 연습을 꾸준히 하며 익혀야 한다.

    반응형

    댓글