[JS교양] 객체의 얕은 복사, 깊은 복사 | 얕은 지식으로 정리한 글

나는 자바스크립트를 대~충 배워 대~충 쓰고 있었다. 

아직도 배우는 단계인데 모르는 지식이 굉장히 많고 대충 사용하면 안되겠구나라는걸 느끼게 된 것 중 하나인 

객체의 얕은 복사와 깊은 복사를 정리한 글. (얕은 지식으로 정리하였음)

 


 

객체

JS에서는 데이터 타입을 크게  원시 데이터 타입(Primitive Data Types) 참조 데이터 타입(Reference Data Types) 두 가지로 나눌 수 있다. 객체는 둘 중 참조 데이터 타입 이다. 객체는 변수에 직접 값을 저장하지 않고 값이 저장된 메모리 주소(참조)를 저장한다. 객체의 속성에 접근해서 데이터를 변경하면 `데이터를 가리키는 주소값이` 변경 된다.(데이터가 변경되면 데이터의 주소값이 바뀌기 때문)

  • 메모리에는 데이터가 저장되는 주소가 있다. (`데이터 주소`)
  • 속성에는 `데이터 주소`를 가리키는 주소가 있다. (`데이터를 가리키는 주소` 라고 칭하겠음 정확한 명칭 모름,,)
  • 객체 `A`를 객체 `B`에 복사하면 `A`의 ` 데이터를 가리키는 주소`를 복사한다. 
  • `B`에서 데이터를 변경하면 ` 데이터를 가리키는 주소 `가 가리키는 `데이터 주소`를 변경한다
  • ` 데이터를 가리키는 주소 `를 공유하기 때문에 `B`에서 변경한 주소를 `A`에서도  참조하게 된다.
  • 대충 이렇게 주소값이 변경이 되는 특징을 가진 것들을 가변성 자료라고 한다.

요약하면 객체를 대충 복사해서 사용하면 원본 객체 데이터에 영향을 끼칠 수 있으니 가변성의 리스크를 염두하고 코드를 작성해야한다. 

 

 

코드로 보면 좀 쉽게 이해가 갈 것이다. 대충 복사하면 어떤일이 발생하는지 코드로 예시를 봐보도록 하자. 

// user객체 생성 (name = 길동)
var user = {
    name: "길동",
    age: 30,
}

// newUser에 user복사
var newUser = user;
console.log(user.name);  // "길동" 출력

newUser.name = "순신";  // 새 객체의 name 변경
console.log(user.name); // "순신" 출력 (원본 객체도 같이 변경됨)

복사한 객체의 속성을 변경했음에도 원본 객체까지 변경된 것을 볼 수 있다. (`자체주소` 를 공유하기 때문)

  • 메모리 어딘가에는 "길동"이 담긴 `데이터 주소`가 있다. 
  • user의 name 속성에는 "길동"이라는 `  데이터를 가리키는 주소 `가 있다. 
  • newUser 객체를 복사하면 "길동"이라는 값이 담긴 ` 데이터를 가리키는 주소 ` 를 복사한다. 
  • newUser의 name 속성을 "순신"으로 변경하면  "순신"이 담긴 새로운 `데이터 주소` 를 ` 데이터를 가리키는 주소 `덮어쓴다. (= 데이터를 가리키는 주소` 를 공유하기 때문에 원본 객체에서도 `데이터 주소`값이 바뀌는 것)

 


객체 올바르게 복사하기(1)

그래서 원본 객체를 유지할 수 있으면서, 같은 데이터를 지닌 객체를 복사할때는 어떻게 하는가? 

새로운 객체에 값만 복사해주면 된다. 

var user = {
    name: "길동",
    age: 30,
}

// 복사 객체 반환 함수
function copyObj(user, name){
    return {
        name: name,
        age: user.age,
    }
}

var newUser = copyObj(user, "순신");

console.log(user.name);  // 길동 출력 (원본 유지)
console.log(newUser.name) // 순신 출력

원본 객체의 데이터는 복사하면서 새로운 객체를 생성하기 때문에 값은 같아도 참조 하는 주소값은 개별적인 공간에 저장된다. (= 복사본을 변경해도 원본은 유지된다)

 

문제점

객체의 속성이 적다면 별 문제 없겠지만 아주아주 많은 속성의 객체를 복사하려면 굉장한 노동력이 필요할 것이다.(하드코딩 문제점)

 


객체 올바르게 복사하기(2) -  얕은 복사

하드코딩 문제에 대한 해결 방안 코드 예시

var user = {
    name: "길동",
    age: 30,
}

var copyObject = function(target){
    var result = {};
    
    // for ~ in으로 전체 순회하며 복사
    for(var prop in target){
        result[prop] = target[prop];
    }
    return result;
}

var newUser = copyObject(user);
  • for(~in) 문법을 사용해서 객체의 모든 요소를 순회한다. 
  • 순회하며 복사될 객체의 속성과 값을 저장한다.
  • 복사가 완료된 객체를 반환한다.

 

문제점

안타깝게도 이 방법(얕은 복사)도 약간의 문제가 있는데, 중첩객체 복사시 일반 데이터는 정상적으로 복사가 가능하지만 객체안의 객체(중첩된 객체) 에 대한 복사는 제대로 수행되지 않는다. (중첩된 객체일 경우 주소값을 복사하게됨)

 

예시)

var info = {
	name: "길동",
    url: {
    	blog: "reactjy2.tistory.com",
        git: "www.github.com",
    }
}

`name `속성은 우리가 바라는대로 복사가 되겠지만 `url` 속성은 그렇지 않을 것이다.

 


객체를 올바르게 복사하기(3) - 깊은 복사

찐막이다. 깊은 복사의 패턴을 사용한다면 우리가 원하는대로 복사가 될 것이다. 단, 재귀함수를 사용한다. (어렵다)

var info = {
    name: "길동",
    url:{
        blog: "www.tistory.com",
        github: "www.github.com",
    }
}

var copyObjectDeep = function(target){
    var result = {};
    if(typeof target === 'object' && target !== null){
        for(var prop in target){
            result[prop] = copyObjectDeep(target[prop]); // 본인 호출
        }
    }else{
        return target;
    }
    return result;
}

var newUser = copyObjectDeep(info);

info객체

  1. `info` 객체는 `name`과 `url` 두 개의 속성을 가진다. `url` 속성은 또 다른 객체를 갖고 있다. (중첩객체)

copyObjectDeep() 함수

  1. `target`을 매개변수로 받는다
  2. `result`는 `target`을 복사해서 반환할 변수
  3. `target` 이 객체인지 확인함 (null 관련 설명은 생략)
  4. 객체일 경우 `for...in`반복문을 사용해서 순회함
  5. 각 속성(`prop`)마다 `copyObjectDeep(target[prop])`를 호출하여 재귀적으로 속성의 값을 복사 =>  중첩된 객체의 경우에도 깊은 복사
  6. `target`이 객체가 아닌 경우 (`else`), 즉 원시 데이터 타입인 경우에는 값을 그대로 반환
  7. `result` 반환

댓글

Designed by JB FACTORY