19.1 객체지향 프로그래밍
객체는 데이터의 상태와 동작을 하나의 논리적인 단위로 묶은 자료구조다. 이러한 객체의 상태 데이터를 property, 동작을 method 라고 부른다.
19.2 상속과 프로토타입
상속은 중복된 내용을 재 사용하기 위한 방법이며, JS는 프로토타입을 기반으로 상속을 구현한다. 이를통해 동일한 메서드는 상속을 통해 자산을 공유한다.
19.3 프로토타입 객체
[[prototype]] 에는 직접 접근할 수 없지만 __proto__ 접근자 프로퍼티를 통해 간접적으로 접근할 수 있다.
19.3.1 __proto__ 접근자 프로퍼티
__proto__ 접근자 프로퍼티를 통해 [[Prototype]] 내부 슬롯이 가리키는 객체인 Object.prototype에 접근할 수 있다. Object.prototype는 자체적으로 값을 갖지 않고 접근자 함수(Get, Set), 즉 프로퍼티 속성으로 구성된 프로퍼티다.
__proto__ 접근자 프로퍼티는 객체가 직접 가지고 있는 것이 아닌 Object.prototype에서 상속받은 프로퍼티다.
이렇게 복잡한 방식을 사용하는 이유는 프로토타입 체인이 생성되는 것을 방지하기 위해서다. 만약 서로의 __proto__ 접근자 프로퍼티에 서로 상대를 넣게된다면 순환참조가 발생한다. 이러항 방식을 막기 위해서 __proto__ 접근자 프로퍼티통해 프로토타입에 접근하고 교체하도록 되어있다.
__proto__ 접근자 프로퍼티는 직접 사용하는 것을 권장하지 않으며, Object.getPrototypeOf이나 Obejct.setPrototypeOf를 사용할 것을 rnjsgkrh dlTek.
19.3.2 함수 객체의 prototype 프로퍼티
함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킨다. 객체를 생성하지 않는 일반 함수의 prototype 프로퍼티는 아무런 의미가 없으며, 다른 객체의 __proto__ 접근자 프로퍼티와 함수 객체의 prototype 프로퍼티는 동일한 프로토타입을 가리킨다.
19.3.3 프로토타입의 contstructor 프로퍼티와 생성자 함수
모든 프로토타입은 contstructor 프로퍼티를 가지며, 이는 자신을 참조하고 있는 생성자 함수를 가리킨다.
function Person(name){
this.name = name;
}
const me = new Person('123');
console.log(me.constructor === Person) // true
JavaScript
복사
19.4 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입
리터럴 표기법과 같은 객체 생성 방식과 같이 명시적으로 new를 붙이지 않은 경우에도 프로토타입이 존재하며 Object가 생성자함수가 된다.
const obj = {}
console.log(obj.constructor === Object); // true
JavaScript
복사
단 빈 new Object()의 경우에는 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하는데 리터럴평가는 이점에서는 동일하나, new.target의 확인이나 프로퍼티를 추가하는 처리등 세부내용이 달라 리터럴에 의해 생성된 객체는 Object가 생성한 객체가 아니다.
함수의 경우 Function을 통해 만들면 렉시컬스코프도없고, 클로저도 만들지 않지만 constructor 프로퍼티를 통해 확인해보면 함수의 생성자함수는 Function이다.
function foo(){}
console.log(foo.constructor === Function) // true
JavaScript
복사
19.5 프로토타입의 생성 시점
프로토타입은 생성자 함수는 쌍으로 존재해야하기 때문에 동시에 생성된다.
19.5.1 사용자 정의 생성자 함수와 프로토타입 생성 시점
생성자 함수로서 호출할 수 없는 함수 즉 non-constructor는 프토로 타입이 생성되지 않는다. 함수 선언문은 JS 엔진에 의해 먼저 싱핼되는데 이때 프로토타입도 같이 생성된다.
19.5.2 빌트인 생성자 함수와 프로토타입 생성 시점
Object. String 등과 같은 빌트인 생성자 함수도 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성되는데ㅐ 이는 전역 객체가 생성될 때 생성된다.
19.6 객체 생성 방식과 프로토타입의 결정
프로토 타입은 추상 연상 OridinaryObjectCreate에 전달된는 인수에 의해 결정된다.
19.6.1 객체 리터럴에 의해 생성된 객체의 프로토타입
객체 리터럴시 추상 연산 OrdinaryObjectCreate를 호출한다.
따라서 아래 그림과 같은 관계가 생성된다.
19.6.2 Object 생성자 함수에 의해 생성된 객체의 프로토타입
Object 생성자 함수를 인수 없이 호출하면 리터럴과 마찬가지로 OrdinaryObjectCreate가 호출되는데 이때 전달되는 프로토 타입은 Obejct.prototype이다. 즉 객체 리터럴과 같은 구조가 만들어진다.
19.6.3 생성자 함수에 의해 생성된 객체의 프로토타입
생성자 함수에 의해 생성된 객체의 경우 prototype이 constructor뿐인데 여기에 Person.prototype을 통해 프로토타입에도 프로퍼티를 추가/삭제할 수 있다.
19.7 프로토타입 체인
function Person(name){
this.name = name;
}
Person.prototype.sayHello = function(){
console.log(`helloe ${name}`);
}
const me = new Peerson('Yoon');
console.log(me.hasOwnProperty('name'));
JavaScript
복사
이는 me가 Person.prototype과 Object.prototype을 상속받았다는 것을 의미한다.
Object.getPrototypeOf(me) === Person.prototype; // true
Object.getPrototypeOf(Person.prototype) === Object.prototype; // true;
JavaScript
복사
객체에 접근하려는 프로퍼티가 없을 시 부모를 타고 올라가는데 이를 프로토타입 체인이라 한다.
이때 객체의 최상위는 항상 Object.prototype인데 이를 프로토타입 체인의 종점이라고 한다. 이때 Object.prototype의 프로토타입은 null이다.
프로토타입 체인은 상속과 프로퍼티 검색을 위해 존재하고, 스코프 체인은 식별자 검색을 위해 존재한다.
19.8 오버라이딩과 프로퍼티 섀도잉
프로토타입의 인스턴스는 매서드 인스턴스에 의해 가려지는데 오버라이딩된 것을 실행하기 때문이다. 이를 프로퍼티 섀도잉이라고 한다.
오버라이딩 : 상위 클래스에 있는 메서드를 하위가 재정의
오버로딩 : 함수 이름이 같지만 매개변수의 타입이나 갯수가 달라 구별하여 호출하는 방식
또한 프로토타입 프로퍼티는 하위 객체에서 get은 허용되나 set이 허용되지 않는다.
19.9 프로토타입의 교체
19.9.1 생성자 함수에 의한 프로토타입의 교체
const Person = (function(){
function Person(name){
this.name = name;
}
Person.prototype = {
sayHello(){
console.log(`Hi, ${this.name}`);
}
};
return Person;
}());
const me = new Person('Yoon');
console.log(me.constructor === Person); // false
console.log(me.constructor === Object); // true
JavaScript
복사
그림으로 나타내면 아래와 같다.
프로토타입으로 교체한 객체 리터럴에는 constructor 프로퍼티가 없다. 따라서 me객체의 생성자 함수를 검색하면 Person이 아닌 Object가 나온다.
그런데 만약 교체한 객체 리터럴에 constructor 프로퍼티를 추가하면 프로토타입의 constructor가 살아난다.
const Person = (function(){
function Person(name){
this.name = name;
}
Person.prototype = {
constructor: Person,
sayHello(){
console.log(`Hi, ${this.name}`);
}
};
return Person;
}());
const me = new Person('Yoon');
console.log(me.constructor === Person); // true
console.log(me.constructor === Object); // false
JavaScript
복사
19.9.2 인스턴스에 의한 프로토타입의 교체
function Person(name) {
this.name = name;
}
const me = new Person('me');
const parent = {
sayHello(){
console.log('hi');
}
};
Object.setPrototypeOf(me, parent);
me.sayHello();
JavaScript
복사
위 처럼 me객체의 프로토 타입을 교체할 수있다. 이는 생성자 함수에 의한 프로토타입 교체와 인스턴스에 의한 프로토타입 교체는 별 다른 차이가 없어보이지만 미묘한 차이가 있다.
인스턴스에 의해 프로토타입을 교체시 기존 생성자 함수와의 연결이 끊키게 된다. 이는 parent의 constructor를 Person으로 지정하는 방법을 통해서 해결 가능하지만 직접 건드는 것은 피하는 것이 좋다.
19.10 instanceof 연산자
객체 instanceof 생성자 함수
우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true아니라면 false가 나온다. 이는 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다.
const Person = (function(){
function Person(name){
this.name = name;
}
return Person;
}());
const me = new Person('Yoon');
console.log(me.constructor === Person); // true
JavaScript
복사
const Person = (function(){
function Person(name){
this.name = name;
}
Person.prototype = {
sayHello(){
console.log(`Hi, ${this.name}`);
}
};
return Person;
}());
const me = new Person('Yoon');
console.log(me.constructor === Person); // false
JavaScript
복사
19.11 직접 상속
19.11.1 Object.create에 의한 상속
Object.create 매서드는 명시적으로 프로토타입을 지정하여 새로운 객체를 생성한다. 여기서도 OrdinaryObejctCreate를 호출한다.
/**
* @param {Object} prototype - 생성할 객체의 프로토타입으로 지정할 객체
* @param {Object} [propertiesObject] - 생성할 객체의 프로퍼티를 갖는 갳체
* @returns {Object} 지정된 프로토타입 및 프로퍼티를 갖는 새로운 객체
*/
Object.create(prototype[, propertiesObject])
JavaScript
복사
// 프로토타입이 null인 객체를 생성하는데 이는 프로토타입 체인의 종점에 위치한다.
// obj = null
let obj = Object.create(null);
console.log(Object.getPrototypeOf(obj) === null); // true
console.log(obj.toString()); // TypeError: obj.toString is not a function
// obj -> Object.prototype -> null
// obj = {}; 와 동일
obj = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
// obj -> Object.prototype -> null
// obj = {x: 1};와 동일
obj = Object.create(Object.prototype, {
x: { value: 1, writable: true, enumerable: true, configureable: true }
});
// obj = Object.create(Object.prototype);
// obj.x = 1; 과 동일하다.
console.log(obj.x); // 1
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
JavaScript
복사
이처럼 Object.create 매서드는 첫 번째 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체를 생섭합니다. 객체를 생성하며서 직접적으로 상속을 구현한 것입니다.
•
new 연산자 없지객체 생성가능
•
프로토타입을 지정하면서 생성가능
•
객체 리터럴에 의해 생성된 객체도 상속 가능
19.11.2 객체 리터럴 내부에서 __proto__에 의한 직접 상속
ES6에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용하여 직접 상속을 구현할 수 있다.
const myProto = { x : 10 };
const obj = {
y: 20;
__proto__: myProto
};
JavaScript
복사
19.12 정적 프로퍼티/메서드
정적 프로퍼티/메서드는 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 말한다.
Person.staticProp = 'static prop';
const me = new Person('lee');
Person.staticMethod(); // staticMethod
me.staticMethod(); // TypeError: me.staticMethod is not a function
JavaScript
복사
체인에 속한 객체의 프로퍼티/메서드가 아니라 인스턴스로 접근할 수 없다.
function Foo(){}
Foo.prototype.x = function(){ console.log('x') };
const foo = new Foo();
foo.x(); // x
Foo.x = function(){ console.log('x') };
// 정적 메서드는 인스턴스를 생성하지 않아도 호출할 수 있다.
Foo.x(); // x
JavaScript
복사
19.13 프로퍼티 존재 확인
19.13.1 in 연산자
in 연산자는 객체 내에 특정 프로퍼티가 존재하는지 여부를확인한다.
/**
* key: 프로퍼티 키를 나타내는 문자열
* object: 객체로 평가되는 표현식
*/
key in object
JavaScript
복사
당연하게도 상속받은 모든 프로토타입의 프로퍼티를 확인한다.
ES6에서 도입된 Reflect.has 메서드를 사용할 수도 있으며 in과 동일하게 동작한다.
19.13.2 Object.prototype.hasOwnProperty 메서드
특정 프로퍼티가 존재하는지 확인할 수 있다. 단 인수로 전달받은 프로퍼티 키가 객체 고유의 ㅍ로퍼티 키인 경우에만 true를 반환하고 상속받은 경우 false를 반환한다.
19.14 프로퍼티 열거
19.14.1 for…in 문
객체의 모든 프로퍼티 중 [[Enumberable]] 값이 ture인 경우를 순회하며 열거한다
for(변수 선언문 in 객체) {...}
JavaScript
복사
만약 상속 받은 프로퍼티를 제외하고 싶다면 Object.prototype.hasOwnPropery메서드를 사용하여 객체 자신의 프로퍼티인지 확인해야한다.
const Person = {
name: 'Lee',
address: 'Seoul',
__proto__: { age: 20 }
}
for(const key in person){
if(!person.hasOwnProperty(key)) continuse;
console.log(key + ":" + person[key]);
}
// name: Lee
// address: Seoul
JavaScript
복사
•
프로퍼티키가 심벌인 경우는 열거하지 않는다.
•
순서가 보장되지는 않지만, 대부분의 브라우저에선 순서를 보장하고 숫자는 정렬한다.
for…in의 경우 상속받은 프로퍼티도 열거하기 때문에 Object.keys/values/entries를 사용하기를 권장한다.