여태까지 자바스크립트에서는 데이터의 불변성을 지켜야 한다는 얘기를 많이들으면서 프로그래밍 공부를 해왔다. 하지만 불변성을 지켜야 한다는 의미는 정확하게 모른채 데이터가 있으면 map을 돌려서 데이터를 가지고 필요한 부분에 적용을 했었는데 이번에는 불변성에 대해 공부를 해보았다.
자바스크립트에서의 불변성이란 객체가 생성된 이후 그 상태를 변경 할 수 없는 것을 의미한다.
과거 ES6이전에 자바스크립트에서 일반적으로 변수를 선언할 때는 var
키워드를 사용했었는데 이는 값이 변할 수 있고 참조하는 변수의 명도 변해도 에러를 도출하지 않고 그대로 사용할 수 있어 원본훼손에 대한 위험성이 매우 높았으나 ES6이후 let
과 const
의 도입부터 이를 방지할 수 있게 되었다.
let
키워드나const
키워드를 활용해 변수명을 선언해 주면 된다.
먼저 값에는 원시형 타입과 객체형 타입이 존재한다.
원시형 타입을 제외하곤 나머지는 모두 객체형 타입이라고 생각하면 된다.
let a = 1;
let b = a;
a = 2;
console.log(a === b); // false
a라는 변수에 1이라는 값을 할당하고 b라는 변수는 a라는 변수와 같은 값을 할당하고나서 a변수의 값을 2로 바꾸고 비교했을때 false가 나오는 이유는 무엇일까?
우리가 눈으로 보고 있을 때는 b가 a의 값을 공유하기 때문에 a의 값이 바뀌어도 b도 같이 변한다고 생각 할 수 있다.
만약 b에게 a를 할당해주는 시점에서 비교를 한다면 true
가 나온다. 하지만 도중에 a의 값을 재할당해주었는데 여기서 false
가 나오는 시점이다.
변수를 선언하고 값을 할당하는 과정은 컴퓨터의 측면에서 봤을 때 a라는 변수에 1이라는 값이 저장되는 것이 아니다.
1이라는 값이 컴퓨터 메모리에 저장이 되고 a라는 변수는 1이 저장되어 있는 메모리의 주소를 바라보게 되는 것이다. 이를 a라는 변수가 1이라는 값을 참조(Reference)한다라고 한다.
코드를 순서대로 설명하자면
1. a라는 변수는 1이라는 메모리 주소를 참조하고 있다.
2. b라는 변수는 a가 참조하는 1이라는 메모리 주소를 같이 참조하고 있다.
3. a 변수가 참조하던 메모리 주소가 2라는 값의 메모리 주소를 참조하고 있다.(a = 2;)
그래서 최종적으로 a와 b를 비교했을 때 b는 1의 메모리 주소를 보고 있고 a는 바뀐 2의 메모리 주소를 바라보고 있기 때문에 a과 b를 비교하면 false
가 나오는 것이다.
let obj1 = {
name: 'Park'
}
let obj2 = {
name: 'Park'
}
console.log(obj1 === obj2) // false
console.log(obj1.name === obj2.name) // true
위의 코드를 보자마자 1번 객체와 2번객체를 비교했을 때 false
가 왜 나오는지 의아할 수 있다. 왜냐하면 객체안에 있는 값이 동일한데 이 둘을 비교했을 때 false
가 나왔기 때문이다.
객체형 타입은 그 안의 값이 무엇이 있던간에 객체 자체에 메모리 주소가 할당된다. 그래서 1번객체와 2번객체는 객체자체의 메모리 주소가 다르기 때문에 비교를 하게 되면 false
가 나오게 되는 것이다.
반면 객체내부의 값을 비교했을때는 true
가 나오게 되는데 이는 객체 자체에 할당된 메모리 주소가 있고 또 객체 내부의 속성값들끼리 다른 메모리 주소를 할당되기 때문이다.
결론적으로 객체 껍데기 자체로는 메모리 주소가 다르기 때문에 객체 자체를 비교하는 것은 그 내부의 값이 어떻던간에 false
가 도출되게 되는 것이고 속성값은 메모리 주소가 동일하기 때문에 값을 비교했을 때는 true
가 나오게 되는 것이다.
객체형 타입의 메모리 주소 할당 특성에 의해 원본을 훼손할 가능성이 원시형 타입보다 훨씬 높아지게 된다. 그래서 불변성을 유지해주는 방법이 있는데 그 중 가장 대표적인 방법 몇 가지를 찾아봤다.
Object.assign
let obj1 = { name: 'Park' }
let obj2 = Object.assign({}, obj1);
obj2.name = "Kim"
console.log(obj1) // { name: 'park' }
console.log(obj2) // { name: 'Kim' }
Object.assign
은 객체를 복사하는 작업이다.
기존 객체를 복사해서 새로운 변수에 할당 해주는 것으로 위의 코드 처럼 원본은 변하지 않으면서 새로운 객체가 생성되는 것을 확인 할 수 있다.
Object.freeze
let obj1 = { name: 'Park' }
Object.freeze(obj1);
obj1.name = 'Kim';
console.log(obj1); // { name: 'Park' }
Object.freeze
는 freeze의 뜻처럼 원본을 얼려버려서 다른 곳에서 못건드리게 막아버리는 것이다. 1번 객체를 freeze시키고 속성값을 바꿔도 에러는 나오지 않지만 1번 객체가 불변성을 유지해서 원본을 도출해내게 되는 것이다.
var
, let
, const
를 공부했을 때 var
는 변수명도 값도 자유자재로 변하는게 가능하고 let
은 값은 변할 수 있으나 변수명은 제한적이고 const
는 그 두 가지를 아예 못하게 막는것으로 이해하고 있을 것이다.
위에서 메모리 할당과 참조를 설명했던 부분의 관점에서 보면 const
는 상수의 개념으로 const
로 변수명을 선언하고 값을 할당하는 것은 const
키워드가 붙은 변수명은 오로지 자신에게 할당된 메모리 주소 하나만 바라보게끔하고 참조하는 이름 자체도 그 메모리 주소에게 아예 고정을 시켜버리는 것으로 생각하면 될 것 같다.