ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 깊은복사와 얕은복사, 그리고 최신문법 structuredClone 까지
    JavaScript 2023. 1. 15. 11:40

     

    먼저, 자바스크립트에서 값은 원시값 참조값 두 가지 데이터 타입의 값이 존재한다.

    원시형

    출처: MDN

    즉, 원시값은 기본 자료형 (단순한 데이터)를 의미하며 변형할 수 없다!!

    변형할 수 없다는 것이지 재할당 할 수 없다는 뜻은 아니다.

    원시값을 복사할 때 그 값은 또 다른 독립적인 메모리 공간에 할당하기 때문에,

    복사를 하고 값을 수정해도 기존 원시값을 저장한 변수에는 영향을 끼치지 않는다.

    • String
    • number
    • boolean
    • undefined
    • symbol
    • null

     

    참조형

    • 원시형이 아닌것들은 모두 참조형이다.
    • 예로는 객체, 배열, 함수와 같은 것들이 있다.
    • 참조 자료형을 변수에 할당할 때는 변수에 값이 아닌 주소를 저장한다.
    • 따라서 할당된 변수를 조작하는 것은 사실 객체 자체를 조작하는 것이 아닌, 해당 객체의 참조를 조작하는 것이다.    

     

    > 데이터 타입 쪼끔 더 알고가기

     


    깊은복사와 얕은복사

    원시값과 참조값을 복사해보자 !

     

    원시값

    let str = 'hizini'
    let copiedStr = str
    
    str = 'HIZINI'
    
    console.log(str)
    console.log(copiedStr)

    원시값은 변경불가능한 값이며 주소가 복사되지 않고, 값이 복사되기 때문에 

    copiedStr str는 별개의 주소값을 가지며 기존 값에 영향을 끼치지 않는다.

     

    깊은 복사란 원시형을 복사할 때처럼 독립적인 주소 값을 할당하여 생성하는 것을 말한다.

     

    참조값

    let Hizini = {
      age: '27',
      job: 'frontend Developer',
      language: {first: 'js', second: 'python'}
    }
    
    let copiedHizini = Hizini
    
    copiedHizini.age = '25'
    
    console.log(Hizini, copiedHizini)

    copiedHizini age를 바꿨는데 원본인 Hizini age까지 모두 25로 바뀌었다.

    이렇게 객체와 같은 참조형변수들은 변수에 할당하게되면 주소값이 할당되기 때문에

    해당 객체의 값을 변경하게되면 참조하고있는 모든 곳에 영향을 끼친다.

     

    얕은 복사란 위처럼 객체를 복사할 때

    원래값과 복사된 값이 같은 참조를 가리키고있는 것을 말한다.

     

    객체안에 객체가 있을 경우 한개의 객체라도 원본 객체를 참조하고 있다면 이를 얕은 복사라고 한다.

     

    그렇다면 객체와 같은 참조형에서 깊은복사를 구현하고 싶을때는 어떻게 해야할까?


    우선 깊은복사는 아니지만 원본의 값을 변형시키지 않고 복사한 객체의 프로퍼티 값만 변경하는 방법들이 있다 ..!!!

     

    1. 전개연산자 (Spread Operator)

    let Hizini = {
      age: '27',
      job: 'frontend Developer',
      language: {first: 'js', second: 'python'}
    }
    
    let copiedHizini = { ...Hizini }
    
    copiedHizini.age = '25'
    copiedHizini.language.first = 'java'
    
    console.log(Hizini.age, copiedHizini.age)
    console.log(Hizini.language.first, copiedHizini.language.first)

    copiedHizini  age를 할당해주면 원본(Hizini)과  copiedHizini age가 연결되어있지 않고 서로 다른 값을 가짐을 확인 할 수 있다.

    그러나 전개연산자를 활용하여도 두단계 이상의 depth부터는 깊은복사가 이루어지지 않는다 !!!

    copiedHizini  language  first를  java 바꾸었더니 원본인 Hizini까지 java로 변경되었음을 확인 할 수 있다.

     

    전개 연산자는 두단계 이상의 depth부터는 여전히 참조 값을 전달하는 얕은복사를 하기 때문에

    한단계까지의 깊은복사만을 이용하려면 전개연산자를 사용하면 될 것 같다.

     

    2. Object.assign() 이용하기

    Object.assign(생성할 객체, 복사할 객체)의 형태로 

    메소드의 첫 번째 인자로 빈 객체를 넣어주고 두 번째 인자로 복사할 객체를 넣어주면 된다.

    빈 object에 복사할 객체를 병합하여 반환한다는 뜻이다.

    let Hizini = {
      age: '27',
      job: 'frontend Developer',
      language: {first: 'js', second: 'python'}
    }
    
    let copiedHizini = Object.assign({}, Hizini)
    
    copiedHizini.age = '25'
    copiedHizini.language.first = 'java'
    
    console.log(Hizini.age, copiedHizini.age)
    console.log(Hizini.language.first, copiedHizini.language.first)

    역시, 원본과 copiedHizini  age가 연결되어있지 않고 서로 다른 값을 가짐을 확인 할 수 있다.

    하지만 이 또한 전개연산자와 같이 두단계 이상의 depth부터는 얕은복사가 진행된다..ㅠㅠ

    출처: MDN


    Depth 에 상관없이 복사를 하고 싶다면 ?!

     

    참조값 깊은복사

    1. JSON.parse, JSON.stringify 이용하기

    JSON.stringfy()는 객체를 문자열로 변환시켜주는 함수이다.

    객체를 json 문자열로 변환하는과정에서 원본 객체와의 참조가 모두 끊어진다.
    문자열로 변환후 JSON.parse()를 이용해 다시 객체로 만들어주면 깊은 복사가 된다.

    let Hizini = {
      age: '27',
      job: 'frontend Developer',
      language: {first: 'js', second: 'python'}
    }
    
    let copiedHizini = JSON.parse(JSON.stringify(Hizini))
    
    copiedHizini.language.first = 'java'
    
    console.log(Hizini.language.first, copiedHizini.language.first)

    이 방법이 가장 간단하고 쉽지만 다른 방법에 비해 성능면에서 리소스를 많이 잡아먹어 느리다는 것과

    객체가 function일 경우,  undefined로 처리한다는 것이 단점이라고 한다...

    최근에는 JSON에 대한 속도 최적화가 어느정도 되어있다고는 하지만 복잡한 객체에 대해서는 처리속도가 느리다고 알려져 있다.

     

    2. 재귀 함수를 구현하여 복사하기

    const object = {
      a: "a",
      number: {
        one: 1,
        two: 2,
      },
      arr: [1, 2, [3, 4]],
    }
     
    function deepCopy(object) {
      if (object === null || typeof object !== "object") {
        return object
      }
      // 객체인지 배열인지 판단
      const copy = Array.isArray(object) ? [] : {}
     
      for (let key of Object.keys(object)) {
        copy[key] = deepCopy(object[key])
      }
     
      return copy
    }
     
    const copy = deepCopy(object)
     
    copy.number.one = 3
    copy.arr[2].push(5)
     
    console.log(object === copy) // false
    console.log(object.number.one === copy.number.one) // false
    console.log(object.arr === copy.arr) // false
     
    console.log(object) // { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }
    console.log(copy) // { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }

     

    ㄷ ㄷ 복잡하다는게 단점이다..

     

    3. 라이브러리 이용하기 ! (lodash의 clone deep)

    import cloneDeep from lodash/cloneDeep;
    
    const original = { 
      ...
    }
    const copied = cloneDeep(original);
      
      
    copied === original // false
    copied.nested === original.nested // false
    copied.nested.doubleNested === original.nested.doubleNested // false

    그런데, 깊은복사를 하나를 위해 외부 라이브러리를 설치하거나 느리거나 복잡해야 하는것인가 ??? -_-

    하고 찾아보니 이제 아니래요~!!!!

     

    3. structuredClone

    자바스크립트는 이제 structuredClone()이라는 깊은 복사를 위한 내장 기능이 제공된다 !!!

    호환 브라우저는 이렇다.

    let Hizini = {
      age: '27',
      job: 'frontend Developer',
      language: {first: 'js', second: 'python'}
    }
    
    let copiedHizini = structuredClone(Hizini)
    
    copiedHizini.language.first = 'java'
    
    console.log(Hizini.language.first, copiedHizini.language.first)

    structuredClone(value)로 간단하게 사용할 수 있다.

     

    [structuredClone이 지원하는 타입] 


    결론

    JS에서 참조 값의 깊은 복사본을 생성해야 하는 경우 더 이상 라이브러리를 활용하지 않아도 되겠다.

    짱짱 structuredClone()가 있으니까 !!

    반응형

    'JavaScript' 카테고리의 다른 글

    HTTP 구조  (0) 2022.06.17
    Axios  (0) 2022.06.14
    자바스크립트 객체 종류와 생성 시기  (0) 2022.06.10
    Array Method  (0) 2022.06.09
    배열 (Array)  (0) 2022.06.07

    댓글

Designed by Tistory.