리팩터링(소프트 코딩)을 하던 도중 아래와 같이 Constant를 immutable화 함으로써 안정성을 확보하였다.
const END_POINT = Object.freeze({
HELLO: '/api/hello',
});
const ERROR = Object.freeze({
ENONT: 'ENOENT',
ENONT_MESSAGE: 'Unusable API EndPoint',
PROTOCOL_MESSAGE: 'Protocol Error',
});
const API = Object.freeze({
END_POINT: END_POINT,
ERROR: ERROR,
});
??? : 저기요 선생님. 코드는 이렇게 깔끔해야 좋습니다.
const API = Object.freeze({
END_POINT: {
HELLO: '/api/hello',
},
ERROR: {
ENONT: 'ENOENT',
ENONT_MESSAGE: 'Unusable API EndPoint',
PROTOCOL_MESSAGE: 'Protocol Error',
}
});
하나(은행 아님)는 알고 둘은 모르는 소리이다.
위처럼 객체가 중첩된 상태에서 Object.freeze()를 사용하면 depth가 1
인 값(데이터 영역)들에 대해서만 불변성이 확정
되며 depth가 2이상
의 참조값은 값이 여전히 가변적
이다.
따라서 각 Layer별로 object를 freeze하고 상위 객체에서 이를 참조한 뒤, 다시 freeze를 하는 방식으로 구현한 것이다.
문제는 nested Object의 depth가 깊어질수록 위의 과정이 복잡해져 이를 해결하는 재귀함수를 작성해야 한다는 의미이다.
왜 Object.freeze()는 depth가 1까지만 적용
될까?
문득 생각해보면, 참조 데이터(Object, Array)
에 적용되는 메서드들은 대부분 depth가 1
이다. spread 연산자를 통한 deepCopy 또한 1만큼만 복사가 되고, 하위 depth는 참조값을 그대로 유지한다.
왜 이런 문제가 발생할까?
JS의 메모리 구조
를 살펴보자.
메모리는 변수 영역
과 데이터 영역
으로 구분된다.
불변값(string, number, boolean, Symbol, undefined, null)을 저장하는 경우 변수영역은 식별자
와 불변값이 담겨있는 메모리의 주소
를 저장한다. 따라서 해당 메모리 주소로 가면 값을 참조할 수 있게 된다.
const myObject = {
score: 100
}
한 편, object의 경우 식별자의 메모리 주소(변수 영역)에 담겨있는 object의 데이터 주소를 따라가보자.
@5002번(데이터 영역)에는 값이 아닌 또 다른 변수영역들
이 담겨있다.
const
키워드는 식별자의 변수영역의 재할당을 막는 처리
였다면, Object.freeze()
는 데이터 영역의 변경을 막는 처리
를 진행하는 것으로 추론할 수 있다.
const obj = Object.freeze({1:2});
a[1] = 3;
console.log(a) //{1: 2}
a[2] = 3;
console.log(a) //{1: 2}
요컨대 해당 질문은 아래와 같이 간단하게 요약할 수 있다.
Object.freeze와 Array(참조 데이터)의 스프레드 연산자는 "해당 변수영역의 데이터 영역만" 적용되는가?"
우선은 맞다고 판단했다.
참조 데이터(객체)의 변수 영역은 데이터 영역의 주소가, 해당 데이터 영역의 주소에는 각 key와 value의 쌍으로 이루어진 변수 영역의 메모리 주소들이 담긴다.
참조 데이터를 freeze하더라도 해당 식별자(변수 영역에 저장되어있는 데이터 영역의 메모리 주소)의 데이터 영역만을 freeze하기 때문에 key에 대한 value값이 다시 변수영역인 참조 데이터의 경우 (nested Object나 value값이 Array인 경우), 해당 변수 영역에 대한 데이터 영역의 불변성을 보장할 수 없다. 따라서, Object.freeze()는 depth가 1을 초과하는 참조 데이터에 대한 값의 불변성을 보장하지 못하게 되는 것이다. (spread연산자도 동일한 논리)
추론과 근거에 대한 정리는 끝냈지만, 아직 답에 대한 확정을 내지는 못했다.
JS를 만든 Netscape에 문의하려 했으나, 기술에 대한 contact us를 유료로 열어두어, 네이버 개발팀에 메일을 보내게 되었다.
메일 내용은 아래와 같다.
안녕하십니까 네이버 개발자 선생님들. 저는 Javascript를 공부하는 학생입니다.
JS학습 과정에서 하나의 궁금증이 발생했습니다.
자료를 찾아보며 자체적 결론을 내렸으나, 이에 대한 추론이 맞는지 확인할 길이 없어 답답함에 문의드립니다.
Spread Operator과 Object.freeze()는 해당 식별자의 데이터 영역에만 적용되는가?입니다.
Spread Operator과 Object.freeze()는 각각 적용되는 depth가 공통적으로 1입니다. 무언가 공통점이 있는 것 같다는 생각에 메모리적 관점으로 문제를 접근하게 되었습니다.
Spread Operator와 Object.freeze()는 전부 "참조 타입의 데이터"에 사용됩니다. 이것들은 참조 데이터가 할당된 식별자의 해당 데이터 영역에만 적용될 뿐이지, 해당 데이터 영역의 변수영역에 무엇이 들어있는지는 신경을 쓰지 않는다고 추론했습니다. 적용되는 depth가 1이기 때문입니다.
즉, 위 함수들이 적용되는 부분은 해당 참조 타입 데이터 식별자의 "데이터 영역 뿐"이라고 생각했습니다.
혹시 답을 알고계시다면, 답변해주시면 감사하겠습니다!!!
아래는 조금 더 보시기 쉽게 풀어 쓴 글입니다.
질문 내용을 이해하셨다면 아래의 내용은 보실 필요가 없습니다. :)
1. 궁금증이 발생한 계기
궁금증이 발생한 계기는 다음과 같았습니다.
2. 궁금증 발생.
둘 다 참조 데이터 타입이고 둘 다 depth가 1이네?? 왜그럴까??
3. 추론
Object.freeze()의 예.
freeze()를 걸어준 해당 depth만 적용되고 하위 depth는 적용이 안되는 것을 확인!
(추론) Object.freeze() 메서드가 해당 참조 타입 데이터에 대한 데이터 영역만 고정하는 방식이라면 납득이 가능하다!
그렇다면, 데이터 영역에 들어있는 변수영역(key, value)들의 데이터 영역이 또 다른 참조 타입 데이터(nestedObject나 고차원 배열)인 경우, 이에 대한 불변성을 확정하지 못하는 방식으로 동작하는 것이겠구나!
Spread 연산자도 똑같은 참조 타입 데이터이니, 위의 이유와 같겠군!이라고 추론했습니다.
4일째 고민중입니다 호호
답이 아니더라도 위의 추론에 대한 의견을 듣고싶습니다 :)
감사합니다!
네이버 선생님들의 답변은 오지 않았다 ㅠㅠ 대신, 주변 대기업 개발자 지인분과 이야기를 하는 도중, 해당 부분에 대해 의견을 나누는 시간이 있었고, 위의 추론 및 결론 과정이 충분히 타당하다고 답변을 해주셨다. 그 근거로 C계열 언어의 동작 원리와 컴퓨터 구조 및 동작원리에 대한 설명을 해주셨다. 역시 CS적 지식이 아주 중요한 것 같다.