const person = {
name: 'lee',
};
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// { value: 'lee', writable: true, enumerable: true, configurable: true }
객체에 name : 'lee'
라는 프로퍼티와 프로퍼티 값을 설정해두고 getOwnPropertyDescriptor
를 실행하면 name
프로퍼티와 관한 내용들이 담긴 객체가 반환된다.
이것이 의미하는게 뭘까 ?
자바스크립트는 객체 지향 프로그래밍 언어로서 객체와 관련된 내부 동작들이 구성되어 있다.
내부 동작들에는 내부 상태를 저장하는
내부 슬롯과 내부 동작을 구현하기 위한
내부 메소드`가 존재한다.
내부 슬롯과 내부 메소드가 어떤 것이 존재하는지에 대한 내용은 추후 살펴보겠지만
대부분 내부 슬롯과 내부 메소드의 경우 사용자가 직접 접근 할 수 없지만 일부는 사용자가 직접 접근하거나 설정하여 객체의 특성을 정의하는 것이 가능하다.
이름부터가 상당히 어질어질해보인다.
const person = {
name: 'lee',
};
위처럼 person
이란 식별자에 name
이란 프로퍼티와 lee
라는 프로퍼티 값을 설정해주었다.
결국 객체란 프로퍼티와 프로퍼티 값들로 이뤄진 코드 블록이다.
이 때 자바스크립트 엔진은 객체가 선언되면 프로퍼티와 관련된 상태를 기본값으로 자동 정의한다.
데이터 프로퍼티 어트리뷰트 | 접근자 프로퍼티 어트리뷰트 |
---|---|
프로퍼티의 값과 관련된 값 | 프로퍼티의 동작과 관련된 값 |
const person = {
name: 'lee',
};
const Descriptor = Object.getOwnPropertyDescriptor(person, 'name');
console.log(Descriptor);
// { value: 'lee', writable: true, enumerable: true, configurable: true }
데이터 프로퍼티의 상태와 관련된 객체는 프로퍼티 어트리뷰트
라고 한다. 말 그대로 프로퍼티 상태
데이터 프로퍼티의 상태를 알기 위해서는
Object
라는 이미 선언되어있는 내부 메소드의 getOwnPropertyDescriptor('객체명' , '프로퍼티명')
을 통해 데이터 프로퍼티 어트리뷰트를 객체 타입으로 반환 받을 수 있다.
데이터 프로퍼티 어트리뷰트에 존재하는 프로퍼티들은 다음과 같다.
value
value
값에 해당 값을 저장한다.writable
true
인 경우엔 값을 할당하는 것이 가능하고 false
인 경우엔 읽기 전용으로 사용된다.enumeratble
true
인 경우엔 반복문이나 Object.keys()
등으로 열거 가능하다.configurable
true
인 경우엔 프로퍼티의 값을 다시 정의하거나 삭제하는 것이 가능하다.단
writable
이true
인 경우에는 값의 변경과writable 을
false` 로 변경하는 것은 가능하다.
Object.getOwnPropertyDescription
로 프로퍼티 하나 하나를 살펴보는것도 가능하고
복수형으로 사용하여 객체의 모든 프로퍼티에 대해 살펴보는 것도 가능하다.
const person = {
name: 'lee',
age: 16,
};
const Descriptors = Object.getOwnPropertyDescriptors(person);
console.log(Descriptors);
{
name: {
value: 'lee',
writable: true,
enumerable: true,
configurable: true
},
age: { value: 16, writable: true, enumerable: true, configurable: true }
}
이처럼 자바스크립트 엔진은 프로퍼티들로 이뤄진 객체를 설정하면 자동으로 프로퍼티 어트리뷰트를 설정한다고 하였다.
자동으로 설정된 프로퍼티 어트리튜브틔 기본 설정은
{ value: 설정한 프로퍼티 값, writable: true, enumerable: true, configurable: true }
이다.
그렇다면 프로퍼티를 설정 할 때 프로퍼티 어트리뷰트를 지정해서 설정하는게 가능할까 ?
가능합니다요
Object.defineProperty('객체명' , '프로퍼티명' , '프로퍼티 어트리뷰트')
를 통해 설정 가능하다.
const person = {};
Object.defineProperty(person, 'firstName', {
value: 'lee',
writable: true,
enumerable: true,
configurable: true,
});
Object.defineProperty(person, 'lastName', {
value: 'dongdong',
writable: true,
});
console.log(Object.getOwnPropertyDescriptors(person));
{
firstName: {
value: 'lee',
writable: true,
enumerable: true,
configurable: true
},
lastName: {
value: 'dongdong',
writable: true,
enumerable: false,
configurable: false
}
}
처럼 프로퍼티 어트리뷰트를 하나하나 내가 설정 해줄 수도 있으며 일부 설정은 설정하지 않을 수 있다.
만약 difineProperty
를 이용해 프로퍼티 어트리뷰트로 프로퍼티를 설정하게 된다면 기본값은 undefined , false
가 기본값이다.
const person = {};
Object.defineProperty(person, 'firstName', {
value: 'lee',
}); // defineProperty 를 이용한 프로퍼티 설정
person.lastName = 'dongdong'; // 동적 프로퍼티 설정으로 한 프로퍼티 설정
console.log(Object.getOwnPropertyDescriptors(person));
{
firstName: { // defineProperty 를 이용한 프로퍼티 설정
value: 'lee',
writable: false,
enumerable: false,
configurable: false
},
lastName: { // 동적 프로퍼티 설정으로 한 프로퍼티 설정
value: 'dongdong',
writable: true,
enumerable: true,
configurable: true
}
}
그러면 프로퍼티 어트리뷰트
값에 따른 변화를 살펴보자
enumerable
const person = {};
Object.defineProperty(person, 'firstName', {
value: 'lee',
});
person.lastName = 'dongdong';
console.log(person); // { lastName: 'dongdong' }
위처럼 firstname
프로퍼티는 enumerable (열거 가능성)
이 false
, lastName
은 true
이다.
firstname
은 열거가 불가능하기 때문에
객체 내에 존재하지만 열거가 불가능하여 로그자체가 되지 않는다.
console.log(Object.keys(person)); // [ 'lastName' ]
프로퍼티 명을 열거하는 것이 불가능하기에 프로퍼티 키들을 배열 형태로 반환하는 Object.Keys
에서도 열거 불가능하다.
이후 예시에서는 열거 가능하도록 enumerable
을 true
로 설정해준 후 예시를 살펴보겠다.
writable
const person = {};
Object.defineProperty(person, 'firstName', {
value: 'lee',
enumerable: true,
});
person.lastName = 'dongdong';
person.firstName = 'kim';
person.lastName = 'kangkang';
console.log(person); // { firstName: 'lee', lastName: 'kangkang' }
writable
은 객체의 프로퍼티 값 가능여부를 나타내는 속성이라 하였다.
firstName
은 writable
이 false
이기 때문에 프로퍼티 값을 변경하는 것에 대한 요청이 무시되었다.
configurable
객체의 프로퍼티 값을 삭제하는 방법은
delete person.lastName;
처럼 delete
를 이용해서 프로퍼티 키를 제거한다고 하였다.
이런 삭제 가능 여부를 configurable
을 통해 제어한다고 하였다.
const person = {};
Object.defineProperty(person, 'firstName', {
value: 'lee',
enumerable: true,
});
person.lastName = 'dongdong';
delete person.firstName; //firstName 은 configurable 이 false 이기 때문에 삭제 요청이 무시됨
delete person.lastName; // configurable : true 이기 때문에 삭제 가능
console.log(person); // { firstName: 'lee' }
객체의 프로퍼티를 설정하면 데이터 프로퍼티
가 생성된다고 하였다.
데이터 프로퍼티
는 프로퍼티의 값
에 관한 내용이였다면
접근자 프로퍼티
는 프로퍼티의 동작을 정의한다.
동작을 정의한단게 뭘까 ?
예를 들어 fullName
과 lastName
을 프로퍼티로 갖는 객체 person
이 있다고 해보자
내가 이 사람의 WholeName
을 알고 싶다면 fullName
과 lastName
을 더해야만 알 수 있다.
person
의 fullName , lastName
은 값만을 가지고 있다.
하지만 내가 person
의 wholeName
을 알 수 있게 해주는 동작을 정의
하거나 wholeName
을 설정하면 자동으로 fullName , lastName
에 설정되록 하게 하는 동작을 정의
한다면
이런 정의된 동작을 접근자 프로퍼티
라고 한다.
정리
접근자 프로퍼티는 프로퍼티의 동작을 정의한 프로퍼티를 의미한다.
접근자 프로퍼티는 get , set
이란 프로퍼티 어트리뷰트
를 갖는다.
get , set
프로퍼티 어트리뷰트는 사실 내부 메소드에 정의된 [[getter]] , [[setter]] 함수이다.
말만들으면 잘 모르겠으니 예시를 통해 알아보자
const person = {
firstName: 'lee',
lastName: 'dongdong',
get wholeName() {
return `${this.firstName} ${this.lastName}`;
},
set wholeName(name) {
[this.firstName, this.lastName] = name.split(' ');
},
};
console.log(person); // { firstName: 'lee', lastName: 'dongdong', wholeName: [Getter/Setter] }
person
객체를 선언 할 때 wholeName
이란 프로퍼티 키를 설정해주고 get , set
일 때의 작동 로직을 다르게 설정해뒀다.
마치 생김새만 보면 function(){}
과 유사해보이지만 차이가 존재한다.
person
객체를 살펴보면 wholeName : [Getter / Setter]
로 표현되는데 이는 wholeName
이란 프로퍼티는 get , set
내부 메소드를 이용한다는 것을 알 수 있다.
console.log(person.wholeName); // lee dongdong
다음처럼 person
의 wholeName
프로퍼티에 접근하면 getter
함수가 작동하여 get
으로 정의된 wholeName
코드 블록이 실행된다. this
에 대한 문법은 나중에 살펴보겠지만 this
는 해당 객체를 나타내며 this.firstName
은 해당 객체의 firstName
프로퍼티를 참조한다.
const person = {
firstName: 'lee',
lastName: 'dongdong',
get wholeName() {
return `${this.firstName} ${this.lastName}`;
},
set wholeName(name) {
[this.firstName, this.lastName] = name.split(' ');
},
};
person.wholeName = 'Kim KangKang';
console.log(person); // { firstName: 'Kim', lastName: 'KangKang', wholeName: [Getter/Setter] }
console.log(person.wholeName); // Kim KangKang
이번에는 할당 연산자를 이용하여 kim kangkang
을 wholeName
에 할당해주었더니 setter
함수가 작동하여 person
객체의 firstName, lastName
을 설정해주었다.
이 때 할당 받은 인수를 set
에 정의된 name
매개 변수에 할당한다.
정리
접근자 프로퍼티
는 프로퍼티의 동작을 구성하는 프로퍼티로 프로퍼티의 값을 읽거나, 저장할 때 사용한다.
get
:getter
메소드로 객체의 데이터 프로퍼티의 값을 읽을 때 사용
set
:setter
메소드로 객체의 데이터 프로퍼티의 값을 할당 할 때 사용
접근자 프로퍼티는 get , set
말고도 데이터 프로퍼티처럼 enumerable , configurable
어트리뷰트를 갖는다.
위에서 Object.defineProperty('객체명' , '프로퍼티명' , '프로퍼티 어트리뷰트')
로 설정해주었다면
Object.defineProperties('객체명' , '프로퍼티 객체')
를 이용해 많은 프로퍼티들이 담긴 객체를 이용해 2개 이상의 프로퍼티들을 설정해줄 수 있다.
let person = {};
let properties = {
firstName: {
value: 'lee',
enumerable: true,
},
lastName: {
value: 'dongdong',
enumerable: true,
},
wholeName: {
get() {
return `${this.firstName} + ${this.lastName}`;
},
set(name) {
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
},
};
Object.defineProperties(person, properties);
console.log(person); // { firstName: 'lee', lastName: 'dongdong', wholeName: [Getter/Setter] }
프로퍼티 어트리뷰트 중 writable
를 false
로 설정하여, 객체의 프로퍼티 값을 변경할 수 없게 만들 수 있다. 이를 통해 데이터의 불변성을 유지하고 객체의 상태를 안정적으로 유지하는데 도움이 된다.
enumerable
을 false
로 설정해주어 프로퍼티가 열거되지 않게 하고, 객체를 반복할 때 특정 프로퍼티를 무시하고자 할 때 유용하다.
configurable
을 false
로 만들어 프로퍼티의 설정을 변경 할 수 있다.
이를 통해 객체의 구조를 변경하지 못하게 하거나 실수로 프로퍼티를 삭제하는 것을 방지하는 도움이 된다.
정리
프로퍼티 어트리뷰트 설정은 코드의 안정성과 유지보수성을 향상 시키고 의도치 않은 부작용을 방지하는데 도움이된다. 특히 라이브러리나 프레임워크를 개발할 때 명시적인 설정을 통해 예상치 못한 문제를 방지하고 안정적인 인터페이스를 제공할 수 있다.
그럼 객체의 프로퍼티 어트리뷰트들을 설정해주려면 프로퍼티를 추가 할 때 마다
어트리뷰트를 설정하고 추가해줘야 할까 ?
객체 전부를 설정해주고 싶다면 .. ?
노농
Object
내부 메소드에 준비가 이미 되어있다구
구분 | 메소드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티 값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 값 재정의 |
---|---|---|---|---|---|---|
객체 확장 금지 | Object.preventExtension | X | O | O | O | O |
객체 밀봉 | Object.seal | X | X | O | O | X |
객체 동결 | Object.freez | X | X | O | X | X |
Object.preventExtension
은 객체의 프로퍼티가 늘어나는 것을 방지해주는 메소드이다.
let person = {
name: 'lee',
age: 16,
};
console.log(Object.isExtensible(person)); // true
person.address = 'korea';
console.log(person); // { name: 'lee', age: 16, address: 'korea' }
기본적으로 객체는 새로운 프로퍼티를 추가 가능 여부 isExtensible
가 true
로 되어있으나
let person = {
name: 'lee',
age: 16,
};
console.log(Object.isExtensible(person)); // true
Object.preventExtensions(person);
console.log(Object.isExtensible(person)); // false
person.address = 'korea'; // 무시됨
console.log(person); // { name: 'lee', age: 16, address: 'korea' }
Object.perventExtension
을 이용해 새로운 프로퍼티 값을 추가하는 것을 막을 수 있다.
Object.seal('객체명')
을 이용해 객체를 밀봉하는 것이 가능하다.
let person = {
name: 'lee',
age: 16,
};
console.log(Object.isSealed(person));
Object.seal(person);
console.log(Object.isSealed(person));
delete person.name; // 삭제 요청 무시됨
person.address = 'korea'; // 프로퍼티 추가 요청 무시됨
console.log(person); // { name: 'lee', age: 16 }
Object.defineProperty(person, 'name', { enumerable: false }); // TypeError: Cannot redefine property: name
// 프로퍼티 값 재정의 요청 무시됨
객체를 밀봉하면 새로운 값을 추가하거나 삭제 하는 것이 불가능하고 프로퍼티 어트리뷰트를 변경하는 것도 불가능하다.
let person = {
name: 'lee',
age: 16,
};
Object.seal(person);
person.name = 'kim';
console.log(person.name); // kim
프로퍼티 값을 읽거나 재정의하거나 가능하다.
객체 동결은 Object.freeze('객체명')
을 이용해 동결할 수 있다.
let person = {
name: 'lee',
age: 16,
};
console.log(Object.isFrozen(person));
Object.freeze(person);
console.log(Object.isFrozen(person));
person.name = 'kim';
console.log(person); // {name : lee , age : 16}
으로 객체의 값을 쓰는 연산 조차 불가능하며 객체 프로퍼티들을 모두 읽기전용
으로만 변경한다.
그럼 객체 자체를 변경하는 것은 ?
let person = { name: 'lee', age: 16, }; Object.freeze(person); person = { address: 'korea' }; console.log(Object.isFrozen(person)); // false console.log(person); // {address : korea}
객체 자체를 변경하는 것은 가능하다.
객체 자체를 변경하고 싶지 않다면const
를 이용해 변경해주자
위에서 나열한 내용들은 모두 객체의프로퍼티 속성
을 변경해주는 값이다.
객체 자체가 변경되고 프로퍼티 자체가 변경된다면 이전에 설정해준 값들은 유효하지 않다.
그럼 객체의 프로퍼티 값을 변경하고 객체 자체를 변경하였을 때, 동일한 프로퍼티 명이 있을 경우에는 이전 설정해준 값이 적용될까 ?
안된다. 객체 자체가 변경되면 새로운 객체는 새로운 프로퍼티 어트리뷰트 값을 갖는다.
위에서 설명한 객체 확장 금지 , 객체 밀봉 , 객체 동결
은 모두 얕은 변경 방지
이다.
얕은 변경 방지
란 직속 프로퍼티 (한겹의 프로퍼티) 만 변경이 방지되며 중첩 객체까진 영향을 주지 못한다는 것이다.
let person = {
name: 'kim',
information: {
age: 16,
address: 'seoul',
},
};
Object.freeze(person);
console.log(Object.isFrozen(person)); // true
console.log(Object.isFrozen(person.information)); // false
이것을 방지하기 위해서는 깊은 변경 방지
를 사용해야 한다.
function deepFreez(obj) {
if (obj && typeof obj === 'object' && !Object.isFrozen(obj)) {
Object.freeze(obj);
for (let key of Object.keys(obj)) {
if (typeof obj[key] === 'object') {
obj[key] = deepFreez(obj[key]);
}
}
}
return obj;
}
let person = {
name: 'kim',
information: {
age: 16,
address: 'seoul',
},
};
person = deepFreez(person);
console.log(Object.isFrozen(person)); // true
console.log(Object.isFrozen(person.information)); // true
for in , for of
순회 가능한 배열에 대해서
for in
을 사용하면 배열의 인덱스를 조회하고 ,for of
를 사용하면 배열의 값을 순회한다.