불변데이터를 만들기위한 배경지식

junamee·2021년 8월 23일
0

자바스크립트

목록 보기
7/11

가변성과 불변성

  • 불변성: 기본형 데이터에 해당한다. 변수의 데이터를 바꾸는 방법은 데이터를 변경하는 것이 아니라, 데이터를 새로 만들어 할당하는 동작을 통해 발생한다. 만들어진 데이터는 가비지컬렉팅을 당하기 전까지 변하지 않고 존재한다.

    • 변수영역 : 변경되고자 하는 데이터가 새로 생성되면 새로 생성된 주솟값을 갖게된다.
    • 데이터 영역 : 변수에 할당하고자 하는 데이터가 저장된 곳, 데이터 하나가 저장된 메모리 공간이며 이 메모리 공간은 생성/삭제만 가능하지 변경되지 않는다.
  • 가변성: 참조형 데이터의 기본성질에 해당하나, 방법에 따라 불변성을 유지하게 할 수 있다. (*Object.defineProperty, Object.freeze)
    기본형 데이터와의 차이는 객체의 변수(프로퍼티)영역이 별도로 존재한다는 점이다.
    (중첩객체를 예로들었을 때 아래와 같이 동작한다.)

    • 변수영역 : 객체자체의 주소값을 가지고 있다.
    • 데이터 영역 : 객체변수에 할당된 데이터도 일반변수의 데이터 메모리 공간을 사용한다.
    • 객체 변수 영역 : 데이터 영역의 값은 불변성을 띄지만, 객체변수의 값이 달라질 땐 데이터영역에 새로운 메모리를 생성하고 해당 주소값을 객체변수 영역이 변경된 주솟값으로 바꿔버리게 된다.

즉, 변수영역의 변수는 동일한 주솟값을 갖고있지만 참조하고있는 객체변수영역의 주소값이 변경되면서 가변성을 띄게 된다. mmutable(가변적인)값이라고 할 수 있다.


1) 객체의 프로퍼티를 변경하는 경우

var a = 10
var b = a
b=30
a === b   //false : a와 b는 서로 다른 주솟값을 갖게 되므로 둘은 다르다.
var obj1 = {c:10, d: 'hi'}
var obj2 = obj1
obj2.c=30
obj1 === obj2  //true : obj1과 obj2는 동일한 주솟값을 바라보므로 수정 후에도 동일하다.

2) 객체 자체를 새로운 객체로 변경한 경우

var obj1 = {c:10, d:'hi'}
var obj2 = obj1
obj2 = {c:30, d:'hi'}
obj1 === obj2 //false : 데이터 영역에 새로운 객체를 저장하는 주소영역이 생성되고
	      //obj2의 주소값이 새로운 주소로 변경된다.

결론: 객체의 가변성은 참조형 데이터를 새로운 데이터로 인식시키는 경우에는 해당되지 않고
내부 프로퍼티를 변경할 때 성립한다.

작업 중, 값으로 전달받은 객체에 변경을 가하더라도 원본 객체는 변하지 않아야 하는 경우가 있다. 데이터의 불변성을 확보해야 하는 상황이므로 새로운 객체를 만들어 재할당되도록 한다.


얕은 복사와 깊은 복사

  • 얕은 복사 fn: copyObject
    : 중첩객체를 예로 들면 바로 아래단계의 키, 밸류들을 복사하는 것이 얕은 복사이다.
    : 얕은 복사에서는 중첩 객체 내부까지는 복사하지 못한다.
    : 중첩 객체 내부는 복사한 대상의 주솟값만 복사한 상태
    => 원시값이 변경되면 사본이 변경되고, 사본이 변경되면 원시값도 바뀌게 된다.

  • 깊은 복사 fn: copyObjectDeep, JSON
    : 깊은 복사는 중첩된 객체의 내부까지 복사하는 것을 말한다.

var menu = {
  name: "bibimbap",
  recipe: {
    ingredient: "rice, eggs, etc...",
    source: "kochujang",
  },
};

function copyObject(obj) {
  var result = {};
  for (var key in obj) {
    result[key] = obj[key];
  }
  return result;
}

var menu2 = copyObject(menu)

/* (참고)
function copyObject(obj){
	return { ... obj}
    }
위와 같이 새로운 객체를 반환하는 식으로도 구현가능한데,
얕은 복사에서만 유효하고, 중첩객체가 있는 경우 내부 프로퍼티를 인식하지 못해
undefined를 반환하는 에러가 있다.
*/

1) 얕은복사 - 객체내부 속성을 변경한 경우

menu.name = 'bulgogi'
menu.name === menu2.name //false

2) 얕은복사 - 중첩객체 내부 속성을 변경한 경우

menu2.recipe.source = 'chilli source'
menu.recipe.source === menu2.recipe.source // true

: 사본과 원본 모두 변경되었다. 내부 객체는 복사한 객체의 주소값을 참조하고 있는 상태이기 때문이다. 중첩 객체의 내부까지 깊은 복사로 만들어 주려면 내부프로퍼티에 대하여 copyObject를 한번 더 진행한다.

menu2.recipe = copyObject(menu.recipe)
menu2.recipe.source = 'chilli source'
menu.recipe.source === menu2.recipe.source // false

3-1) 깊은 복사
내부 객체까지 반영하여 deepcopy를 하는 함수는 다음과 같이 구현할 수 있다.

function copyObjectDeep(target){
  var result ={}
  if(typeof target === 'object' && target !==null){ 
    	//null의 타입이 'object'이기 때문에 조건을 추가하였다.
    for(var prop in target){
      result[prop] = copyObjectDeep(target[prop]) 
      //target이 객체일 때, 한번더 내부 프로퍼티를 탐색함
    }
  } else {
    result = target
  }
  return result;
}
var menu = {
  name: "bibimbap",
  recipe: {
    ingredient: "rice, eggs, etc...",
    source: "kochujang",
  },
};

var menu2 = copyObjectDeep(menu)
menu2.recipe.source = '비빔면양념'
menu.recipe.source === menu2.recipe.source //false 

* 타겟 속성을 갖는 객체를 생성하는 Object.assign, Object.create 역시 얕은 복사를 한다.

const target = { a: 1, b:{c: 3, d: 4}};
const test = Object.create(target)

test.a='바보'
test.a === target.a    //false
target.b.c = 100
test.b.c === target.b.c //true : 중첩객체내부 복사하지 못함

3-2) 깊은 복사 - JSON
target 객체를 문자화했다가 다시 객체화 시킨다.

var copyObjectViaJSON = function(target){
  return JSON.parse(JSON.stringify(target))
}

const target = { a: 1, b:{c: 3, d: 4}};

var test = copyObjectViaJSON(target)
target.b.c='안뇽'
target.b.c === test.b.c  //false

마무리 & 주의하기🧧
참조형 데이터를 수정해야 하는 경우 원본 데이터까지 변경되지 않도록 주의해야하는 경우가 있다.
이럴 때, 원본을 복사한 데이터를 수정대상으로 삼아야 하는데
만일 데이터의 깊이가 깊지 않다면 Object.assign이나 create 를 통해 간단하게 복사해 사용할 수 있지만
내부에 또 참조형 데이터가 있다면 꼭 깊은 복사를 해야한다.

  • 참고서적: 코어자바스크립트
profile
아티클리스트 - bit.ly/3wjIlZJ

0개의 댓글