[JS] 자바스크립트의 객체 복사

jiny·2025년 1월 20일

기술 면접

목록 보기
29/78

🗣️ 자바스크립트의 객체는 참조에 의해 복사됩니다. 이로 인해 생길 수 있는 오류와, 해당 오류를 피하는 방법을 설명해주세요.

  • 의도: 참조에 의한 접근과 값에 의한 접근을 구분할 수 있는지 확인하는 질문

  • 팁: 객체를 참조가 아닌 값으로서 복사하는 방법도 소개하면 좋다.

  • 나의 답안

    객체, 배열, 함수 등의 객체 데이터 타입참조에 의해 복사됩니다.
    이는 변수에 객체의 값이 직접 저장되는 것이 아니라, 객체가 저장된 메모리 주소(참조)가 저장되는 것입니다.
    객체를 다른 변수에 할당하거나 함수에 전달할 때, 메모리 주소(참조)가 복사되므로 두 변수는 동일한 객체를 가리키게 됩니다.
    복사된 변수는 같은 메모리 주소를 참조하므로, 한 쪽의 변경이 다른 쪽에도 영향을 미칠 수 있습니다.
    따라서 불변성을 유지하거나 깊은 복사를 사용하여 원본 데이터를 보호하는 것이 중요합니다.

    깊은 복사객체의 모든 레벨을 복사하여, 원본 객체와 복사된 객체가 완전히 독립적이게 만드는 방법입니다.
    가장 단순한 깊은 복사 방법은 JSON 방법입니다.
    JSON.stringify() 메서드를 이용하여 객체를 문자열로 변환한 후, 이를 JSON.parse() 메서드를 이용하여 다시 객체로 변환하여 깊은 복사를 수행합니다.
    하지만 이 방법은 함수, undefined, Symbol, 순환 참조를 포함한 객체는 제대로 복사되지 않는다는 단점이 있습니다.

    깊은 복사의 두 번째 방법으로는 재귀 함수를 사용하는 것입니다.
    이는 깊은 복사를 직접 구현하는 방법으로, 재귀적으로 객체의 모든 속성을 복사하는 것입니다.
    하지만 이 방법은 구현이 복잡하다는 단점이 있습니다.

    Lodash 라이브러리를 사용하면 _.cloneDeep 메서드를 사용하여 깊은 복사를 쉽게 수행할 수 있습니다.
    추가적으로, 최신 JavaScript에서는 structuredClone() 메서드를 사용하여서도 깊은 복사를 쉽게 수행할 수 있습니다.
    앞서 설명한 이 두 가지 방법이 가장 간단한 깊은 복사 방법이라서 저는 이 방법을 선호합니다.

  • 주어진 답안 (모범 답안)

    여기서 참조에 의한 복사가 일어나는 이유는 값 자체를 복사해주는 게 아니라 값이 들어 있는 메모리 주소를 복사해서 주기 때문입니다.
    그렇기에 객체를 복사하게 되면 서로 같은 메모리 주소를 가리키게 되어 한쪽이 값을 바꾸면 다른 쪽이 보여주는 값도 바뀌게 되는 것입니다.
    이러한 특징 때문에 여러 오류가 생기곤 합니다.

    그래서 깊은 복사를 통해 객체 또한 값으로서 복사하도록 하는 방법들이 있는데 대표적으로는 JSON.stringify()를 통해 객체를 문자열로 바꾸어서 복사하는 방법이 있고, 비교적 최근에 나온 structuredClone()을 사용하는 방법이 있습니다.
    개인적으로는 후자가 더 짧고 쉽게 때문에 더 애용하는 편입니다.


📝 개념 정리

자바스크립트에서 참조에 의한 접근값에 의한 접근은 데이터가 변수에 저장되고 다른 변수로 복사되거나 함수로 전달될 때의 동작 방식을 설명한다.
이 두 개념은 데이터 타입(원시 타입 vs. 객체 타입)에 따라 결정된다.

자바스크립트에서 객체를 복사하는 것은 프로그래밍에서 자주 필요하지만, 올바르게 수행하려면 객체의 특성과 복사 방식의 차이를 이해해야 한다.
객체 복사는 얕은 복사(Shallow Copy)깊은 복사(Deep Copy)로 나눌 수 있다.

🌟 값에 의한 접근(Pass by Value)

  1. 설명
    • 값에 의한 접근은 원시 데이터 타입(Primitive Types)에 해당한다.
    • 변수에 저장된 값 자체가 복사되며, 복사된 값은 완전히 독립적이다.
      따라서 하나의 값을 변경해도 다른 깂에 영향을 미치지 않는다.
  1. 원시 타입: string, number, boolean, undefined, null, symbol

  2. 작동 방식
    1) 값을 복사하여 새 변수에 전달한다.
    2) 두 변수는 서로 독립적이다.

  3. 예제

    let a = 10; // 원시 값 저장
     let b = a; // 값 복사
     b = 20; // b의 값 변경

    함수 호출 시 동작

    function changeValue(num) {
       num = 20;
       console.log("Inside function:", num); // 20
     }
    
    let x = 10;
    changeValue(x);
    console.log("Outside function:", x); // 10 (원본은 영향 없음)
  1. 특징
    • 값 복사는 메모리의 새로운 위치에 값을 저장한다.
    • 원본 데이터와 복사된 데이터는 서로 영향을 주지 않는다.
    • 메모리 구조: 원시 값은 스택(Stack)에 저장된다.
    • 동작: 복사된 값이 독립적으로 사용되며, 다른 변수에 영향을 주지 않는다.

🌟 참조에 의한 접근(Pass by Reference)

  1. 설명
    • 참조에 의한 접근은 객체 데이터 타입(Object Types)에 해당한다.
    • 변수에 객체의 값이 직접 저장되는 것이 아니라, 객체가 저장된 메모리 주소(참조)가 저장된다.
    • 객체를 다른 변수에 할당하거나 함수에 전달할 때, 메모리 주소(참조)가 복사되므로 두 변수는 동일한 객체를 가리키게 된다.
  1. 객체 타입: Object, Array, Function

  2. 작동 방식
    1) 객체의 참조값(메모리 주소)을 복사하여 새 변수에 전달한다.
    2) 두 변수가 동일한 객체를 참조한다.

  3. 예제

    let obj1 = { name: "Alice" }; // 객체 생성
     let obj2 = obj1; // 참조 복사
     obj2.name = "Bob"; // obj2를 통해 수정
    
     console.log(obj1.name); // "Bob" (같은 객체를 참조)
     console.log(obj2.name); // "Bob"

    함수 호출 시 동작

    function modifyObject(obj) {
       obj.name = "Bob";
     }
    
    let person = { name: "Alice" };
    modifyObject(person);
    console.log(person.name); // "Bob" (원본 객체가 변경됨)
  1. 특징
    • 객체는 메모리에 저장된 데이터를 참조하므로, 하나의 변수를 통해 객체를 변경하면 모든 참조가 영향을 받는다.
    • 변수는 실제 데이터가 아니라 데이터의 참조(주소)를 저장한다.
    • 불변성(Immutability)을 유지하거나 깊은 복사(Deep Copy)를 사용하여 원본 데이터를 보호하는 것이 중요하다.
    • 메모리 구조: 객체힙(Heap)에 저장되고, 변수에는 힙의 주소(참조)스택에 저장된다.
    • 동작: 복사된 변수는 같은 메모리 주소를 참조하므로, 한쪽의 변경이 다른 쪽에도 영향을 미친다.
    • 주의점: 객체의 참조를 공유하기 때문에, 의도하지 않은 변경으로 오류가 발생할 수 있다.

🌟 값에 의한 접근과 참조에 의한 접근 비교

값에 의한 접근참조에 의한 접근
적용되는 데이터 타입원시 타입객체 타입
복사 방식값 자체를 복사메모리 주소를 복사
독립성복사된 값은 독립적복사된 참조는 동일 객체를 가리킴
원본 데이터에 미치는 영향없음모든 참조가 영향을 받음

🌟 얕은 복사(Shallow Copy)

얕은 복사는 객체의 1단계 속성만 복사한다.
즉, 객체 내부의 참조 타입(다른 객체나 배열 등) 속성은 복사되지 않고 원본 객체의 참조를 공유한다.

  1. 얕은 복사 방법
    1) Object.assign() 사용

    const original = { a: 1, b: { c: 2 } };
     const shallowCopy = Object.assign({}, original);
     shallowCopy.b.c = 42;
    
     console.log(original.b.c); // 42 (참조 공유)

    2) 스프레드 연산자(...) 사용

    const original = { a: 1, b: { c: 2 } };
     const shallowCopy = { ...original };
     shallowCopy.b.c = 42;
    
     console.log(original.b.c); // 42 (참조 공유)
  1. 특징
    • 원본 객체의 최상위 속성은 복사되지만, 중첩된 객체(내부 객체)는 참조가 복사된다.
    • 원본과 복사본은 내부 객체를 공유하므로, 내부 객체를 수정하면 원본에도 영향을 미친다.

🌟 깊은 복사(Deep Copy)

깊은 복사는 객체의 모든 레벨을 복사하여, 원본 객체와 복사된 객체가 완전히 독립적이게 만든다.
중첩된 객체나 배열도 별도로 복사된다.

  1. 깊은 복사 방법
    1) JSON 방법 (간단하지만 한계 존재)

    const original = { a: 1, b: { c: 2 } };
     const deepCopy = JSON.parse(JSON.stringify(original));
     deepCopy.b.c = 42;
    
     console.log(original.b.c); // 2 (독립적)
    • 장점: 간단하고 빠르며, 대부분의 일반적인 객체를 복사하는 데 적합하다.
    • 단점: 함수, undefined, Symbol, 순환 참조(Circular Reference)를 포함한 객체는 제대로 복사되지 않는다.
      const obj = { a: 1, b: undefined, c: Symbol('test') };
       const copy = JSON.parse(JSON.stringify(obj));
       console.log(copy); // { a: 1 } (b와 c는 복사되지 않음)

    2) 재귀 함수 사용: 깊은 복사를 직접 구현하려면 재귀적으로 객체의 모든 속성을 복사해야 한다.

    function deepCopy(obj) {
      if (obj === null || typeof obj !== 'object') {
        return obj; // 원시 값은 그대로 반환
      }
      const copy = Array.isArray(obj) ? [] : {}; // 배열과 객체 구분
      for (const key in obj) {
        if (obj.hasOwnProperty(key)) {
          copy[key] = deepCopy(obj[key]); // 재귀 호출로 중첩 객체 처리
        }
      }
      return copy;
    }
    
    const original = { a: 1, b: { c: 2 } };
    const deepCopyObj = deepCopy(original);
    deepCopyObj.b.c = 42;
    
    console.log(original.b.c); // 2 (독립적)
    • hasOwnProperty를 사용해 객체의 자체 프로퍼티만 복사한다. (상속된 프로퍼티 제외)
    • 각 프로퍼티의 값이 객체라면, 재귀 호출로 중첩 객체를 처리한다.
    • 최종적으로 완성된 복사본을 반환한다.

    3) Lodash 라이브러리 사용: Lodash는 강력한 유틸리티 라이브러리로, _.cloneDeep 메서드를 제공하여 깊은 복사를 쉽게 수행할 수 있다.

    const _ = require('lodash');
    
     const original = { a: 1, b: { c: 2 } };
     const deepCopy = _.cloneDeep(original);
     deepCopy.b.c = 42;
    
     console.log(original.b.c); // 2 (독립적)

    4) 구조화 복사(Structured Clone): 최신 JavaScript에서는 structuredClone() 메서드를 사용하여 객체를 깊게 복사할 수 있다.

    const original = { a: 1, b: { c: 2 } };
     const deepCopy = structuredClone(original);
     deepCopy.b.c = 42;
    
     console.log(original.b.c); // 2 (독립적)
    • 장점
      • 함수, undefined 등 JSON 방식의 한계를 극복한다.
      • 브라우저 환경에서 기본 제공된다.
      • 순환 참조 객체도 처리 가능하다.
    • 단점: 매우 최신 기술로, 구형 브라우저에서는 지원하지 않을 수 있다.

🌟 얕은 복사와 깊은 복사 비교

얕은 복사깊은 복사
복사 깊이1단계 속성까지만 복사객체의 모든 중첩 레벨 복사
내부 객체 처리참조를 공유별도의 새로운 객체 생성
성능빠름복잡한 객체일수록 느림
예제 사용법Object.assign, 스프레드 연산자JSON, 재귀, structuredClone

0개의 댓글