[REAL Deep Dive into JS] 16. 프로퍼티 어트리뷰트

young_pallete·2022년 9월 11일
0

REAL JavaScript Deep Dive

목록 보기
16/46
post-custom-banner

🚦 본론

16장 프로퍼티 어트리뷰트.
사실 처음 자바스크립트를 접하는 개발자에게는 추천하고 싶지는 않은 챕터에요. 😭
섣불리 들어갔다가는 괜히 자바스크립트에 대한 체감 난이도를 높일 수 있기 때문입니다.

다만 이 챕터가 필요하지 않다는 것은 아니에요. 다만, 이 챕터를 통해 얻고자 하는 방향성을 명확히 알고 들어가야 합니다.

✅ 본 챕터에 대한 개인적인 목표 설정

일단 프로퍼티 어트리뷰트가 정말로 필요한지에 대해 의문을 가져야 합니다.
여러분이, TodoList를 만들 때 이 프로퍼티 어트리뷰트를 조작할 일이 있었나요? 단언컨대 없었을 것 같아요.

그렇다면 우리는 의문을 가져야 해요. 왜 이 파트가 있어야 할까요? 혹은, 왜 알아야 할까요?
제 생각에는, 짚고 넘어가야 할 부분은 크게 2가지 입니다.

  1. 자바스크립트에서, 프로퍼티는 어떤 사양을 갖고 있는가?
  2. 실제로 이를 조작했을 때, 얻을 수 있는 이점은 무엇일까?

결국 이 프로퍼티에 대한 자바스크립트의 기본값을 알아야 우리가 제대로 프로퍼티를 조작할 수 있습니다.

그렇다면 이를 제대로 인지하면 좋은 점이 무엇일까요? 😮
저는 객체 데이터에 대한 불변성 보장이라고 생각해요. 결과적으로 객체 타입은 pass by reference이며, 따라서 조작에 대한 불변성에 취약하기 때문이죠.

예컨대 API에서 받아온 객체를 그대로 쓴다면, 더 안정적으로 관리하기 위해서는 아예 해당 객체 값을 변경할 수 없게 하면 더욱 안정적인 코드 관리가 가능합니다. 따라서 이러한 기능들을 프로퍼티 어트리뷰트 파트를 통해 우리는 얻어갈 수 있어요! 😆

그럼 이 두 가지를 토대로 이 파트를 재밌게 접근해봅시다 🌈

내부 슬롯과 메서드

둘 다 ECMAScript을 따르며, 자바스크립트 엔진의 구현을 설명하기 위한 용도에요. 이때

  • 내부 슬롯은 의사 프로퍼티이며
  • 내부 메서드은 의사 메서드입니다!

음... 쉽게 생각하면, 우리 객체에서도 값을 프로퍼티(state)와 메서드(action)으로 나누죠? 그런 개념이라고 보면 돼요!

직접 접근 불가

그런데 요 녀석은 직접 접근은 불허합니다. 예측하기로는, 아무래도 일관성 때문인 것 같아요.
본래 ECMAScript는 일관성 있는 브라우저를 위한 자바스크립트 엔진 표준을 명세화시킨 것이죠. 그런데 만약 이러한 프로퍼티 어트리뷰트를 조작한다면? 이러한 표준을 무시해버리는 현상이 발생합니다.

따라서 내부 로직을 접근한다는 것은, 개발자의 의도에 따라 표준과 다른 동작으로 인해 예측하기 쉽지 않은 결과를 초래할 확률이 높습니다.

따라서 이러한 직접적인 제어는 권장하지 않기 때문에, [[prototype]]에 접근하기 위해서는 .__proto__ 등을 통해 간접적으로 접근할 수 있어요! 😉

프로퍼티 어트리뷰트, 프로퍼티 디스크립터 객체

🙆🏻 이름 길어서 짜증나죠? 쉽게 생각하면 돼요.

Attribute가 뭐죠? 속성이죠!
html에서 Attributeclass와 같은 특정 태그가 갖고 있는 열거된 속성들을 이야기합니다.

여기서의 어트리뷰트도 따라서 유추할 수 있어요. 결국 객체의 프로퍼티란 놈이, 어떤 열거할 수 있는 속성들을 갖고 있냐!는 의미입니다.

그렇다면 이를 서술할 수 있어야하겠죠?
자바스크립트에서는 Object.getOwnPropertyDescriptor(Object, key)라는 친구를 통해 알 수 있답니다 🥰

const object = { a: 1 }
console.log(Object.getOwnPropertyDescriptor(object, 'a');
// {value: 1, writable: true, enumerable: true, configurable: true}

쉽게 생각하면, 우리가 알고 있는 프로퍼티의 현재 특성을 알려주는 겁니다.

  • value: [[Value]]를 나타내는 디스크립터 객체 프로퍼티입니다. 이름 그대로 값이에요. (두둥!)
  • writable: [[Writable]]를 나타내는 디스크립터 객체 프로퍼티입니다. 값을 변경할 수 있는지에 대한 여부를 알려줘요.
  • enumerable: [[Enumerable]]에 대한 디스크립터 객체 프로퍼티이고, 반복문으로 열거가 가능한지에 대한 여부를 알려줘요.
  • configurable: [[Configurable]]에 대한 디스크립터 객체 프로퍼티이고, 재정의 및 프로퍼티 삭제, 프로퍼티 어트리뷰트 값 변경에 대한 허용 여부를 알려줘요. (단, [[Value]]의 변경과 [[Writable]]값을 false로 하는 것은 허용한다고 하네요!

🖐🏻 잠깐! 여기서 우리는 1번을 달성할 수 있네요?
자바스크립트 객체 프로퍼티는 다음과 같이 초기값을 통해 명세할 수 있겠어요.

  • 값을 정의한 후 재정의 및 프로퍼티 삭제, 값 변경이 가능해요!
  • 반복문으로 각 프로퍼티들을 순회할 수 있네요! (for (... in ...)
  • 프로퍼티 어트리뷰트에 대한 사양 역시 변경이 가능하군요! (이를 통해 readonly라는 걸 좀이따 만들어 볼 겁니다. 유용해요!)

자, 그러면 우리는 1번을 알아냈군요. 이 파트 생각보다 어렵지 않죠? ㅎㅎ
인생은 거리가 아닌 방향이니, 우리가 무엇을 얻어가야 하는지 방향에 초점을 맞춘다면, 기분좋게 정복할 수 있어요! 💪🏻

자, 그러면 2번인 이점 - 객체 데이터 불변성 보장에 대한 방법을 파악할 차례네요!

접근자 프로퍼티

자체적으로 값을 갖지는 않는데, 다른 프로퍼티 값을 읽거나 저장할 때 쓰는 프로퍼티입니다.
이를 통해 클래스에서 private한 프로퍼티와 유사한 동작을 우리가 직접 만들어낼 수도 있어요.

const productPrices = { 
  pencil: 1000, 
  eraser: 500, 
  get total() {
    return this.pencil + this.eraser
  },
  set total(obj) {
    this.pencil = obj.pencil
    this.eraser = obj.eraser
  }
};

console.log(productPrices) // total 값 없음. {pencil: 1000, eraser: 500}
console.log(productPrices.total) // 접근자 함수를 통해 접근 가능. 1500

productsPrices.total = { pencil: 1200, eraser: 1000 }
console.log(productPrices.total) // 2200

// {enumerable: true, configurable: true, get: ƒ, set: ƒ}
console.log(Object.getOwnPropertyDescriptor(productPrices, 'total'))

얼레? 여기서는 디스크립터 객체에 getset이라는 친구들이 나왔죠?!
이 친구들이 바로 이러한 접근자 프로퍼티에 대한 명세를 나타내요. 이 친구들은 설명에서도 나오듯이, 자체적인 값이 아니라 어떤 특정한 함수 리터럴을 갖고 있죠!
따라서 getset접근자 함수라고 하는데요, 🙇🏻‍♂️ 이 내부 함수 로직을 통해 해당 프로퍼티 값에 접근하여 읽거나 조작하게 되는 거에요!

프로퍼티 정의

그렇다면 우리는 이렇게 접근자 프로퍼티까지 보았어요.
이제 사실상 거의 프로퍼티 어트리뷰트 명세를 모두 살펴보았어요! 🎉
다만 호기심이 많은 분들이라면 아직 해결되지 않은 게 하나 있죠?!

😮 그럼... 아까 말했던 이점인 그 객체에 대한 데이터 조작 변경 방지는 어떻게 해요?

저는 이에 대한 것이 바로 우리가 직접 프로퍼티 특성을 통해 안정적인 객체 데이터 운영을 가능케 합니다!

예컨대 다음과 같이 말이죠.

const immutableObj = { a: 1 }
Object.defineProperty(immutableObj, 'a', {
	value: immutableObj.a,
  	writable: false,
    enumerable: true,
    configurable: false
})

immutableObj.a = 'test'
console.log(immutableObj.a) // { a: 1 }

😮 잠깐! 그런데, 이상하게도 프로퍼티 키를 추가하는 것은 또 가능하네요...?!

immutableObj.b = 2;
console.log(immutableObj) // { a: 1, b: 2 }

맞습니다! 결국 프로퍼티 하나에 대한 어트리뷰트를 재정의해주는 것이기 때문에, 프로퍼티 추가와 같은 완전한 객체 변경을 막지는 못해요.
따라서 자바스크립트는 이를 조작하기 위한 추가적인 메서드를 또 따로 제공합니다!

객체 변경 방지 메서드

그것은 바로 다음 3가지에요!

  • Object.preventExtensions - 프로퍼티 추가만 막아요!
  • Object.seal - 프로퍼티 추가와 삭제, 재정의만 막아요!
  • Object.freeze - 오로지 프로퍼티 읽기만 가능해요!

사실 웬만하면 자바스크립트 표준에 맞게 작성하는 것을 권장해요.
이유는, 위에서 말씀드렸다시피 개발자가 작업하는 표준과 달리 일관성이 깨져버리기 때문입니다!

다만 읽기만 가능한 객체는 쓰는 편이에요.
대표적으로 vue3readonly가 있어요! 참고자료 - vue3 공식문서(readonly)

저는 vue를 많이 쓴 편이라, 이 파트를 보았을 때 이러한 유틸 함수에 대한 호기심이 생길 수밖에 없더라구요.

더군다나, 예제에서 보여준 deepFreeze는 객체만 동결하기 때문에 실제 원시값도 동결하지 못한다는 점이 아쉬웠어요. 따라서 직접! 구현해보았답니다 😉

const readonly = (object) => {
  const allowTypesSet = new Set(['Array', 'Object'])
  if (!allowTypesSet.has(object?.constructor?.name)) return object;

  for (const key in object) {
    object[key] = readonly(object[key]);
  }

  object = Object.freeze(object);

  return object;
}

const obj = {a: 1}
const freezedObj = readonly(obj)
freezedObj.b = 1;
freezedObj.a = 123;
console.log(freezedObj) // {a: 1}

참 신기하죠! 😉
이처럼 객체 동결을 안다면 좀 더 안정적인 객체 관리를 할 수 있게 됩니다.

🎉 마치며

직접 의사메서드를 핸들링할 경우는 거의 없을 거에요.
이는 어떻게 보면 표준을 벗어나는 프로퍼티 핸들링이기 때문에, 자칫하면 같이 개발하는 사람들에게도 혼란을 줄 수 있기 때문입니다.

다만, 우리가 생성한 객체 역시 이렇게 엔진에서 사양을 갖고 동작하는구나! 라는 것을 깊이 이해할 수 있는 파트였던 거 같아요.

🌈 이 글을 보신 분들은 아마 이 파트로 인해 현타(?)가 오셔서 검색하셨을텐데, 이 글이 도움이 되었으면 좋겠어요. 이상!

profile
People are scared of falling to the bottom but born from there. What they've lost is nth. 😉
post-custom-banner

0개의 댓글