CH19. 프로토타입
19.1 객체지향 프로그래밍
- 속성을 통해 여러 개의 값을 하나의 단위로 구성한 복합적인 자료구조를 객체라고 함
- 객체지향 프로그래밍은 객체의 상태를 나타내는 데이터와
상태 데이터를 조작할 수 있는 동작을 하나의 논리적인 단위로 묶어 생각함
- 객체의 상태 데이터를 프로퍼티, 동작을 메서드라고 함
19.2 상속과 프로토타입
- 자바스크립트는 프로토타입을 기반으로 상속을 구현하여 불필요한 중복을 제거함
- 중복을 제거하는 방법? 기존의 코드를 적극적으로 재사용하는 것 -> 상속
// 생성자 함수
function Circle(radius) P
this.radius = radius;
}
Circle.prototype.getArea = function() {
return Math.PI * this.radius ** 2;
};
const circle1 = new Circle(1);
const circle2 = new Circle(2);
console.log(circle1.getArea === circle2.getArea);
console.log(circle1.getArea());
console.log(circle2.getArea());
19.3 프로토타입 객체
- 객체 간 상속을 구현하기 위해 사용하는 프로토타입 객체
- 프로토타입을 상속받은 하위 객체는 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용 가능
- 모든 객체는 [[Prototype]]이라는 내부 슬롯을 가짐
- [[Prototype]]에 저장되는 프로토타입은 객체 생성 방식에 의해 결정됨
- [[Prototype]] 내부 슬롯에는 접근할 수 없지만, __proto__ 접근자 프로퍼티를 통해 간접적 접근 가능
19.3.1 __proto__ 접근자 프로퍼티
1. __proto__는 접근자 프로퍼티
2. __proto__ 접근자 프로퍼티는 상속을 통해 사용됨
3. __proto__ 접근자 프로퍼티를 통해 프로토타입에 전근하는 이유?
: 상호 참조에 의해 프로토타입 체인이 생성되는 것을 방지하기 위함
4. __proto__ 접근자 프로퍼티를 코드 내에서 직접 사용하는 것은 권장하지 않음
: 모든 객체가 __proto__ 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문
19.3.2 함수 객체의 prototype 프로퍼티
- 함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킴
- Object.prototype으로부터 상속받은 __proto__ 접근자 프로퍼티와
함수 객체만이 가지고 있는 prototype 프로퍼티는 결국 동일한 프로토 타입을 가리킴
19.3.3 프로토타입의 constructor 프로퍼티와 생성자 함수
- 모든 프로토타입이 가진 constructor 프로퍼티는 prototype 프로퍼티로 자신을 참조하고 있는 생성자 함수를 가리킴
- 이 연결은 생성자 함수가 생성될 때 이뤄짐
// 생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
console.log(me.constructor === Person);
19.4 리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토 타입
- constructor 프로퍼티가 가리키는 생성자 함수는 인스턴스를 생성한 생성자 함수
const obj = new Object();
console.log(obj.constructor === Object);
const add = new Function('a', 'b', 'return a + b');
console.log(add.constructor === Function);
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
console.log(me.constructor === Person);
- 명시적으로 new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하지 않는 객체 생성 방식도 있음
const obj = {};
const add = function (a, b) {return a + b; };
const arr = {1, 2, 3};
const regxp = /is/ig;
- 객체 리터럴이 평가될 때는 추상 연산 OrdinaryObjectCreate를 호출하여 빈 객체를 생성하고 프로퍼티를 추가하도록 정의되어 있음
- 프로토 타입과 생성자 함수는 단독으로 존재할 수 없고 항상 쌍으로 존재함
- 리터럴 표기법에 의해 생성된 객체는 생성자 함수에 의해 생성된 객체가 아님
19.5 프로토타입의 생성 시점
19.5.1 사용자 정의 생성자 함수와 프로토타입 생성 시점
- 생성자 함수로서 호출할 수 있는 함수는 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성됨
console.log(Person.prototype);
function Person(name) {
this.name = name;
}
- 생성자 함수로서 호출할 수 없는 non-constructor는 프로토타입이 생성되지 않음
const Person = name => {
this.name = name;
};
console.log(Person.prototype);
19.5.2 빌트인 생성자 함수와 프로토타입 생성 시점
- 빌트인 생성자 함수도 일반 함수와 마찬가지로 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성됨
- 모든 빌트인 생성자 함수는 전역 객체가 생성되는 시점에 생성됨
- 생성된 프로토 타입은 빌트인 생성자 함수의 prototype 프로퍼티에 바인딩됨
- 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 [[Prototype]] 내부 슬롯에 할당됨
: 프로토 타입 상속
19.6 객체 생성 방식과 프로토 타입의 결정
19.6.1 객체 리터럴에 의해 생성된 객체의 프로토 타입
- 자바스크립트 엔진은 객체 리터럴을 평가하여 객체를 생성할 때 추상 연산을 호출함
- 객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype
const obj = {x: 1};
console.log(obj.constructor === Object);
console.log(obj.hasOwnProperty('x'));
19.6.2 Object 생성자 함수에 의해 생성된 객체의 프로토 타입
- Object 생성자 함수를 인수 없이 호출하면 빈 객체가 생성됨
- 일단 빈 객체를 생성한 이루 프로퍼티를 추가해야 함
const obj = new Object();
obj.x = 1;
console.log(obj.constructor === Object);
console.log(obj.hasOwnProperty('x'));
19.6.3 생성자 함수에 의해 생성된 객체의 프로토 타입
- 다른 객체 생성 방식과 마찬가지로 추상 연산이 호출됨
- 생성자 함수에 의해 생성되는 객체의 프로토 타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${this.name}`);
};
const me = new Person('Lee');
const you = new Person('Kim');
me.sayHello();
you.sayHello();
19.7 프로토타입 체인
프로토타입 체인?
: 자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면
[[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색함
- 프로토타입 체인은 자바스크립트가 객체지향 프로그래밍의 상속을 구현하는 메커니즘
- Object.prototype을 프로토타입 체인의 종점
- 프로토타입 체인의 종점에서도 프로퍼티를 검색할 수 없는 경우 undefined 반환
- 이때 에러가 발생하지 않음
console.log(me.foo); // undefined
- 프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘
- 스코프 체인은 식별자 검색을 위한 메커니즘
- 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라
서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용됨
19.8 오버라이딩과 프로퍼티 섀도잉
? 오버라이딩
: 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
? 오버로딩
: 함수의 이름은 동일하지만 매개변수의 타입 또는 개수가 다른 메서드를 구현하고 매개변수에 의해 메서드를 구별하여 호출하는 방식. 자바스크립트는 오버로딩을 지원하지 않지만, arguments 객체를 사용하여 구현할 수는 있음
? 프로퍼티 섀도잉
: 상속 관계에 의해 프로퍼티가 가려지는 현상
19.9 프로토타입의 교체
- 부모 객체인 프로토타입을 동적으로 변경 가능
- 객체 간의 상속 관계를 동적으로 변경 가능
- 프로토타입은 생성자 함수 또는 인스턴스에 의해 교체할 수 있음
19.9.1 생성자 함수에 의한 프로토타입의 교체
- 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴됨
- 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살림
const Person = (function () {
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
return Person;
} ());
const me = new Person('Lee');
console.log(me.constructor === Person);
console.log(me.constructor === Object);
19.9.2 인스턴스에 의한 프로토타입의 교체
- 프로토타입 교체를 통해 객체 간의 상속 관계를 동적으로 변경하는 것은 꽤나 번거로움
- 프로토 타입은 직접 교체하지 않는 것이 좋음
19.10 instanceof 연산자
- 이항 연산자로, 좌변에 객체를 가리키는 식별자, 우변에 생성자 함수를 가리키는 식별자를 피연산자로 받음
- 만약 우변의 피연산자가 함수가 아닌 경우 TypeError 발생함
- 생성자 함수의 prototype에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인함
- 생성자 함수에 의해 프로토타입이 교체되어 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴되어도 생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결은 파괴되지 않으므로 instanceof는 아무런 영향을 받지 않음
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); // false
console.log(me instanceof Person); // true
console.log(me instanceof Object); // true
19.11 직접 상속
19.11.1 Object.create에 의한 직접 상속
- 명시적으로 프로토타입을 지정하여 새로운 객체를 생성함
- 첫 번째 매개변수에는 생성할 객체의 프로토타입으로 지정할 객체를 전달함
- 두 번째 매개변수에는 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체를 전달함
- 두 번째 인수는 옵션이므로 생략 가능
장점?
1. new 연산자가 없이도 객체 생성 가능
2. 프로토타입을 지정하면서 객체 생성 가능
3. 객체 리터럴에 의해 생성된 객체도 상속받기 가능
19.11.2 객체 리터럴 내부에서 __proto__ 에 의한 직접 상속
- Object.create 메서드에 의한 직접 상속은 여러 장점이 있지만, 두 번째 인자로 프로퍼티를 정의하는 것은 번거로움
- ES6에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용하여 직접 상속을 구현할 수 있음
19.12 정적 프로퍼티/메서드
정적 프로퍼티/메서드?
: 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드
- Person생성자 함수는 객체이므로 자신의 프로퍼티/메서드를 소유할 수 있음
- 이렇게 소유한 프로퍼티/메서드를 정적 프로퍼티/메서드라고 함
- 이는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없음
- 생성자 함수가 생성한 인스턴스는 자신의 프로토타입 체인에 속한 객체의 프로퍼티/메서드에 접근할 수 있음
- 정적 프로퍼티/메서드는 인스턴스의 프로토타입 체인에 속한 객체의 프로퍼티/메서드가 아니므로 인스턴스로 접근 불가
const obj = Object.create({name: 'Lee'});
obj.hasOwnProperty('name'); // false
- 프로토타입 프로퍼티/메서드를 표기할 때 prototype을 #으로 표기하는 경우도 있음
ex) Object.prototype.isPrototypeOf 를 Object#isPrototypeOf
19.13 프로퍼티 존재 확인
19.13.1 in연산자
- 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인
- 확인 대상 객체의 프로퍼티뿐만 아니라 확인 대상 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인하므로 주의
- in 연산자 대신 ES6에 도입된 Reflect.has 메서드를 사용할 수도 있음 (동일하게 작동)
const person = {name: 'Lee'};
console.log(Reflect.has(person, 'name'));
console.log(Reflect.has(person, 'toString'));
19.13.2 Object.prototype.hasOwnProperty 메서드
- 객체에 특정 프로퍼티가 존재하는지 확인할 수 있는 또다른 메서드
- 인수로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true 반환
- 상속받은 프로토타입의 프로퍼티 키인 경우 false를 반환
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('age')); // false
19.14 프로퍼티 열거
- 객체의 모든 프로퍼티를 순회하며 열거하기 위함
19.14.1 for ... in 문
- 객체의 프로퍼티 개수만큼 순회하며 프로퍼티 키를 할당함
- 순회 대상 객체의 프로퍼티뿐만 아니라 상속받은 프로토타입의 프로퍼티까지 열거함
- for ... in 문은 객체의 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 true 인 프로퍼티를 순회하며 열거함
- 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않음
const sym = Symbol();
const obj = {
a : 1,
[sym] : 10
};
for (const key in obj) {
console.log(key + ': ' + obj[key]);
}
19.14.2 Object.keys/values/entries 메서드
- for ... in 문은 객체 자신의 고유 프로퍼티뿐 아니라 상속받은 프로퍼티도 열거함
- 객체 자신의 고유 프로퍼티만 열거하기 위해서는 Object.keys/values/entries 메서드를 사용하는 것을 권장함
- Object.keys 메서드는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환함
const person = {
name : 'Lee',
address : 'Seoul',
__proto__: {age:20}
};
console.log(Object.keys(person));
'2022-2 웹개발 스터디' 카테고리의 다른 글
[코드] 2 (1) | 2022.10.05 |
---|---|
[모던 JS] CH 20. strict mode, CH 21. 빌트인 객체, CH22. this (0) | 2022.10.05 |
[모던 JS] CH18 함수와 일급객체 (0) | 2022.10.04 |
[모던 JS] CH16 프로퍼티 어트리뷰트, CH17 생성자 함수에 의한 객체 생성 (0) | 2022.10.04 |
[모던 JS] CH14 전역 변수의 문제점, CH15 let, const 키워드와 블록 레벨 스코프 (1) | 2022.10.03 |