> [!date] published: 2023-04-17 ## const `const`로 선언한 객체는 재할당은 불가능하지만, 객체 내의 속성의 추가/삭제나 속성 값의 변경은 가능합니다. ```js const sample = { name: "sample", age: 50, }; // 0. 속성 값 수정 시도 sample.name = "choonsik"; print("sample", sample); // 1. 속성 추가 / 삭제 시도 sample.nickname = "goguma"; sample.address = "Seoul"; delete sample.address; print("sample", sample); // 2. 재할당 sample = { name: "Ryan", age: 20, }; ``` ``` // 0. 속성 값 수정 시도 => 가능 =========== sample =========== { name: 'choonsik', age: 50 } // 1. 속성 추가 / 삭제 시도 => 가능 =========== sample =========== { name: 'choonsik', age: 50, nickname: 'goguma' } // 2. 재할당 => 불가 sample = { ^ TypeError: Assignment to constant variable. ``` ## Object.freeze() [Object.freeze() - JavaScript | MDN](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) ### 객체 동결 `Object.freeze()`는 객체를 동결시킵니다. 동결된 객체에는 속성을 추가하거나 삭제할 수 없으며 속성의 값을 변경할수도 없습니다. (속성 추가, 삭제, 변경의 시도는 무시되거나 TypeError를 발생시킵니다.) 하지만 객체가 담긴 변수의 재할당은 변수가 선언된 키워드에 따라 가능해지므로 `let`, `var`로 선언된 변수는 재할당이 가능하여 값이 바뀔 수 있습니다. ```jsx let sample = { name: "sample", age: 50, }; // 0. 속성 값 변경 시도 sample.name = "choonsik"; print("sample", sample); // 1. 객체 동결 Object.freeze(sample); console.log("++++++++++++", "Object.freeze(sample)", "++++++++++++\\\\n"); // 2. 속성 값 변경, 속성 추가 시도 sample.age = 20; // 속성 값 변경 sample.nickname = "goguma"; // 속성 추가 print("sample", sample); // 3. 재할당 sample = { name: "Ryan", age: 20, }; print("sample", sample); ``` ``` // 0. 속성 값 변경 시도 => 변경 가능 =========== sample =========== { name: 'choonsik', age: 50 } ++++++++++++ Object.freeze(sample) ++++++++++++ // 속성 값 변경, 속성 추가 시도 => 무시됨 (strict mode에선 TypeError) =========== sample =========== { name: 'choonsik', age: 50 } // 3. 재할당 => 가능 (새로운 객체) =========== sample =========== { name: 'Ryan', age: 20 } ``` ### 얕은 동결 `Object.freeze()` 메서드로 객체를 동결하면 그 동결의 대상은 직속 속성만이 됩니다. (바로 하위의 속성들) 따라서 만약 직속 속성의 값이 객체라면, 그 객체는 동결의 대상이 되지 않아서 속성의 추가, 삭제, 값이 변경이 가능합니다. ```jsx let sample = { name: "choonsik", age: 50, address: { city: "Seoul", country: "Korea", }, }; print("sample", sample); // 0. 동결 Object.freeze(sample); console.log("++++++++++++", "Object.freeze(sample)", "++++++++++++\\\\n"); // 1. 직속 속성 변경 시도 sample.name = "goguma"; print("sample", sample); // 2. 직속 아닌 속성 변경 시도 sample.address.city = "Seongnam"; print("sample", sample); ``` ``` =========== sample =========== { name: 'choonsik', age: 50, address: { city: 'Seoul', country: 'Korea' } } ++++++++++++ Object.freeze(sample) ++++++++++++ // 1. 직속 속성 변경 시도 (name) => 무시됨 =========== sample =========== { name: 'choonsik', age: 50, address: { city: 'Seoul', country: 'Korea' } } // 2. 직속 아닌 속성 변경 시도 (address.city) => 변경됨 =========== sample =========== { name: 'choonsik', age: 50, address: { city: 'Seongnam', country: 'Korea' } } ``` 만약에 객체를 완전히 불변하게 만들고 싶다면 재귀적으로 동결시키는 방법이 있습니다. (깊은 동결) 하지만 이 경우에는 순환을 포함하지 않는지, 동결하면 안되는 객체를 동결하는 경우가 없는지를 확인해야 합니다. ```jsx const deepFreeze = (obj) => { // 객체가 아니라면 동결하지 않고 그대로 반환 if (obj === null || typeof obj !== "object") return obj; // 객체 속성 각각을 동결 Object.keys(obj).forEach((key) => { deepFreeze(obj[key]); }); // 객체 동결 return Object.freeze(obj); }; const sample = { name: "choonsik", age: 50, address: { city: "Seoul", country: "Korea", }, }; print("sample", sample); // 0. 동결 deepFreeze(sample); console.log("++++++++++++", "deepFreeze(sample);", "++++++++++++\\\\n"); // 1. 직속 속성 변경 시도 sample.name = "goguma"; print("sample", sample); // 2. 직속 아닌 속성 변경 시도 sample.address.city = "Seongnam"; print("sample", sample); ``` ``` =========== sample =========== { name: 'choonsik', age: 50, address: { city: 'Seoul', country: 'Korea' } } ++++++++++++ deepFreeze(sample); ++++++++++++ // 1. 직속 속성 변경 시도 (name) => 무시됨 =========== sample =========== { name: 'choonsik', age: 50, address: { city: 'Seoul', country: 'Korea' } } // 2. 직속 아닌 속성 변경 시도 (address.city) => address도 동결객체이므로 무시됨 =========== sample =========== { name: 'choonsik', age: 50, address: { city: 'Seoul', country: 'Korea' } } ``` ## Object.seal() [Object.seal() - JavaScript | MDN](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) `Object.seal()` 메서드는 객체를 "밀봉"합니다. 밀봉했으므로 새로운 속성을 추가하거나 삭제할 수 없습니다. 하지만 그 객체를 완전히 얼려서 값 또한 변경할 수 없게 만드는 `Object.freeze()` 메서드와는 달리 속성의 값은 변경할 수 있습니다. ```jsx let sample = { name: "sample", age: 50, }; // 0. 속성 값 변경, 속성 추가 시도 sample.name = "choonsik"; sample.nickname = "goguma"; print("sample", sample); // 1. 객체 밀봉 Object.seal(sample); console.log("++++++++++++", "Object.seal(sample)", "++++++++++++\\\\n"); // 2. 속성 값 변경 시도 sample.age = 30; print("sample", sample); // 3. 속성 추가, 삭제 시도 sample.address = "seoul"; // 속성 추가 delete sample.nickname; // 속성 삭제 print("sample", sample); ``` ``` // 0. 속성 값 변경, 속성 추가 시도 => 가능 =========== sample =========== { name: 'choonsik', age: 50, nickname: 'goguma' } ++++++++++++ Object.seal(sample) ++++++++++++ // 2. 속성 값 변경 시도 => 가능 =========== sample =========== { name: 'choonsik', age: 30, nickname: 'goguma' } // 3. 속성 추가, 삭제 시도 => 불가능 (무시됨) (strict mode에서는 TypeError 발생) =========== sample =========== { name: 'choonsik', age: 30, nickname: 'goguma' } ``` ## 활용(?) 불변 객체를 만드는 이유는 여러가지가 있겠지만 제가 불변 객체를 만들기 위한 방법을 찾아본 이유는 JavaScript에서 최대한 TypeScript의 리터럴 타입같은 것을 사용해보고 싶어서입니다. 따라서 `const`와 `Object.freeze()` 메서드의 조합으로 리터럴 타입 비슷한 것을 만들어서 사용해보았습니다.. ```jsx export const loginResultType = { SUCCESS: "success", FAIL: "fail", }; Object.freeze(loginResultType); ``` `const`로 할당한 객체이므로 재할당이 불가능하고, 속성의 값이 객체가 아닌 객체이므로 `Object.freeze`만으로도 불변 객체를 만들 수 있었습니다. (하지만 타입스크립트...👍) ## 🙇 참고 [[JavaScript]const와 Object.freeze()의 차이](https://developer-talk.tistory.com/243) [[Javascript] const와 Object.freeze()를 이용하여 Immutable한 Object 만들기](https://yorr.tistory.com/21) [Object.freeze vs Object.seal - 불변성과 관련된 두 가지 네이티브 메서드](https://ui.toast.com/posts/ko_20220420) [Object.freeze() - JavaScript | MDN](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) [Object.seal() - JavaScript | MDN](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/seal)