javascript 15강 프로퍼티


JavaScript 15강 예습

  • 15강 : 프로퍼티 정의

    1. 프로퍼티 정의란?
    • 프로퍼티 정의 및 동적 생성
    1. 내부 / 슬롯 메소드
      • 내부 메소드 [[ Get ]]
    2. 접근자 프로퍼티
      • 접근자 프로퍼티와 데이터 프로퍼티의 구별 방법
    3. 프로퍼티 어트리뷰트
      • 데이터 프로퍼티의 어트리뷰트
      • 접근자 프로펕
      • Object.defineProperty 메소드


15강

프로퍼티 정의


프로퍼티 정의란?

프로퍼티 어트리뷰트의 값을 정의하여 프로퍼티의 상태를 관리하는 것을 말한다.

프로퍼티 값을 갱신 가능하도록 할 것인지, 프로퍼티를 열거 가능하도록 할 것인지, 프로퍼티를 재정의 가능하도록 할 것인지 정의.

*JS 엔진은 프로퍼티를 생성(객체 리터럴의 평가, 프로퍼티 동적 생성)할 때, *

프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의


프로퍼티 정의 및 동적 생성

프로퍼티 어트리뷰트란? 프로퍼티의 상태를 나타내는 값

1
2
3
4
5
6
7
8
9
10
11
12
const obj = {};

// 자바스크립트 엔진은 프로퍼티를 생성할 때, 프로퍼티의 상태를 나타내는
// 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
obj.prop = 10;

// 정의된 프로퍼티 어트리뷰트는 Object.getOwnPropertyDescriptor 메소드로 확인할 수 있다.
const descriptor = Object.getOwnPropertyDescriptor(obj, 'prop');
console.log(descriptor);
// obj 객체의 prop 프로퍼티는 value, writable, enumerable, configurable
// 어트리뷰트가 정의되어 있다.
// {value: 10, writable: true, enumerable: true, configurable: true}

위 예제에서 JS엔진은 먼저, obj라는 객체 안에 prop라는 프로퍼티가 있는지 확인한다.

prop 가 없다면 동적으로 생성한 후 10을 할당한다. 이를 동적 생성이라 한다.

동적 생성은 프로퍼티가 존재하지 않을 때, 프로퍼티를 생성하여 추가하는 것. 동적 생성시 프로퍼티 어트리뷰트를 기본값으로 지정한다.

프로퍼티 정의는 프로퍼티 어트리뷰트를 정의하는 것 ( 프로퍼티 어트리뷰트 : 프로퍼티의 상태 )

프로퍼티 상태는 프로퍼티의 값, 갱신여부, 열거가능여부, 재정의 가능 여부 를 말한다.

프로퍼티 어트리뷰트는 Object.getOwnPropertyDescriptor 메소드를 사용해 참조할 수 있다. 이 메소드는 프로퍼티 어트리뷰트 정보를 제공하는 객체인 프로퍼티 디스크립터(PropertyDescriptor)를 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
// 하나의 프로퍼티 어트리뷰트를 표현하는 프로퍼티 디스크립터 객체를 취득한다.
const descriptor = Object.getOwnPropertyDescriptor(obj, 'prop');
console.log(descriptor);
// {value: 10, writable: true, enumerable: true, configurable: true}

// ES8
// 모든 프로퍼티 어트리뷰트를 표현하는 프로퍼티 디스크립터 객체를 취득한다.
const descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
// {
// prop: { value: 10, writable: true, enumerable: true, configurable: true }
// }

존재하지 않는 프로퍼티 / 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면 undefined가 반환된다.

Object.getOwnPropertyDescriptor(객체명, '키명')


내부 슬롯/메소드

내부 슬롯과 내부 메소드는 ECMAScript 스펙에서 요구하는 객체와 관련된 내부 상태와 내부 동작을 정의한 것이다.

내부 슬롯과 내부 메소드는 객체의 프로퍼티가 아니다.

따라서 내부 슬롯과 내부 메소드는 직접적으로 접근하거나 호출할 수 있는 방법을 원칙적으로 제공하지 않는다.

단, 일부 내부 슬롯과 내부 메소드(의 구현체)에 간접적으로 접근할 수 있는 수단은 있다.

내부 메소드 [[ Get ]]

내부 메소드 [[ Get ]]는 프로퍼티 키로 프로퍼티 값에 접근하면 내부적으로 호출된다.

  1. 프로퍼티 키가 유효한지 확인한다. 프로퍼티 키는 문자열 또는 심볼이어야 한다. (“9.3 프로퍼티” 참고)
  2. 프로토타입 체인에서 프로퍼티를 검색한다.
  3. 검색된 프로퍼티가 “데이터 프로퍼티(Data property)“라면 프로퍼티 값,
    즉 데이터 프로퍼티의 프로퍼티 어트리뷰트 [[Value]]의 값을 그대로 반환한다.
  4. 만약 프로퍼티가 “접근자 프로퍼티(Accessor property)”라면 접근자 프로퍼티의 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수를 호출하여 그 결과를 반환한다.

프로토 타입 과 프로토 타입 체인

프로토 타입 : 어떤 객체의 부모 역할을 하는 객체
프로토 타입은 하위 객체에게 자신의 프로퍼티와 메소드를 상속함.

프로토 타입 체인 : 프로토 타입이 단방향 링크드 리스트 형태로 연결되어 있는 상속 구조를 의미.

객체의 프로퍼티나 메소드에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티 또는 메소드가 없다면 프로토타입 체인을 따라 프로토타입의 프로퍼티나 메소드를 차례대로 검색한다.


접근자 프로퍼티

​ 프로퍼티는 데이터 프로퍼티 와 접근자 프로퍼티로 구분된다.

  • 데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티다.
  • 접근자 프로퍼티 : 자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티

접근자 함수는 getter/setter 함수라고도 부른다.

접근자 프로퍼티는 getter와 setter 함수를 모두 정의할 수도 있고 하나만 정의할 수도 있다.

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
32
33
34
35
36
37
38
39
const person = {
// 데이터 프로퍼티
firstName: 'Ungmo',
lastName: 'Lee',

// fullName은 접근자 함수로 구성된 접근자 프로퍼티이다.
// getter 함수
get fullName() {
return this.firstName + ' ' + this.lastName;
},
// setter 함수
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ');
}
};

// 데이터 프로퍼티를 통한 프로퍼티 값의 참조.
console.log(person.firstName + ' ' + person.lastName); // Ungmo Lee

// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
// 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출된다.
person.fullName = 'Heegun Lee';
console.log(person); // {firstName: "Heegun", lastName: "Lee"}

// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
// 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다.
console.log(person.fullName); // Heegun Lee

// firstName는 데이터 프로퍼티이다.
// 데이터 프로퍼티는 value, writable, enumerable, configurable 프로퍼티 어트리뷰트를 갖는다.
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor);
// {value: "Heegun", writable: true, enumerable: true, configurable: true}

// fullName는 접근자 프로퍼티이다.
// 접근자 프로퍼티는 get, set, enumerable, configurable 프로퍼티 어트리뷰트를 갖는다.
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(descriptor);
// {get: ƒ, set: ƒ, enumerable: true, configurable: true}

person 객체의 firstNamelastName 프로퍼티는 일반적인 데이터 프로퍼티.

메소드 앞에 get,set이 붙은 메소드가 있는데 이것들이 바로 getter와 setter 함수이고 getter/setter 함수의 이름 fullName이 접근자 프로퍼티이다.

접근자 프로퍼티는 자체적으로 값(value 프로퍼티 어트리뷰트)을 가지지 않으며 다만 데이터 프로퍼티의 값을 읽거나 저장할 때 관여할 뿐이다.

접근자 프로퍼티 fullName으로 프로퍼티 값에 접근하면 내부적으로 [[Get]] 내부 메소드가 호출되어 아래와 같이 동작한다.

  1. 프로퍼티 키가 유효한지 확인한다. 프로퍼티 키 “fullName”은 문자열이므로 유효한 프로퍼티 키이다.
  2. 프로토타입 체인에서 프로퍼티를 검색한다. person 객체에 fullName 프로퍼티가 존재한다.
  3. 검색된 fullName 프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인한다. fullName 프로퍼티는 접근자 프로퍼티이다.
  4. 접근자 프로퍼티 fullName의 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수를 호출하여 그 결과를 반환한다. 프로퍼티 fullName의 프로퍼티 어트리뷰트 [[Get]]의 값은 Object.getOwnPropertyDescriptor 메소드가 반환하는 프로퍼티 디스크립터(PropertyDescriptor) 객체의 get 프로퍼티 값과 같다.

접근자 프로퍼티와 데이터 프로퍼티의 구별 방법

1
2
3
4
5
6
7
// 일반 객체의 __proto__는 접근자 프로퍼티이다.
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__');
// {get: ƒ, set: ƒ, enumerable: false, configurable: true}

// 함수 객체의 prototype은 데이터 프로퍼티이다.
Object.getOwnPropertyDescriptor(function() {}, 'prototype');
// {value: {…}, writable: true, enumerable: false, configurable: false}

프로퍼티 어트리뷰트

데이터 프로퍼티의 어트리뷰트

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

  1. [[Value]] / value

    • 프로퍼티 키에 접근하면 [[Get]]에 의해 반환되는 값
    • 프로퍼티 키로 값을 저장하면 [[Value]]에 값을 저장한다. 프로퍼티가 없다면 프로퍼티를 생성하고 저장한다.
  2. [[Writable]] / writable

    • 프로퍼티 값의 변경가능 여부를 나타내며, boolean 값 을 지님.
    • false 면 해당 프로퍼티의 [[Value]]를 변경 불가.
  3. [[Enumerable]] / enumerable

    • 프로퍼티의 열거 가능 여부를 나타내며, boolean 갑을 지님.
    • false 면 for…in 문이나 Object,keys 메소드 등으로 열거 불가.
  4. [[Comfigurable]] / configurable

    • 프로퍼티의 재정의 가능 여부를 나타내며 불리언 값을 지님.
    • false 면, 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지된다.
    • [[Writable]]이 true인 경우, [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것은 허용된다.

접근자 프로퍼티

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

  1. [[Get]]
    • 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수.
  2. [[Set]]
    • 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수.
  3. [[Enumerable]]
    • 데이터 프로퍼티의 [[Enumerable]]와 같다.
  4. [[Configurable]]
    • 데이터 프로퍼티의 [[Configurable]]와 같다.

Object.defineProperty 메소드

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
const obj = { prop1: 1 };

// 프로퍼티 동적 추가
obj.prop2 = 2;

// 프로퍼티 어트리뷰트를 표현하는 프로퍼티 디스크립터 객체를 취득한다.
const descriptor = Object.getOwnPropertyDescriptor(obj, 'prop2');
console.log(descriptor);
// → { value: 2, writable: true, enumerable: true, configurable: true }

Object.defineProperty 메소드를 사용하면 프로퍼티의 어트리뷰트를 정의할 수 있다. 인수는 객체의 참조와 데이터 프로퍼티의 키인 문자열 그리고 프로퍼티 디스크립터 객체를 전달한다.
const person = {};

// 데이터 프로퍼티 정의
Object.defineProperty(person, 'firstName', {
value: 'Ungmo',
writable: true,
enumerable: true,
configurable: true
});

Object.defineProperty(person, 'lastName', {
value: 'Lee'
});

let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log('firstName', descriptor);
// firstName {value: "Ungmo", writable: true, enumerable: true, configurable: true}

// 디스크립터 객체의 프로퍼티를 누락시키면 undefined, false가 기본값이다.
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', descriptor);
// lastName {value: "Lee", writable: false, enumerable: false, configurable: false}

// [[Enumerable]]의 값이 false인 경우,
// 해당 프로퍼티는 for…in 문이나 Object.keys 등으로 열거할 수 없다.
// lastName 프로퍼티는 [[Enumerable]]의 값이 false이므로 열거되지 않는다.
console.log(Object.keys(person)); // ["firstName"]

// [[Writable]]의 값이 false인 경우, 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없다.
// 에러는 발생하지 않고 무시된다.
// lastName 프로퍼티는 [[Writable]]의 값이 false이므로 값을 변경할 수 없다.
person.lastName = 'Kim';

// [[Configurable]]의 값이 false인 경우, 해당 프로퍼티를 삭제할 수 없다.
// 에러는 발생하지 않고 무시된다.
// lastName 프로퍼티는 [[Configurable]]의 값이 false이므로 삭제할 수 없다.
delete person.lastName;

// [[Configurable]]의 값이 false인 경우, 해당 프로퍼티를 재정의할 수 없다.
// Object.defineProperty(person, 'lastName', { enumerable: true });
// Uncaught TypeError: Cannot redefine property: lastName

descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', descriptor);
// lastName {value: "Lee", writable: false, enumerable: false, configurable: false}

// 접근자 프로퍼티 정의
Object.defineProperty(person, 'fullName', {
// getter 함수
get: function () { // === get() {
return this.firstName + ' ' + this.lastName;
},
// setter 함수
set: function (name) { // === set(name) {
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
configurable: true
});

descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log('fullName', descriptor);
// fullName {get: ƒ, set: ƒ, enumerable: true, configurable: true}

person.fullName = 'Heegun Lee';
console.log(person); // {firstName: "Heegun", lastName: "Lee"}

Object.defineProperty 메소드로 프로퍼티 정의할 때 프로퍼티 디스크립터 객체의 프로퍼티를 일부 누락할 수 있다.

프로퍼티 디스크립터 객체에서 누락된 어트리뷰트는 아래와 같이 기본값이 적용된다.

프로퍼티 디스크립터 객체의 프로퍼티 대응하는 프로퍼티 어트리뷰트 디스크립터 객체의 프로퍼티 누락 시의 기본값
value [[Value]] undefined
get [[Get]] undefined
set [[Set]] undefined
writable [[Writable]] false
enumerable [[Enumerable]] false
configurable [[Configurable]] false

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
32
33
const person = {};

Object.defineProperties(person, {
// 데이터 프로퍼티 정의
firstName: {
value: 'Ungmo',
writable: true,
enumerable: true,
configurable: true
},
lastName: {
value: 'Lee',
writable: true,
enumerable: true,
configurable: true
},
// 접근자 프로퍼티 정의
fullName: {
// getter 함수
get: function () {
return this.firstName + ' ' + this.lastName;
},
// setter 함수
set: function (name) {
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
configurable: true
}
});

person.fullName = 'Heegun Lee';
console.log(person); // {firstName: "Heegun", lastName: "Lee"}

Share