본 내용은 책 모던 자바스크립트 Deep Dive 의 내용을 참조했습니다.
자바스크립트 엔진은 프로퍼티를 만들때 프로퍼티 어트리뷰트
를 기본값으로 자동 정의한다.
데이터 프로퍼티는 키와 값으로 이루어진 흔히 아는 프로퍼티다.
프로퍼티 안에는 다음과 같은 속성이 있다.
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 | 설명 |
---|---|---|
[[value]] | value | 프로퍼티의 키값 |
[[Writable]] | writable | 불리언 값, value값을 바꿀수 있게한다. |
[[Enumerable]] | enumerable | 불리언 값, 프로퍼티의 열거 가능여부를 나타낸다 |
[[Configurable]] | configurable | 불리언 값, 프로퍼티의 재정의 가능 여부를 나타낸다 |
만약 프로퍼티의 속성을 확인하려면 ?
Object.getOwnPropertyDescriptor(오브젝트,프로퍼티 키)
를 사용
const person = {
name: 'Lee'
};
// 프로퍼티 디스크립터 객체를 반환함
console.log(Object.getOwnPropertyDescriptor(person,'name'));
// {value: "Lee", writable: true, enumerable: true, configurable: true}
프로퍼티를 처음 생성할때 writable, enumerable, configurable 모두 true
로 설정된다.
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 | 설명 |
---|---|---|
[[Get]] | get | getter 함수를 호출하고 프로퍼티값을 읽는다 |
[[Set]] | set | setter 함수를 호출하고 프로퍼티 값을 설정후 저장한다. |
[[Enumerable]] | enumerable | 불리언 값, 프로퍼티의 열거 가능여부를 나타낸다 |
[[Configurable]] | configurable | 불리언 값, 프로퍼티의 재정의 가능 여부를 나타낸다 |
Object.getOwnPropertyDescriptor(오브젝트,프로퍼티 키)
에서
get 과 set 이면 접근자 프로퍼티 , value 와 writable 이면 데이터 프로퍼티
그럼 프로퍼티는 어떻게 정의할까?
Object.defineProperty
를 사용한다.
인수 (객체,프로퍼티 키 , 디스크립터 객체) 를 받고 정의를 하면된다.
const person = {
name: 'kim'
}
console.log(Object.getOwnPropertyDescriptor(person,'name'));
//{ value: 'kim', writable: true, enumerable: true, configurable: true }
Object.defineProperty(person,'name',{
value: 'kim',
writable: false,
enumerable: false,
configurable: true
})
console.log(Object.getOwnPropertyDescriptor(person,'name'));
//{value: 'kim',writable: false,enumerable: false,configurable: true}
한번에 정의하려면 Object.defineProperties
위와 같이 데이터프로퍼티 직접 정의할수 있지만.
객체안의 모든 프로퍼티값을, 또 재정의 자체를 막아버리는 메서드도 존재한다.
구분 | 메서드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 재정의 |
---|---|---|---|---|---|---|
객체 확장 금지 | Object.preventExtensions | X | O | O | O | O |
객체 밀봉 | Object.seal | X | X | O | O | X |
객체 동결 | Object.freeze | X | X | O | X | X |
프리벤트 익스텐션을 사용하면 프로퍼티 동적추가 , Object.defineProperty 가 금지된다.
아래로 내려갈수록 강도가 세지며 freeze 까지 내려가면 읽기만 가능한 객체가 된다.
객체에 위의 메서드가 사용 됬는지 확인하려면
isExtensible
isSealed
isFrozen
를 사용한다.
🧨객체 변경 방지는 얕은 변경만 된다. 객체 안의 객체까지 효과를 줄 수 없기 때문에
재귀적 함수 호출로 깊은 변경을 이용해야 한다.
객체를 만드는 방법은 2가지가 있다.
객체리터럴
- const a = { }
생성자 함수
- csont b = new Object();
리터럴 방식은 간단하지만 프로퍼티 값과 메서드를 계속 적어줘야 한다.
동일한 코드는 항상 배제해야 하이 때문에 쉽게 만들수 있는 생성자함수(템플릿)를 사용한다.
// 생성자 함수
function Circle(radius) {
// 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
// 인스턴스의 생성
const circle1 = new Circle(5); // 반지름이 5인 Circle 객체를 생성
const circle2 = new Circle(10); // 반지름이 10인 Circle 객체를 생성
console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20
🔨여기서 this 란? 자기 참조 변수를 뜻한다.
또 호출 방식에 따라 바인딩 되는 값이 다르다.
함수 호출 방식 | this가 가르키는 값 |
---|---|
일반 함수로 호출 | 전역 객체 |
메서드로 호출 | 메서드를 호출한 객체 |
생성자 함수로 호출 | 생성자 함수가 만들 인스턴스 |
생성자 함수를 사용하면 다음과 같은 일이 벌어진다.
인스턴스 생성과 this 바인딩
인스턴스 초기화
인스턴스 반환
🧨생성자 함수는 자동으로 return이 되기때문에 return 문을 생략 해야된다.
리턴문 뒤에 원시값을 넣으면 무시가 되지만
리턴뒤에 새로운 객체를 명시하면 명시한 객체가 리턴된다.
함수 와 객체의 차이점은 뭘까?
함수는 호출 가능한 객체 이고 객체는 호출 불가능한 객체다.
따라서 모든 함수는 내부 메서드로 call을 가지고있다.
그렇다면 construct 는 뭐지?
함수앞에 new 를 붙이면 생성자 함수가 되곤 했다. 하지만 정확히 구분하자면 다음과 같다.
constructor - 함수 선언문
, 함수 표현식
, 클래스
non-constructor - 축약형 메서드
, 화살표 함수
콜론과 펑션을 생략한 축약형 메서드 나 화살표 함수를 생성자함수로 호출하게 되면 오류가 발생한다.
// 일반 함수 정의: 함수 선언문, 함수 표현식
function foo() {}
const bar = function () {};
// 프로퍼티 x의 값으로 할당된 것은 일반 함수로 정의된 함수다. 이는 메서드로 인정하지 않는다.
const baz = {
x: function () {}
};
// 일반 함수로 정의된 함수만이 constructor이다.
new foo(); // -> foo {}
new bar(); // -> bar {}
new baz.x(); // -> x {}
// 화살표 함수 정의
const arrow = () => {};
new arrow(); // TypeError: arrow is not a constructor
// 메서드 정의: ES6의 메서드 축약 표현만을 메서드로 인정한다.
const obj = {
x() {}
};
new obj.x(); // TypeError: obj.x is not a constructor
new 를 붙이느냐 마느냐에 따라 생성자가 되기도 일반함수가 되기도 한다.
그렇다면 정의된 함수만 보고 생성자함수로 사용될 예정인지 아닌지를 어떻게 구분할수있을까?
개발자들은 생성자 함수에는 파스칼케이스 컨벤션을 사용하기로 한다.
대문자로 시작하는 함수들은 생성자 함수라고 약속 한것
그럼에도 실수가 발생한다면? 이때
new.target 을 사용하면 new 연산자로 호출됬는지 아닌지를 감지할수 있다.
// 생성자 함수
function Circle(radius) {
// 이 함수가 new 연산자와 함께 호출되지 않았다면 new.target은 undefined다.
if (!new.target) {
// new 연산자와 함께 생성자 함수를 재귀 호출하여 생성된 인스턴스를 반환한다.
return new Circle(radius);
}
this.radius = radius;
this.getDiameter = function () {
return 2 * this.radius;
};
}
// new 연산자 없이 생성자 함수를 호출하여도 new.target을 통해 생성자 함수로서 호출된다.
const circle = Circle(5);
console.log(circle.getDiameter());