관리 메뉴

흰둥씨의 개발장

[함수형 코딩#7] 얕은 복사 vs 깊은 복사 본문

함수형 프로그래밍/쏙쏙 들어오는 함수형 코딩

[함수형 코딩#7] 얕은 복사 vs 깊은 복사

돈워리비해삐 2024. 4. 4. 03:47

불변성(Immutable)

상태를 변경하지 않는 것 (<=> 상태가 변경된다는 의미는 "메모리에 할당된 값을 변경하는 모든 행위")
ㄴ 불변성을 지키지 않는 예시 ) 전역변수 남용해서 어디서 값이 바뀐지 모르는 상황, 변수에 재할당, etc...
ㄴ 불변성을 지키지 않을 때 함수형 프로그래밍에서 일어나는 일 :  야근, 예기치 못한 동작(에러, 실행...등), 의도치 않은 상태 변화...
ㄴ 불변성을 지키는 것으로 "참조에 의한 전달"로 변경될수 있는 자료형의 변화를 쉽게 감지할수 있어 예기치못한 상황을 막을수 있음 

 

변수는 메모리 주소를 가지고 있고, 변수의 선언은 값이 할당될 공간을 마련하는 것
(변수는 값을 저장하기 위한 공간 자체, 메모리 공간을 식별하기 위해 붙인 이름) 
=> 변수에 값을 할당하면 "변수가 가리키는 메모리주소에 가서 할당된 값을 찾을 수 있다"

copy를 알기위해 먼저 원시 타입과 객체(참조) 타입의 차이에 대해 알 필요가 있음

구분 원시타입 (Primitive type) 객체(참조)타입 (Object/reference type)
정의  변경 불가능한 값 (immutable value) 변경 가능한 값 (mutable value)
변수에 할당하면 ? 확보된 공간에 실제 값이 저장되는 것처럼 동작 확보된 공간에 참조 값이 저장됨
해당 타입이 할당된 변수를
다른 변수에 할당하면? 
값에 의한 전달 
(원본의 원시 값이 복사되어 전달됨)
참조에 의한 전달 
(원본의 참조값이 복사되어 전달됨)
자료형  - string
- number
- boolean
- undefined
- Symbol
- null
- BigInt(ES11에서 추가됨)
- Array
- Object
- function
- etc...
해당 타입을 갖는
변수 값을 변경하려면? 
재할당 외에는 방법이 없음  재할당 없이 직접 변경가능 (동적추가, 갱신, 삭제 가능)
//원시타입 예시
let myRank = 1;         //원시타입(숫자)을 변수에 할당 =>메모리 주소1에 값 저장 
let yourRank = myRank;  //다른 변수에 복사! =>메모리 주소 1에 있던 값을 복사(yourRank도 같은 원시값 참조하다가)
yourRank = 2;           //재할당  =>재할당 시점에 새로운 값을 새로운 메모리 주소 2에 저장

console.log(myRank);    //1 결과적으로 복사본 변경되어도, 원본이 변경되지 않음
console.log(yourRank);  //2

//yourRank가 myRank를 복사할 때는 같은 원시값을 복사(참조)하다가,
//yourRank에 재할당이 일어나면 새로운 메모리주소에 값이 저장되어 myRank는 변경 없음 
//=> 값에 의한 전달(Call by value)
//원시타입 예시2
function test (number){
	return number += 100;
}

let num = 0;     //원시값

console.log(num);//0 원본그대로

test(num);       //100 원시타입 인수는 값자체가 복사되어 매개변수에 전달되기 때문에 

console.log(num);//0 원본 훼손되지 않음

//객체 타입 예시 
let obj = { a : 1, b : 2 };  //객체 타입을 할당, 메모리 주소1에 저장 
let objCopy = obj;           //objCopy도 메모리 주소1을 복사해 감 
                             //=> 참조에 의한 전달(Call by reference)

objCopy.name = "Zzanggu";    //복사본의 값을 변경하면,(프로퍼티 값 동적 생성)

console.log(objCopy);  //{a: 1, b: 2, name: 'Zzanggu'} 복사본만 변경되는 것이 아니라 
console.log(obj);      //{a: 1, b: 2, name: 'Zzanggu'} 원본도 변경됨
//객체 타입 예시2
function test(obj){
	return obj.name = '흰둥이';
}
let myPet = { name : '짱구' };

console.log(myPet);  //{name: '짱구'}

test(myPet);         //객체 타입 인수는 참조값이 복사되어 매개변수에 전달되기 때문에

console.log(myPet);  //{name: '흰둥이'} 참조값을 통해 객체 변경할 경우 원본이 훼손됨 => 부수효과 발생

 

 

구분 방어적 복사 (=deep copy) 카피온라이트(=shallow copy)
언제 써야 할까?  신뢰할 수 없는 코드와 데이터 주고 받을 때  통제 가능한 데이터 쓸 때 
동작 객체에 중첩되어있는 객체까지 모두 복사함 1depth만 복사함
객체의 경우 참조값을 복사
예시  원시 값을 할당한 변수를 다른 변수에 할당하는 것,
객체를 복사할때는 아예 새로운 객체를 생성해서 복사하기
객체를 할당한 변수를 다른 변수에 할당하는 것 
목적 (안전지대의 경계에서)
신뢰할수 없는 데이터는 원본이 변경되는 것을 막자
(얕은 복사보다 리소스 좀더 듦) 
(안전지대 안에서)
통제 가능한 데이터는 원본 보존되니까
리소스를 적게 쓰자 
원칙 1. 데이터가 안전한 코드에서 나갈때 복사하기
2. 안전한 코드로 데이터가 들어올때 복사하기 
(tip. 신뢰할 수 없는 데이터를 받아오는 동작을
분리해서 검증하자 / JS에서 구현시 Lodash library 추천함)
1. 바꿀 데이터의 얕은 복사
2. 복사본을 변경함
3. 복사본을 리턴함
깊은복사와
얕은 복사의
차이점
모든 레벨을 새롭게 복사하기 때문에
내부에 참조데이터가 있어도 
원본과 복사본의 상태가 공유 되지 않음

즉, 원본의 내부에 있는 참조데이터 값 하나를 변경하더라도 
복사본 내부의 해당값에 영향이 없음
첫 번째 레벨만 복사하기 때문에 
내부에 참조데이터가 있는경우 
원본과 복사본의 상태가 공유 됨

즉, 원본의 내부에 있는 참조데이터 값 하나를 변경하면 복사본 내부의 해당값도 변경됨

 

 

객체 깊은 복사 

//JSON.parse()JSON.stringify() 사용하기
//아래 방법은 쉽지만, 내부에 함수, undefined, Symbol 등의 타입이 포함되어있다면 제대로 복사되지 않음

let obj = { a: 1, b: 2 };

let objCopy = JSON.parse(JSON.stringify(obj));
objCopy.c = 3

console.log(obj === objCopy) //false
//재귀함수 사용하기

let MY = { a: 1, b: { c: 2 } };

function Copycat (OBJ) {
    let res = {};
    for(let key in OBJ){
    	typeof OBJ[key] === 'object' ? res[key] = Copycat(OBJ[key]) : res[key] = OBJ[key];
    }
    return res;
}


let copy = Copycat(MY);

console.log(copy === MY); //false
console.log(copy);        //{ a: 1, b: { c: 2 } };

 

 

배열의 깊은 복사 

//JSON.parse()JSON.stringify()사용하기
//아래 방법은 배열이나 객체 내부에 
//함수, undefined, Date 객체, RegExp 객체 등 
//JSON으로 변환할 수 없는 값을 포함하지 않을 때만 사용가능

const original = [1, 2, 3, [4, 5]];
const copy = JSON.parse(JSON.stringify(original));

 

//재귀함수사용하기 
function deepCopy(obj) {
  // 기본 타입이거나 null이면, 그대로 반환
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  // 배열인 경우
  const copy = Array.isArray(obj) ? [] : {};
  
  // 객체 또는 배열일 경우
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      copy[key] = deepCopy(obj[key]); // 재귀적으로 복사
    }
  }
  return copy;
}

const original = [1, 2, 3, [4, 5],{name: "흰둥이"}];
const copy = deepCopy(original);

console.log(copy); // [1, 2, 3, [4, 5],{name: "흰둥이"}];
console.log(original === copy); // false
console.log(original[3] === copy[3]); // false
console.log(original[4] === copy[4]); // false

 

 

객체 얕은 복사

//Object.assign()
const 점심차려이각박한세상속에서 = { name : '하하' };

const 정발산기슭곰발냄새 = Object.assign({}, 점심차려이각박한세상속에서); //새로운 객체를 생성해버리면 됨

console.log(점심차려이각박한세상속에서 ===  정발산기슭곰발냄새);  //false

===================================

let original = { a: 1, b: { c: 2 } };
const copy = Object.assign({}, original);
original.b.c = 5;
console.log(copy.b.c); // 5
console.log(original.b === copy.b); // true 내부에 참조데이터값이 있는경우 원본과 복사본 상태공유됨
console.log(original === copy);  //false
//스프레드 연산자 

const myName = { name : 'cho seongjin' }

const yourName = { ...myName };

console.log(myName === yourName);  //false

=====================
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
copy.a = 3;
copy.b.c = 4;

console.log(original); // 카피본 값만 바꿔도 원본값도 동일하게 변경됨 { a: 1, b: { c: 4 } }
//할당연산자 

const 점심차려이각박한세상속에서 = { name : '하하' };

const 정발산기슭곰발냄새 = 점심차려이각박한세상속에서;

console.log(점심차려이각박한세상속에서 ===  정발산기슭곰발냄새);  //true

 



배열의 얕은 복사 

//스프레드연산자

const original = [1, 2, 3];
const copy = [...original];
//slice()하기 

const original = [1, 2, 3];
const copy = original.slice();
==========
//slice()했을 때 내부에 참조데이터가 있다면 원본과 상태가 공유됨

let arr = [ { name : '조', age: 100 }, { name : '김', age: 100 } ];
let arrCopy = arr.slice();  //얕은 복사

console.log(arrCopy);       //[{name: '조', age: 100}, {name: '김', age: 100}]

arrCopy[0].age = 30;   //복사본 변경

console.log(arr);      //{name: '조', age: 30}, {name: '김', age: 100}]
//원본에 복사본의 변경사항이 반영됨
//concat()하기

const original = [1, 2, 3];
const copy = original.concat();
//from()하기 

const original = [1, 2, 3];
const copy = Array.from(original);