19. 프로퍼티
객체지향 프로그래밍
- 객체의 집합으로 프로그램을 표현하려는 패러다임
- 실세계의 실체(속성)를 인식하는 사고를 프로그래밍에 접목하려는 시도에서 시작
- 다양한 속성 중에서 프로그램에 필요한 속성만 간추려 표현하는 것이
추상화
상태를 나타내는 데이터
와 상태 데이터를 조작할 수 있는 동작
을 하나의 단위로 묶어 생각
상속과 프로토타입
프로토타입 객체
- 객체 간 상속을 구현하기 위해 사용
- 어떤 객체의 상위 객체의 역할을 하는 객체
- 다른 객체에 공유 프로퍼티 제공
[[Prototype]]
내부 슬롯 보유 : 프로토타입의 참조 (객체가 생성될 때 결정되어 저장)
- 직접 접근 불가
__ proto __
를 통해 간접 접근 가능
- constructor 프로퍼티를 통해 생성자 함수에 접근 가능 → 생성자 함수는 prototype 프로퍼티를 통해 프로토타입에 접근 가능
__proto__
접근자 프로퍼티
[[Get]]
, [[Set]]
프로퍼티 어트리뷰트를 통해 프로토타입 취득, 할당 가능
- 객체가 직접 소유하는 프로퍼티가 아니라
Object.prototype
의 접근자 프로퍼티이다.
- 따라서 모든 객체를 상속을 통해
Object.prototype.__proto__
사용 가능
- 접근자 프로퍼티를 사용하는 이유 : 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위함 (프로토타입 체인은 단방향 링크드 리스트로 구현되어야 함)
- 코드 내에서 직접 사용하는 것을 권장하지 않음 : ES6에서 표준으로 채택, 모든 객체가 사용할 수 있는게 아님
- 따라서
Object.getPrototypeOf
, Object.setPrototypeOf
사용
- 함수 객체의 prototype 프로퍼티
- 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입 가리킴
- non-constructor인 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드는 prototype 프로퍼티를 소유하지 않음
- 일반 함수도 prototype 프로퍼티를 소유하지만 의미 없음
-
구분 |
소유 |
값 |
사용 주체 |
사용 목적 |
_ proto _접근자 프로퍼티 |
모든 객체 |
프로토타입의 참조 |
모든 객체 |
객체가 자신의 프로토타입에 접근 또는 교체하기 위해 사용 |
protype프로퍼티 |
constroctor |
프로토타입의 참조 |
생성자 함수 |
생성자 함수가 자신이 생성할 객체(인스턴스)의 프로토타입을 할당하기 위해 사용 |
리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
- 리터럴 표기법에 의해 생성된 객체 : 프로토타입의 constructor 프로퍼티가 가리키는 생성자 함수가 반드시 객체를 생성한 함수라고 단정할 수 없음
-
// 2. Object 생성자 함수에 의한 객체 생성
// 인수가 전달되지 않았을 때 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성
let obj = new Object();
console.log(obj); // {}
// 1. new.target이 undefined나 Object가 아닌 경우
// 인스턴스 -> Foo.prototype -> Object.prototype 순으로 프로토타입 체인이 생성됨
class Foo extends Object {}
new Foo(); // Foo {}
// 3. 인수가 전달된 경우에는 인수를 객체로 변환
// Number 객체 생성
obj = new Object(123);
console.log(obj); // Number {123}
// String 객체 생성
obj = new Object("123");
console.log(obj); // String {"123"}
- 따라서 객체 리터럴에 의해 생성된 객체는 Object 생성자 함수가 생성한 객체가 아니다.
-
// foo 함수는 Function 생성자 함수로 생성한 함수 객체가 아니라 함수 선언문으로 생성했다.
function foo() {}
// 하지만 constructor 프로퍼티를 통해 확인해보면 함수 foo의 생성자 함수는 Function 생성자 함수다.
console.log(foo.constructor === Function); // true
- 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재
-
리터럴 표기법 |
생성자 함수 |
프로토타입 |
객체 리터럴 |
Object |
Object.prototype |
함수 리터럴 |
Function |
Function.prototype |
배열 리터럴 |
Array |
Array.prototype |
정규 표현식 리터럴 |
RegExp |
RegExp.prototype |
프로토타입의 생성 시점
- 생성자 함수가 생성되는 시점에 생성
- 사용자 정의 생성자 함수
- 함수 선언문은 런타임 이전에 먼저 실행되므로 프로토타입도 그때 생성됨
Object.prototype
- 빌트인 생성자 함수 (Object, String, Number, Function, Array, RegExp, Date, Promise)
객체 생성 방식과 프로토타입의 결정
- 종류
- 객체 리터럴
- Object 생성자 함수
- 생성자 함수
- 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체를 인수로 전달
- Object. create 메서드
- 클래스 (ES6)
- 모두 추상 연산인 OrdinaryObjectCreate에 의해 생성
- 빈 객체 생성 → 인수로 받은 프토퍼티를 객체에 추가 → [[Prototype]] 내부 슬롯에 할당 → 생성한 객체 반환
- 전달되는 인수에 의해 결정
- 인수는 객체가 생성되는 시점에 결정
프로토타입 체인
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`); // true
};
const me = new Person("Lee");
// hasOwnProperty 는 Object.prototype의 메서드이다.
console.log(me.hasOwnProperty("name")); // true
-
- 먼저 hasOwnProperty 메서드를 호출한 me 객체에서 hasownProperty 메서드를 검색한다. me 객체에는 hasownProperty 메서드가 없으므로 프로토타입 체인을 따라. 다시 말해 [[Prototype]] 내부 슬롯에 바인딩되어 있는 프로토타입(위 예제의 경우 Person. prototype)으로 이동하여 hasownProperty 메서드를 검색한다.
- Person.prototype에도 hasOwnProperty 메서드가 없으므로 프로토타입 체인을 따라, 다시 말해 [[Prototype]] 내부 슬롯에 바인딩되어 있는 프로토타입(위 예제의 경우 Object. prototype)으로 이동하여 hasOwnProperty 메서드를 검색한다.
- Object.prototype에는 hasOwnProperty 메서드가 존재한다. 자바스크립트 엔진은 Object.prototype.hasOwnProperty 메서드를 호출한다. 이때 Object.prototype.hasOwnProperty 메서드의 this에는 me 객체가 바인딩된다.
- Object.prototype 을 프로토타입 체인의 종점이라 함 → [[Prototype]] 내부 슬롯의 값은 null 이다.
- 이처럼 JS 엔진은 프로토타입 체인을 따라 프로퍼티 / 메서드를 검색함
- 프로토타입 체인은
상속
과 프로퍼티 검색
을 위한 메커니즘
오버라이딩과 프로퍼티 섀도잉
- 오버라이딩
- 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
- 프로퍼티 섀도잉
- 하위 객체를 통해 프로토타입의 프로퍼티를 변경 or 삭제하는 것은 불가능
프로토타입의 교체
- 생성자 함수 또는 인스턴스에 의해 교체 가능
- 생성자 함수
- 생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
- constructor 프로퍼티와 생성자 함수 간의 연결이 파괴됨
- 연결을 되살리기 위한 방법 : 교체한 객체 리터럴에 constructor 프로퍼티 추가
- 인스턴스
- 이미 생성된 객체의 프로토타입을 교체
- constructor 프로퍼티와 생성자 함수 간의 연결이 파괴됨
- 연결을 되살리기 위한 방법 : 교체한 객체 리터럴에 constructor 프로퍼티 추가
- 프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 번거롭다.
- 따라서 프로토타입은 직접 교체하지 않는 것이 좋다.
instanceOf 연산자
- 이항 연산자로서 우변 생성자 함수 prototype에 바인딩된 객체가 좌변 객체 프로토타입 체인에 존재하면 true
- 프로토타입을 교체하여 프로토타입과 생성자 함수간의 연결이 파괴되면 false로 됨
function Person(name) {
this.name = name;
}
const me = new Person("Lee");
const parent = {};
Object.setPrototypeOf(me, parent);
console.log(Person.prototype === parent); // false
console.log(parent.constructor === Person); // false
console.log(me instanceof Person); // false
console.log(me instanceof Object); // true
- 프로토타입을 교체하여 constructor 프로퍼티와 생성자 함수간의 연결이 파괴되어도 영향 없음
const Person = (function () {
function Person(name) {
this.name = name;
}
Person.prototype = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
},
};
return Person;
})();
const me = new Person("Lee");
console.log(me.constructor === Person); // 1
console.log(me instanceof Person); // 2
console.log(me instanceof Object); // 3
직접 상속
- Object.create
- 마찬가지로 추상 연산 OrdinaryObjectCreate 호출
- 매개변수
- 생성할 객체의 프로토타입으로 지정할 객체
- 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이루어진 객체 (생략 가능)
- 첫 번째 매개변수에 전달한 프로토타입 체인에 속하는 객체를 생성 (객체를 생성하면서 직접 상속 구현)
- new 연산자 없이 객체 생성 가능
- 프로토타입 지정하면서 객체 생성 가능
- 객체 리터럴에 의해 생성된 객체도 상속받을 수 있음
- _ _ proto _ _
- 앞선 Object.create 에서는 두 번째 인자로 프로퍼티를 정의하기 어려움
- ES6에서는 객체 리터럴 내부에서 _ _ proto _ _ 를 사용해 직접 상속 구현 가능
정적 프로퍼티 / 메서드
프로퍼티 존재 확인
- in
- 객체 내에 특정 프로퍼티가 존재하는지 확인 (상속받은 것도 확인 ex) toString)
const person = {
name: "Lee",
address: "Seoul",
};
//person 객체에 name 프로퍼티가 존재한다.
console.log("name" in person); // true
// person 객체에 address 프로퍼티가 존재한다.
console.log("address" in person); // true
//person 객체에 age 프로퍼티가 존재하지 않는다.
console.log("age" in person); // false
- ES6에서 도입된 Reflect.has 사용 가능
const Person = { name: "Lee" };
console.log(Reflect.has(Person, "name")); // true
console.log(Reflect.has(Person, "toString")); // true
- Object.prototype.hasOwnProperty
프로퍼티 열거
- for … in 문 사용
- for (변수 선언문 in 객체) { … }
- 프로퍼티 개수만큼 순회 ( [[Enumerable]] 값이 true인 프로퍼티만 순회)
- Object.prototype의 프로퍼티는 열거되지 않음
- 상속받은 프로퍼티도 열거
- 객체 자식의 고유 프로퍼티만 열거하기 위한 방법
- Object.keys/values/entries(키와 값의 쌍 배열) 메서드 사용 권장