[240326] 19장 프로토타입 (3)
19장 프로토타입 (3)
7. 프로토타입 체인
- 자바스크립트는 객체의 프로퍼티에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라 한다.
- 프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype
- 체인의 종점 (end of prototype chain)
- [[Prototype]] 내부 슬롯의 값은 null
- Object.prototype 에서도 검색할 수 없으면 undefined 반환
- 프로토타입 체인 (상속과 프로퍼티 검색을 위한 메커니즘)
- 스코프 체인 (식별자 검색을 위한 메커니즘)
- 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용
8. 오버라이딩과 프로퍼티 섀도잉
const Person = (function () {
// 생성자 함수
function Person(name) {
this.name = name;
}
//프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi ! My name is ${this.name}`);
};
// 생성자 함수를 반환
return Person;
}());
const me = new Person('Lee');
// 인스턴스 메서드
me.sayHello = function () {
console.log(`Hey! My name is ${this.name}`);
};
// 인스턴스 메서드가 호출된다. 프로토타입 메서드는 인스턴스 메서드에 의해 가려진다.
me.sayHello(); // Hey! My name is Lee
프로토타입이 소유한 프로퍼티 : 프로토타입 프로퍼티
인스턴스가 소유한 프로퍼티 : 인스턴스 프로퍼티
- 프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면, 프로토타입 프로퍼티를 덮어쓰는 것이 아니라 인스턴스 프로퍼티로 추가한다.
- 프로퍼티 섀도잉 : 상속 관계에 의해 프로퍼티가 가려지는 현상
- 오버라이딩 : 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하여 사용하는 방식
- 오버로딩: JS 는 오버로딩을 지원하지 않지만 arguments 객체를 사용하여 구현할 수 있다.
- 삭제도 마찬가지로 인스턴스 메서드가 삭제된다.
- 프로토타입 체인을 통해 프로토타입 메서드는 삭제되지 않는다.
- 하위 객체를 통한 set 액세스는 허용하지 않음
- 변경이나 삭제하려면 프로토타입 체인으로 접근하면 안되고 직접 접근해야함
9. 프로토타입의 교체
- 프로토타입은 임의의 다른 객체로 변경 가능
- 이러한 특징을 활용하여 객체 간의 상속 관계를 동적으로 변경 가능
- 생성자 함수 , 인스턴스에 의해 교체 가능
1. 생성자 함수에 의한 프로토타입 교체
const Person = (function () {
// 생성자 함수
function Person(name) {
this.name = name;
}
// 생성자 함수의 prototype 프로퍼티를 통해 프로토타입 교체
Person.prototype = {
sayHello() {
console.log('Hi !');
};
};
// 생성자 함수를 반환
return Person;
}());
const me = new Person('Lee');
// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
console.log(me.constructor === Person); // false
// 프로토타입 체인을 따라 Object.prototype 의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); // true
2. 인스턴스에 의한 프로토타입의 교체
- 생성자 함수의 prototype 프로퍼티에 다른 임의이 객체를 바인딩하는 것은 미래에 생성할 인스턴스의 프로토타입을 교체하는 것
- proto 접근자 프로퍼티를 통해 교체하는 것은 이미 생성된 객체의 프로토타입을 교체하는 것이다.
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
const parent = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
// me 객체의 프로토타입을 parent 객체로 교체
Object.setPrototypeOf(me,parent);
// me.__proto__ = parent; 동일하다.
me.sayHello(); // Hi! My name is Lee
// 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴된다.
console.log(me.constructor === Person); // false
// 프로토타입 체인을 따라 Object.prototype 의 constructor 프로퍼티가 검색된다.
console.log(me.constructor === Object); // true
- ES6 에서 도입된 클래스 사용하면 간편하고 직관적으로 상속 관계 구현 가능
10. instanceof 연산자
- 객체 instanceof 생성자 함수
- 이항 연산자
- 좌변 : 객체를 가리키는 식별자
- 우변: 생성자 함수를 가리키는 식별자
- 우변의 피연산자가 함수가 TypeError 발생
- 우변의 생성자 함수의 prototype 에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true로 평가, 그렇지 않으면 false
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
console.log(me instanceof Person); // true
console.log(me instanceof Object); // true
- instanceof 연산자 함수로 표현하면 아래와 같다.
function isInstanceof(instance, constructor) {
const prototype = Object.getPrototypeOf(instance);
// 재귀 탈출 조건
// prototype null 이면 프로토체인 종점
if(prototype === null) return false;
return prototype === constructor.prototype || isInstanceof(prototype, constructor);
}
11. 직접 상속
1. Object.create 에 의한 직접 상속
- 명시적으로 프로토타입을 지정하여 새로운 객체를 생성한다.
- 추상 연산 호출
- 첫 번째 매개변수 : 생성할 객체의 프로토타입으로 지정할 객체
- 두 번째 매개변수 : 생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체 (옵션)
const obj = Object.create(Object.prototype);
console.log(Object.getPrototypeof(obj)===Object.prototype);
// obj2 = { x:1 }; 와 동일
const obj2 = Object.create(Object.prototype, {
x: { value:1, writable: true, enumerable: true, configurable: true }
});
직접 상속 장점
- new 연산자 없이도 객체 생성
- 프로토타입을 지정하면서 객체 생성 가능
- 객체 리터럴에 의해 생성된 객체도 상속 가능
const obj = Object.create(null);
obj.a = 1;
console.log(Object.getPrototypeof(obj) === null); // true
// obj 는 Object.prototype 의 빌트인 메서드를 사용할 수 없다.
console.log(obj.hasOwnProperty('a'));
// TypeError : obj.hasOwnProperty is not a function
- Object.prototype 의 빌트인 메서드를 객체가 직접 호출하는 것을 권장하지 않음
- 체인의 종점에 위치하는 객체는 빌트인 메서드를 사용할 수 없다.
- 따라서 Object.prototype.hasOwnProperty.call(obj, ‘a’) 처럼 간접적으로 호출하는 것이 좋다.
2. 객체 리터럴 내부에서 proto 에 의한 직접 상속
- Object.create 는 두 번째 인자 프로퍼티 정의하는 것은 번거롭다.
- ES6 에서는 객체 리터럴 내부에서 proto 접근자 프로퍼티를 사용하여 직접 상속을 구현할 수 있다.
const myProto = { x: 10 };
const obj = {
y: 20,
// obj -> myProto -> Object.prototype -> null
__proto__: myProto
};
/* 위 코드는 아래와 동일하다.
const obj = Object.create(myProto, {
y: { value:20, writable: true, enumerable: true, configurable: true }
});
*/
12. 정적 프로퍼티/메서드
- 생성자 함수로 인스턴스를 생성하지 않아도 참조/호출할 수 있는 프로퍼티/메서드를 말한다.
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`${this.name}`);
};
// 정적 프로퍼티
Person.staticProp = 'static prop';
// 정적 메서드
Person.staticMethod = function () {
console.log('staticMethod');
};
const me = new Person('Lee');
Person.staticMethod(); // staticMethod
// 정적 프로퍼티/메서드는 생성자 함수가 생성한 인스턴스로 참조/호출할 수 없다.
// 인스턴스로 참조/호출할 수 있는 프로퍼티/메서드는 프로토타입 체인 상에 존재해야 한다.
me.staticMethod(); // TypeError: me.staticMethod is not a function
// Object.create 는 정적 메서드다.
const obj = Object.create({ name: 'Lee' });
// Object.prototype.hasOwnProperty는 프로토타입 메서드다
obj.hasOwnProperty('name'); // false
- MDN 문서에도 정적과 프로토타입 구분해서 소개하고 있음
- prototype을 # 로 표기하는 경우도 있다.
13. 프로퍼티 존재 확인
1. in 연산자
- 객체 내에 특정 프로퍼티가 존재하는지 여부 확인
- key in object
-
프로퍼티 뿐 아니라 확인 대상 객체가 받은 모든 프로토타입의 프로퍼티를 확인한다.
console.log('toString' in person); // true
-
Reflect.has 메서드도 동일하게 동작 (ES6 도입)
Reflect.has(person, 'name');
2. Object.prototype.hasOwnProperty 메서드
- 프로퍼티타입의 프로퍼티키인 경우 false 반환
- 인스로 전달받은 프로퍼티 키가 객체 고유의 프로퍼티 키인 경우에만 true 반환
14. 프로퍼티 열거
1. for …in 문
- for (변수선언문 in 객체) { … }
- 객체의 모든 프로퍼티를 순회하며 열거
- 상속받은 프로토타입의 프로퍼티까지 열거
- [[Enumerable]] 이 true 이면 열거
- 키가 심벌인 프로퍼티는 열거하지 않는다.
- 상속 받은 프로퍼티를 제외하려면 hasOwnProperty 메서드를 사용해 객체 자신의 프로퍼티인지 확인 해야함
- 열거할 때 순서를 보장하지 않는다.
- 대부분 모던 브라우저는 숫자(사실은 문자열)인 프로퍼티 키에 대해서는 정렬을 실시한다.
- 배열에는 for …in 문 말고 일반적인 for 문이나 for of , Array.prototype.forEach 메서드 권장
- 해당 사항에 관한 자세한건 뒷 장
2. Object.keys/ values / entries 메서드
- 객체 자신의 고유 프로퍼티만 열거
- Object.keys : 열거 가능한 프로퍼티 키를 배열로 반환
- Object.values : 열거 가능한 프로퍼티 값를 배열로 반환 (ES8 도입)
- Object.entries : 열거 가능한 프로퍼티 키와 값의 쌍의 배열로 반환 (ES8 도입)
Leave a comment