얕은 복사와 깊은 복사

이지·2023년 4월 15일
0
post-thumbnail

참조형 데이터를 복사하는 경우의 문제점

let obj1 = {c: 10, d: 'ddd'};
let obj2 = obj1;

obj2.c = 20;

console.log(obj1.c, obj2.c);

→ 서로의 프로퍼티 값이 다르게 나올 것 같았지만 실제론 같은 20이 출력

→ 원본이나 사본의 프로퍼티 값을 변경하면 둘 다 영향을 받음.

얕은 복사

바로 아래 단계의 값만 복사하는 방법

  • 기존 정보를 복사해서 새로운 객체를 반환하는 함수(얕은 복사)

    let copyObject = function(target) {
    	let result = {};
    	for(let prop in target) {
    		result[prop] = target[prop];
    	}
    	return result;
    };
  • 중첩된 객체에 대한 얕은 복사

    → 중첩된 객체에서 참조형 데이터가 저장된 프로퍼티를 복사할 때 그 주소값만 복사

    → 해당 프로퍼티에 대해 원본과 사본이 모두 동일한 참조형 데이터의 주소를 가리키게 됨

    ⇒ 그 결과, 사본을 바꾸면 원본도 바뀌고, 원본을 바꾸면 사본도 바뀜

    let user = {
    	name: 'lion',
    	skills: {
    			HTML: 'lv 1',
    			CSS: 'lv 2',
    			JS: 'lv 3'
    	}
    };
    let user2 = copyObject(user);
    
    user2.name = 'jisu';
    console.log(user.name === user2.name);
    
    user.skills.HTML = 'lv 5';
    console.log(user.skills.HTML === user2.skills.HTML);
    
    user2.skills.CSS = 'lv 3';
    console.log(user.skills.CSS === user2.skills.CSS);

    → 얕은 복사는 객체에 직접 속한 프로퍼티에 대해 복사한 경우엔 문제가 발생하지 않음

얕은 복사 메모리 영역에서 확인해보기

  • let user2 = copyObject(user); // user2에 user에 대한 얕은 복사 실행됨

  • user2.name = 'jisu'; 실행

→ name은 user 객체에 직접 속한 프로퍼티로, user2가 값을 수정해도 새로운 데이터를 만들기 때문에 서로 다른 값임.

  • user.skills.HTML = 'lv 5'; // 중첩된 객체의 프로퍼티 값을 수정함

⇒ 둘이 같은 값을 출력하고 있음

  • user2.skills.CSS = ‘lv 3’

⇒ 이런 현상을 발생하지 않기위해 중첩된 객체에 대해서 불변 객체로 만들 필요가 있음

깊은 복사

내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법

  • 중첩된 객체에 대한 깊은 복사
    let user2 = copyObject(user);
    user2.skills = copyObject(user.skills); //중첩된 객체에 대해서도 한번 더 복사해줌
    
    user2.name = 'jisu';
    
    user.skills.HTML = 'lv 5';
    console.log(user.skills.HTML === user2.skills.HTML); //false
    
    user2.skills.CSS = 'lv 3';
    console.log(user.skills.CSS === user2.skills.CSS); //false
  • 실행 결과


결론

어떤 객체를 복사하는 경우, 객체 내부의 모든 값을 복사해서 완전히 새로운 데이터를 만들고자 할 때, 객체의 프로퍼티 중에서 그 값이 기본형 데이터일 경우에는 그대로 복사하면 되지만 참조형 데이터는 다시 그 내부의 프로퍼티들을 복사해야함. 이 과정을 참조형 데이터가 있을 때마다 재귀적으로 수행해야만 비로소 깊은 복사가 됨

  • 깊은 복사를 수행하는 함수

    let copyObjectDeep = function(target){
    	let result = {};
    	if(typeof target === 'object' && target !== null){ // target이 객체인 경우
    		for(let prop in target){
    				result[prop] = copyObjectDeep(target[prop]);
    		}
    	} else { // target이 객체가 아닌 경우
    			result = target;
    	}
    	return result;
    };
  • 콘솔창에서 실행한 결과

    → user와 user2가 서로 완전히 다른 객체를 참조하게 되어 어느 쪽의 프로퍼티를 변경하더라도 다른 쪽에 영향을 주지 않는 것을 확인할 수 있음!


깊은 복사를 처리할 수 있는 다른 방법들

  • hasOwnProperty 메서드를 활용해 프로토타입 체이닝을 통해 상속된 프로퍼티를 복사하지 않게끔 할 수 있음

  • 객체를 JSON문법으로 표현된 문자열로 전환했다가 다시 JSON 객체로 바꾸는 것

    → 단순함에도 잘 동작함

    → But, 메서드(함수)나 숨겨진 프로퍼티인 proto나 getter/setter 등과 같이 JSON으로 변경할 수 없는 프로퍼티들은 모두 무시.

    →객체 안에 순환 참조가 있을 경우 무한 루프에 빠지므로 주의가 필요(깊은 복사 수행 시 상황에 맞는 방법을 선택해야함)

    → httpRequest로 받은 데이터를 저장한 객체를 복사할 때 등 순수한 정보만 다룰 때 활용하기 좋음

    • JSON을 활용한 간단한 깊은 복사
      let copyObjectViaJSON = function(target) {
      	return JSON.parse(JSON.stringify(target));
      };
      let obj = {
      	a: 1,
      	b: {
      			c: null,
      			d: [1, 2],
      			func1: function () { console.log(3); }
      		},
      		func2: function () { console.log(4); }
      	};
      let obj2 = copyObjectViaJSON(obj);
      
      obj2.a = 3;
      obj2.b.c = 4;
      obj.b.d[1] = 3;
      
      console.log(obj); 
      console.log(obj2);
      • 실행결과

정리하기

얕은 복사는 참조형 타입의 값의 바로 아래 단계의 값만 복사하는 방법

얕은 복사를 사용하여 객체를 복사하면 객체의 속성은 새로운 객체로 복사되지만, 속성값이 객체인 경우 참조 값이 복사되기 때문에 객체 내부의 값을 변경하면 원본 객체와 복사본 모두 변경됨

깊은 복사는 깊은 복사는 내부의 모든 값들을 하나하나 찾아서 전부 복사하는 방법

속성값이 객체인 경우에도 객체 내부의 값이 변경되어도 원본 객체에 영향을 미치지 않음


코어 자바스크립트 책을 읽고 정리한 글입니다.

0개의 댓글