객체의 확장을 막는 방법

IT공부중·2021년 2월 8일
0

JavsScript

목록 보기
14/22
post-thumbnail

객체의 확장을 막는 방법으로 가장 유명한 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);

이렇게 실행을 하면

하나도 바뀌지 않고 불변하게 된 것을 볼 수 있었다!!

profile
3년차 프론트엔드 개발자 문건우입니다.

0개의 댓글