객체의 확장을 막는 방법으로 가장 유명한 Object.freeze 가 있다. 하지만 Object.freeze도 깊은 freeze는 하지 못한다. 그리고 찾아보니 freeze 말고 다른 메소드로도 객체의 확장을 막는 방법들이 있었다.
이를 이해하기 위해서는 먼저 객체의 프로퍼티에 줄 수 있는 속성들을 알면 좋다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
configurable이 false이면 해당키를 제거하지 못 하고, writable 속성을 제외한 나머지 속성들을 변경하지 못 한다.
enumerable은 for..in이나 Object.keys(), 그냥 console.log 등에 노출될지 말지를 결정한다.
writable은 재할당이 가능한지 아닌지의 여부.
var o = {}; // 새로운 객체 생성
// 데이터 속성 서술자와 defineProperty로
// 새로운 속성을 추가하는 예시
Object.defineProperty(o, 'a', {
value: 37,
writable: true,
enumerable: true,
configurable: true
});
객체의 프로퍼티를 넣을 때 이런식으로 넣어줄 수 있는 것이다.
어떻게 확장을 막을 수 있는가? 모든 객체에는 [[Extensible]] 이라는 숨김 프로퍼티가 있는데 이거를 false로 바꾸면 확장이 불가능하게 된다.
다음과 같은 메소드들은 이러한 [[Extensible]]과 객체프로퍼티의 속성들을 바꿈으로써 확장을 막는다.
Object.preventExtensions 는 [[Extensible]] 만 false로 바꾼다.
Object.isExtensible(obj) 로 확인 가능. 확장 가능한지의 여부.
Object.seal은 [[Extensible]]를 false로 바꾸고, 각 속성의 configurable까지 false 로 바꾼다. 그래서 속성 추가 및 속성 삭제할 수 없으나, writable이 true이기 때문에 속성의 값은 바꾸는 것이 가능하다.
Object.isSealed 로 확인 가능.
Object.freeze는 writable: false 까지 해주는 것. 그래서 확장 불가능, 삭제 불가능, 재할당 불가능하게 된다. 하지만 그 안의 객체는 가능하다. 그래서 deep freeze를 해주어야한다.
Object.isFrozen 으로 확인가능.
그럼 실제 불변성을 지키게 하는 코드를 확인해본다.
const person = {
name: '문건우',
job: '학생',
};
console.log(person);
person.job = '프론트 개발자';
console.log(person);
바뀌는 것을 확인할 수 있다.
person에 할당된 객체의 주소값을 못 바꾸는 것이지, 객체 내부는 또 다른 주소를 가리키고 있어서 바꿀 수 있다.
그럼 변경 못 하게 만들려면 어떻게 해야할까?
Object.freeze를 사용하면 된다.
const person = {
name: '문건우',
job: '학생',
};
Object.freeze(person);
console.log(person);
person.job = '프론트 개발자';
console.log(person);
결과가 바뀌지 않는 것을 알 수 있다.
그러면 객체 안의 또다른 객체는 어떻게 될까?
const person = {
name: '문건우',
job: '학생',
skill: ['자바스크립트', '타입스크립트'],
};
Object.freeze(person);
console.log(person);
person.job = '프론트 개발자';
person.skill.push('리액트');
console.log(person);
이런식으로 job은 바뀌지 않았지만, skill은 바뀌는 것을 확인할 수 있다.
Object.freeze를 사용하더라도 객체 안의 객체는 수정할 수 있다.
그래서 Object.freeze를 재귀적으로 돌면서 deep freeze를 해주어야한다.
function deepFreeze(target) {
if (target === null || typeof target !== 'object') {
return;
}
Object.keys(target).forEach((key) => {
deepFreeze(target[key]);
});
Object.freeze(target);
}
deepFreeze는 간단하게 이렇게 구현할 수 있다.
target === null을 확인하는 이유는 typeof null 이면 object로 나오기 때문이다.
const person = {
name: '문건우',
job: '학생',
skill: ['자바스크립트', '타입스크립트'],
};
deepFreeze(person);
console.log(person);
person.job = '프론트 개발자';
person.skill.push('리액트');
console.log(person);
그 후 이 deepFreeze를 적용한 이런식의 코드를 실행하게 되면
다음과 같은 에러가 나타나는걸 확인 할 수 있다.
다른 방법은 없을까?
Object.defineProperty를 사용하면 된다.
const person = {
name: '문건우',
job: '학생',
skill: ['자바스크립트', '타입스크립트'],
};
Object.defineProperty(person, 'test', {
value: '테스트입니다',
writable: false, // 수정할 수 없게 한다.
enumerable: true, // 적어주지 않으면 기본값 false라 person을 log 해도 보이지 않는다.
});
console.log(person);
console.log(person.test);
person.job = '프론트 개발자';
person.skill.push('리액트');
person.test = '바뀌는지 확인';
console.log(person);
그럼 Object.defineProperty로 writable false로 바꾼 속성은 바뀌지 않는 것을 볼 수 있다.
하지만 객체를 넣고 똑같이 적용을 해보니 객체 자체는 변화가 안되지만 객체의 속성은 변화가 되는 것을 확인할 수 있었다. 그래서.. deepFreeze를 이걸로도 만들어보려고 했는데..!
이것만 써서는 안의 객체에 push를 막을 수 없었다..!!
그래서 Object.seal를 사용해보기로 했다.Object.seal 은 객체를 밀봉한다. 객체를 밀봉한다는 것은 새로운 속성을 추가할 수 없고, 현재 존재하는 모든 속성을 설정 불가능(configurable: false, 속성을 제거하지 못 하고, writable을 제외한 다른 속성값을 바꾸지 못 하게 한다.) 상태로 만드는 것이다. 하지만 쓰기 가능한 속성의 값은 밀봉 후에도 변경을 할 수가 있다.(이것이 Object.freeze() 와의 차이.)
즉, Object.seal을 하면 Object.defineProperty로 writable 이외에는 수정 불가 상태가 된다. 그리고 객체에 새로운 속성을 추가할 수는 없지만, 이미 있는 값은 수정할 수 있다.
그럼 이미 있는 값도 수정 못 하게 Object.defineProperty로 writable을 false로 바꿔주면 될 것 같다.
예시를 보면
const person = {
name: '문건우',
job: '학생',
skill: ['자바스크립트', '타입스크립트'],
};
Object.seal(person);
console.log(person);
person.job = '프론트 개발자';
person.skill.push('리액트');
person.test = '123';
console.log(person);
이렇게 해서 실행을 해보면
이렇게 나오는 것을 볼 수 있다. 그러면 Object.seal을 썼음에도 불구하고 job처럼 원래 있던 프로퍼티는 바꿀 수 있고, 그 안에 있는 skill이라는 객체는 seal이 안 돼있어서 추가 할 수 있는걸 볼 수 있다.
하지만 person.test = '123'; 같이 새로운 프로퍼티를 추가하는 것은 막혀있다.
그럼 deepFreeze를 해보면..
function deepFreeze(target) {
if (target === null || typeof target !== 'object') {
return;
}
Object.keys(target).forEach((key) => {
Object.defineProperty(target, key, {
value: target[key],
writable: false,
enumerable: true,
});
deepFreeze(target[key]);
});
Object.seal(target);
}
이런식으로 deepFreeze 코드를 만들었고,
const person = {
name: '문건우',
job: '학생',
skill: ['자바스크립트', '타입스크립트'],
};
deepFreeze(person);
console.log(person);
person.job = '프론트 개발자';
person.skill.push('리액트');
person.test = '123';
console.log(person);
이걸 실행을 하면 에러가 나는 것을 확인할 수 있다.
const person = {
name: '문건우',
job: '학생',
skill: ['자바스크립트', '타입스크립트'],
};
deepFreeze(person);
console.log(person);
person.job = '프론트 개발자';
person.skill = '456';
person.test = '123';
console.log(person);
이렇게 실행을 하면
하나도 바뀌지 않고 불변하게 된 것을 볼 수 있었다!!