티스토리 뷰
[JavaScript | ES6 ] get(접근자), set(설정자), 프로퍼티 플래그와 설명자
blueprint-12 2023. 7. 24. 23:501. set ( functions Setter, 설정자 )
set 구문은 객체의 속성에 할당을 시도할 때 호출할 함수를 바인딩한다.
const language = {
set current(name) {
this.log.push(name);
},
log: []
};
language.current = 'EN';
language.current = 'FA';
console.log(language.log);
// Expected output: Array ["EN", "FA"]
1-1. 문법 (syntax)
{set prop(val) { . . . }}
{set [expression](val) { . . . }}
- `props` : 주어진 함수를 바인딩할 속성 이름
- `val` : props에 할당을 시도한 값
- `expression` : ES6 이후, 주어진 함수를 바인딩할 속성 이름을 구하는 표현식을 사용할 수 있음
JS의 설정자(setter)를 사용하면 지정한 속성 값의 변경을 시도할 때마다 함수를 호출할 수 있다.
설정자는 보통 접근자(getter)와 함께 '유사 속성'을 정의할 때 사용한다. 어떤 속성에 설정자를 바인딩하는 동시에, 해당 속성이 값도 가지도록 할 수는 없다(바인딩과 할당이 동시에는 x).
1-2. 주의사항
- 설정자의 식별자는 숫자나 문자열
- 설정자 함수는 최대 한 개의 매개변수만 가질 수 있다.
- 객체 리터럴에서, 같은 속성 키에 다수의 설정자를 바인딩할 수 없음
const language = {
set current(name) {
this.log.push(name);
},
log: []
}
language.current = 'EN';
console.log(language.log); // ['EN']
language.current = 'FA';
console.log(language.log); // ['EN', 'FA']
- 여기서 current의 값은 정의하지 않았으므로, 할당이 아니라 접근을 시도하면 undefined 만 반환하는 것을 주의
- current 는 setter임!!
1-3. `delete` 연산자로 설정자 제거하기
접근자(setter)를 삭제하려면 간단히 delete 연산자를 사용하면 된다. 👧
delete language.current;
1-4. 이미 존재하는 객체에 설정자 추가하기 with `defineProperty`
const o = {a: 0};
Object.defineProperty(o, 'b', {
set: function(x) { this.a = x / 2; }
});
o.b = 10;
// 설정자 실행, a 속성에 10 / 2 = 5 할당
console.log(o.a)
// 5
1-5. 계산된 속성(computed property) 이름 사용하기
const expr = 'foo';
const obj = {
baz: 'bar',
set [expr](v) { this.baz = v; }
};
console.log(obj.baz);
// "bar"
obj.foo = 'baz';
// run the setter
console.log(obj.baz);
// "baz"
2. get ( Functions Getter, 접근자 )
`get` 구문은 객체의 속성 접근 시 호출할 함수를 바인딩한다.
const obj = {
log: ['a', 'b', 'c'],
get latest() {
return this.log[this.log.length - 1];
}
};
console.log(obj.latest);
// Expected output: "c"
- log 라는 배열이 있고 a, b, c라는 인자를 갖고 있다. lastest 함수는 this.log[마지막요소] 를 return 하고 있다.
- console.log(obj.lastest) 를 통해 함수를 "프로퍼티"처럼 호출하여 사용하고 있다.
2-1. 문법
{get prop() { ... } }
{get [expression]() { ... } }
- `props`: 주어진 함수를 바인딩할 속성 이름
- `expression`:ES6 이후, 주어진 함수를 바인딩할 속성 이름을 구하는 표현식 사용 가능
때로 동적으로 계산한 값을 반환하는 속성이 필요하거나, 명시적인 함수 호출없이도 객체의 내부 변수 상태를 반영하는 값을 나타내고 싶은 경우가 있다. JS에서는 이런 경우 사용할 수 있도록 접근자(getter)라는 기능을 제공한다.
어떤 속성에 접근자를 바인딩하는 동시에, 해당 속성이 값도 가지도록 할 수는 없다. (setter와 동일) 하지만, getter와 setter를 함께 사용해서, 접근과 할당 모두 되는 '유사 속성'을 만들 수는 있다.
2-2. 주의사항
`get` 구문을 이용할 때 아래의 사항을 주의해야 한다.
- 접근자의 식별자는 숫자나 문자열 (setter와 동일)
- 접근자 함수는 매개변수를 가질 수 없다. => 값을 호출하는 역할이기 때문에?
- 객체 리터럴에서, 같은 속성 키에 다수의 접근자(getter)를 바인딩할 수 없다. => 간단히 말하면 같은 이름의 getter를 하나의 객체에서 선언 X
- 객체 리터럴에서 getter가 데이터 속성과 같은 키를 사용할 수 없다. => 객체 프로퍼티 명과 getter 명이 동일할 수 없다.
const obj = {
log: ['example','test'],
get latest() {
if (this.log.length === 0) return undefined;
return this.log[this.log.length - 1];
}
}
console.log(obj.latest); // "test"
- `lastest`에 다른 값을 할당하려해도 아무 변화도 없음
2-3. delete 연산자로 접근자 제거하기
setter와 동일하게 delete 연산자로 getter 접근자를 삭제할 수 있다.
delete obj.latest;
2-4. 이미 존재하는 객체에 설정자 추가하기 with `defineProperty`
- setter와 동일하다.
- 이미 존재하고 있는 객체에 getter 접근자를 추가하려면 Object.defienProperty()를 사용하면 된다.
const o = {a: 0};
Object.defineProperty(o, 'b', { get: function() { return this.a + 1; } });
console.log(o.b) // 접근자 실행, a + 1 반환 (0 + 1 = 1)
+ computed property도 setter와 동일하게 사용 가능
const expr = 'foo';
const obj = {
get [expr]() { return 'bar'; }
};
2-5. 정적(static) 접근자 정의하기 🐱🐉
클래스가 등장한다 ! static 키워드와 함께 !
class MyConstants {
static get foo() {
return 'foo';
}
}
console.log(MyConstants.foo); // 'foo'
MyConstants.foo = 'bar';
console.log(MyConstants.foo); // 'foo', 정적 접근자의 값 변경 불가
2-6. 똑똑한(smart) / 느긋한(lazy) 접근자
접근자를 사용하면 객체에 속성을 정의하되, 접근하기 전에는 속성의 값을 계산하지 않을 수 있다.
접근자는 값이 실제로 필요한 상황이 오기 전까지 계산 비용을 미루는 것이다. 사용하지 않으면 비용을 지불할 일도 없다.
속성 값의 계산을 느긋(lazy)하게 만들거나 미루고, 추가 접근에 사용할 수 있도록 캐시에 저장하는 추가 최적화 방법은 `스마트 or 메모이제이션 접근자` 라고한다.
스마트 접근자 속성의 값은 접근자를 처음 호출할 때 계산하는 동시에 캐시에 저장된다. 덕분에 속성에 추가 접근 시 캐시에서 값을 즉시 반환하므로 값을 다시 계산하는 수고가 필요없다. 아래와 같은 경우 유용하다.
- 값의 계산 비용이 큰 경우: RAM, CPU 시간을 많이 소모하거나 워커스레드를 생성하거나 원격 파일을 불러오거나..
- 값이 지금 당장 필요하지 않은 경우, 나중에 사용할 수도 있고, 일부 상황에선 아예 사용하지않을수도 있는 경우
- 절대 바뀌지 않는 값이거나 다시 계산해서는 안되는 값을 여러 번 사용하는 경우
=> 즉, 변화할 수 있는 값에는 `스마트 접근자`를 사용해서는 안된다. 값이 바뀌어야 하는 상황에서도 접근자의 값은 항상 동일할 것이기 때문이다.
2-7. get vs definedProperty 차이점
둘은 비슷한 결과를 내지만 class에서 사용할 경우 미묘한 차이가 생긴다.
- `get`의 경우, 해당 속성은 인스턴스의 프로토타입에 정의
- Object.defineProperty()의 경우, 속성을 인스턴스 자체에 직접 정의하게 된다.
class Example {
get hello() {
return 'world';
}
}
const obj = new Example();
console.log(obj.hello);
// "world"
console.log(Object.getOwnPropertyDescriptor(obj, 'hello'));
// undefined
console.log(
Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(obj), 'hello'
)
);
/* { configurable: true, enumerable: false,
get: function get hello() { return 'world'; }, set: undefined }
*/
3. 프로퍼티 플래그(flag)와 설명자
객체에는 프로퍼티가 저장된다.
프로퍼티는 단순히 key - value 쌍의 관점이 아니라 더욱 유연하고 강력한 자료구조이다.
3-1. 프로퍼티 플래그
객체의 프로퍼티는 값(value)와 함께 플래스(flag)라 불리는 특별한 속성 세 가지를 갖는다.
- writable - true 이면 값을 수정할 수 있다. 그렇지 않다면 읽기만 가능
- enumerable - true이면 반복문을 사용해 나열할 수 있다. 그렇지 않다면 반복문 사용 X
- configurable - true 이면 프로퍼티 삭제나 플래그 수정이 가능하다. 그렇지 않다면 프로퍼티 삭제와 플래그 수정 X
=> 평범한 방식으로 프로퍼티를 만들게 되면 기본적으로 해당 프로퍼티의 플래그는 모두 true값을 가진다.
3-2. 플래그를 얻는 방법
Object.getOwnPropertyDescriptor 메서드를 사용하면 특정 프로퍼티에 대한 정보를 모두 얻을 수 있다.
let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
// 매개변수
// obj: 정보를 얻고자 하는 객체명
// propertyname: 정보를 얻고자 하는 객체 내 프로퍼티명
- 반환값은 "프로퍼티 설명자(descriptor)"라고 불리는 객체이다. 여기에는 프로퍼티 값과 세 플래그에 대한 정보가 모두 담겨있다.
- 플래그를 변경 or 생성 하고싶다면 Object.defineProperty(obj, propertyName, descriptor) 를 사용하면 된다.
- 위에서 평범한 방식으로 객체 프로퍼티를 만들었을 때와 defineProperty를 이용해 프로퍼티를 만들었을 때의 가장 큰 차이점은 플래그에 있다.
- defineProperty를 사용하 프로퍼티를 만든 경우, descriptor에 플래그의 값을 명시하지 않으면 값이 기본적으로 false가 된다.
1) writable 플래그
- 🔺주의점: 객체의 이름(myObj.name) 프로퍼티의 writable 플래그를 false로 바꿔주었다고 해도, 에러는 엄격 모드(strict)에서만 발생한다. 비 엄격 모드에서 읽기 전용 프로퍼티에 값을 쓰려고해도 가능하지만, 이때 값을 변경하는 것은 불가능하다. 즉, 비엄격 모드에서 플래그에서 정한 규칙을 위반하는 행위는 에러없이 그냥 무시된다.
const user = {
name: 'Ho',
};
Object.defineProperty(user, 'name', {
writable: false,
});
user.name = 'changed?';
console.log('user.name: ', user.name); // "Ho"
- 비엄격 모드에서 user.name 의 값을 재할당하였는데도 user.name을 콘솔에 찍어보면 Ho로 changed? 가 무시된 것을 알 수 있다.
- 엄격 모드에서는 어떻게 되는 지 확인하고 싶어서 "use strict"를 써주었는데 임의로 만든 {} 블록 구문에서는 엄격 모드가 적용되지 않았다.
2) enumerable 플래그
내가 만든 객체에 커스텀 메서드 `toString`을 추가해보자.
- 객체 내장 메서드 toString은 열거 불가능(non-enumerable)하기 때문에 `for...in` 사용시 나타나지 않는다.(알기론 for of 도 마찬가지였던 거 같다.)
- 하지만 커스텀 toString을 추가하면 아래와 같이 for .. in 에 toString이 나타난다.
let user = {
name: 'ho',
toString() {
return this.name;
},
};
for (let key in user) console.log(key); // name, toString
- 물론 커스텀 메서드 toString을 Object.defineProperty() 로 enumerable: false 값을 주면 for..in에서 toString을 열거할 수 없게 만들 수 있다.
😈열거 불가능한 프로퍼티는 Object.keys 에서도 배제된다.
alert(Object.keys(user));
3) configurable 플래그
구성 가능하지 않음을 나타내는 플래그 (non-configurable flag)인 configurable: false는 몇몇 내장 객체나 프로퍼티에 기본으로 설정되어 있다.
어떤 프로퍼티의 configurable 플래그가 false로 설정되어 있다면 해당 프로퍼티는 객체에서 지울 수 없다.
e.g.) Math.PI 프로퍼티, 해당 내장 메서드는 쓰기와 열거, 구성이 불가능하다. 또한, 개발자가 코드를 사용해 Math.PI 값을 변경하거나 덮어쓰는 것도 불가능하다.
- configurable 플래그를 false로 설정하면 돌이킬 방법이 없다. defineProperty를 써서 configurable 플래그의 값을 true로 되돌릴 수 없다.
- `configurable: fale`의 구체적인 제약사항은 configurable 플래그 수정 불가, enumerable 플래그 수정 불가, writable: false의 값을 true로 바꾸기 불가 (단, true -> false 가능) 마지막으로 접근자 프로퍼티 get/set을 변경할 수 없음(새롭게 만드는 것은 가능) 이다.
- configurable: false를 잘 사용하면 "영원히 변경할 수 없는" 프로퍼티를 만들어낼 수 있다.
🚩 non-configurable은 non-writable과 다르다.
configurable 플래그가 false더라도 writable이 true이면 프로퍼티의 값을 변경할 수 있다.
configurable: false 는 플래그 값 변경이나 프로퍼티 삭제를 막기위해 만들어졌지, 프로퍼티 값 변경 자체를 막기위해 만들어진 것이 아니다.
4. 프로퍼티 getter와 setter
객체의 프로퍼티는 두 종류로 나뉜다.
- 데이터 프로퍼티, 우리가 통상적으로 알고있는 프로퍼티이다.
- 접근자 프로퍼티, 새로운 종류의 프로퍼티이며 본질은 함수이다. 하지만, 이 함수는 값을 획득(get)하고 설정(set)하는 역할을 담당한다. 하지만 외부 코드에서는 함수가 아닌 일반적인 프로퍼티처럼 호출된다.
4-1. 접근자 프로퍼티 설명자
데이터 프로퍼티의 설명자와 접근자 프로퍼티의 설명자는 다르다.
접근자 프로퍼티엔 설명자 value와 writable가 없는 대신에 get과 set이라는 함수가 있다.
아래와 같은 설명자를 갖음
- get - 인수가 없는 함수로, 프로퍼티를 읽을 때 사용
- set - 인수가 하나인 함수로, 프로퍼티에 값을 쓸 때 호출
- enumrable - 데이터 프로퍼티와 동일
- configurable - 데이터 프로퍼티와 동일
getter와 setter를 `실제`프로퍼티 값을 감싸는 wrapper처럼 사용하면, 프로퍼티 값을 원하는 대로 통제할 수 있다.
- 아래와 같은 예시 외에도 기존 프로퍼티를 다른 프로퍼티로 저장해야 한다고 했을 때, 기존 프로퍼티를 쓰고 있는 로직을 전체 변경하는 것보다 기존 프로퍼티를 위한 getter를 추가하여 기존프로퍼티와 새로운 프로퍼티 둘 다 사용할 수 있게 만들면 된다.
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("입력하신 값이 너무 짧습니다. 네 글자 이상으로 구성된 이름을 입력하세요.");
return;
}
this._name = value;
}
};
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // 너무 짧은 이름을 할당하려 함
- 위의 코드는 user의 이름은 _name에 저장되고, 프로퍼티에 접근하는 것은 getter 와 setter를 통해 이뤄진다.
🚩 기술적으로 외부 코드에서는 user._name을 사용해 이름에 바로 접근할 수 있으나 밑줄 "_"로 시작하는 프로퍼티는 객체 내부에서만 활용하고, 외부에서는 건드리지 않는 것이 관습이다.
ref:
'Frontend > JavaScript' 카테고리의 다른 글
[JavaScript] 클로저와 클로저의 활용(with IIFE) (0) | 2023.08.03 |
---|---|
[JavaScript | ES6] Class (this, public & private 필드, 상속) (0) | 2023.07.18 |
[JavaScript] Array.from + selectedOptions 으로 선택된 요소의 값만 담은 배열반환하기 (0) | 2023.05.11 |
[JavaScript] 이중부정 연산자(!!) (0) | 2023.04.17 |
[node.js] process.env 환경변수 설정과 dotenv 라이브러리 (0) | 2023.04.13 |
- Total
- Today
- Yesterday
- 항해99프론트후기
- tilde caret
- 원티드 프리온보딩 프론트엔드 챌린지 3일차
- 틸드와 캐럿
- 타입스크립트 장점
- getStaticPaths
- getServerSideProps
- float 레이아웃
- grid flex
- 원티드 프리온보딩 FE 챌린지
- text input pattern
- nvm 설치순서
- 프리온보딩 프론트엔드 챌린지 3월
- 형제 요소 선택자
- reactAPI
- 항해99추천비추천
- 프리렌더링확인법
- 항해99프론트
- 원티드 3월 프론트엔드 챌린지
- Prittier
- 부트캠프항해
- D 플래그
- fs모듈 넥스트
- ~ ^
- aspect-ratio
- nvm경로 오류
- is()
- 타입스크립트 DT
- 원티드 FE 프리온보딩 챌린지
- && 셸 명령어
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |