19.6 객체 생성 방식과 프로토타입의 결정
- 프로토타입은 추상연산 OrdinarayObjectCreate에 전달되는 인수에의해 결정
19.6.1 객체 리터럴에 의해 생성된 객체의 프로토타입
- 겍체 리터럴 내부에 프로퍼티 추가
- 자바스크립트 엔진은 객체 리터럴을 평가하여 객체를 생성할 떄 추상 연산 OrdinaryObjectCreat를 호출
- 객체 리터럴에 의해 생성되는 객체의 프로토타입은 Object.prototype이며 Object.prototype을 상속받음
const obj = {x:1};
//객체 리터럴에 의해 생성된 obj 객체는 Object.prototype을 상속받음
console.log(obj.constructor === Object); //true
console.log(obj.hasOwnProperty('x')); //true
19.6.2 Object 생성자 함수에 의해 생성된 객체의 프로토타입
- Object생성자 함수를 인수 없이 호출하면 빈 객체가 생김
- Object 생성자 함수를 호출하면 객체 리터럴과 마찬가지로 추상 연산 OrdinaryObjectCreate가 호출
- 추상 연산에 의해 전달되는 프로토타입은 Object.prototype이며 Object.prototype을 상속 받음
const obj = new Object;
obj.x = 1;
//Object 생성자 함수에 의해 생성된 obj 객체는 Object.prototype을 상속받음
console.log(obj.constructor === Object); //true
console.log(obj.hasOwnProperty('x')); //true
19.6.3 생성자 함수에 의해 생성된 객체의 프로토타입
- new 연산자와 함께 생성자 함수를 호출하여 인스턴스를 생성하면 추상 연산 OrdinaryObjectCreate가 호출
- 추상 연산에 의해 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
- 사용자 정의 생성자 함수 Person과 더불어 생성된 프로토타입 Person.prototype의 프로퍼티는 constructor뿐이다
- 프로토타입 Person.prototype에 프로퍼티를 추가하여 하위 객체가 상속받을 수 있도록 구현
function Person(name) {
this.name = name;
}
//프로토타입 메서드
Person.prototype.sayHello = function() {
console.log(`Hi! My name is ${this.name}`)
};
const me = new Person('Lee'); //Hi! My name is Lee
const you = new Person('Kim'); //Hi! My name is Kim
me.sayHello();
you.sayHello();
19.7 프로토타입 체인
프로토타입 체인
- 자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면 [[Prototype]] 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색
function Person (name) {
this.name = name;
}
//프로토타입 메서드
Person.prototype.sayHello = function() {
console.log(`Hi! My name is ${this.name}`);
};
const me = new Person('Lee');
//hasOwnProperty는 Object.prototype의 메서드
//me 객체는 프로토타입체인을 따라 hasOwnProperty 메서드를 검색하여 사용
console.log(me.hasOwnProperty('name')); //true
- me 객체가 Person.prototype뿐만 아니라 Object.prototype도 상속 받음
- me 객체의 프로토타입은 Person.prototype
Object.getPrototypeOf(me) === Person.prototype; //true
- Person.prototype의 프로토타입은 Object.prototype
Object.getPrototypeOf(Person.prototype) === Object.prototype; //true
- 프로토타입 체인의 최상단에 위치하는 객체는 언제나 Object.prototype
- 모든 객체는 Obejct.prototype을 상속 받음
- 프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘
- 스코프 체인은 식별자 검색을 위한 메커니즘
- 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라 서로 협력하여 식별자와 프로퍼티를 검색하는 데 사용
ex)
me.hasOwnProperty('name');
- 먼저 스코프 체인에서 me 식별자를 검색
- me 식별자를 검색한 다음 me 객체의 프로토 타입 체인에서 hasOwnProperty 메서드를 검색
19.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(`Hi! My name is ${this.name}`);
};
//인스턴스 메서드가 호출. 프로토타입 메서드는 인스턴스 메서드에 의해 가려짐
me.sayHello(); //Hey! My name is Lee
- 프로토타입 프로피터와 같은 이름의 프로퍼티를 인스턴스에 추가하면 인스턴스 프로퍼티로 추가
- 인스턴스 메서드 sayHello는 프로토타입 메서드 sayHello를 오버라이딩
- 프로토타입 메서드 sayHello는 가려짐
프로퍼티 섀도잉(property shadowing)
- 상속관계에 의해 프로퍼티가 가려지는 현상
- 인스턴스 메서드 sayHello 삭제
//인스턴스 메서드를 삭제한다
delete me.sayHello;
//인스턴스에는 sayHello 메서드가 없으므로 프로토타입 메서드가 호출
me.sayHello();
- 하위 객체를 통해 프로토타입의 프로퍼티를 변경 또는 삭제하는 것은 불가능
- 하위 객체를 통해 프로토타입에 get 액세스는 허용되나 set 액세스는 허용되지 않음
//프로토타입 체인을 통해 프로토타입 메서드가 삭제되지 않음
delete me.sayHello;
//프로토타입 메서드가 호출됨
me.sayHello();
- 프로토타입 프로퍼티를 변경 또는 삭제하려면 프로토타입에 직접 접근해야함
//프로토타입 메서드 변경
Person.prototype.sayHello = function() {
console.log(`Hey! My name is ${this.name}`);
};
me.sayHello();
//프로토타입 메서드 삭제
delete Person.prototype.sayHello;
me.sayHello();
19.9 프로토타입의 교체
- 부모 객체인 프로토타입을 동적으로 변경 가능
- 객체 간의 상속관계를 동적으로 변경 가능
- 프로토타입은 생성자 함수 또는 인스턴스에 의해 교체 가능
19.9.1 생성자 함수에 의한 프로토타입의 교체
- 생성자 함수의 prototype 프로퍼티에 다른 임의의 객체를 바인딩하는 것은 미래에 생성할 인스턴스의 프로토타입을 교체하는 것
const Person = (function() {
function Person(name) {
this.name = name;
}
//생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
Person.prototype = {
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
return Person;
}());
const me = new Person('Lee');
- Person.prototype에 객체 리터럴 할당
- Person 생성자 함수가 생성할 객체의 프로토타입을 객체리터럴로 교체
- 프로토타입으로 교체한 객체 리터럴에는 constructor 프로퍼티가 없음
- 프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴
//프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴
console.log(me.constructor === Person); //false
//프로토타입 체인을 따라 Object.prototype의 constructor 프로퍼티가 검색됨
console.log(me.constructor === Object); //true
- 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 프로토타입의 constructor 프로퍼티를 되살림
const Person = (function() {
function Person(name) {
this.name = name;
}
//생성자 함수의 prototype 프로퍼티를 통해 프로토타입을 교체
Person.prototype = {
//constructor 프로퍼티와 생성자 함수 간의 연결을 설정
constructor: Person,
sayHello() {
console.log(`Hi! My name is ${this.name}`);
}
};
return Person;
}());
const me = new Person('Lee');
//constructor 프로퍼티가 생성자 함수를 가리킴
console.log(me.constructor === Person); //true
console.log(me.constructor === Object); //true
19.9.2 인스턴스에 의한 프로토타입의 교체
- 인스턴스의 __proto__ 접근자 프로퍼티를 통해 프로토타입에 접근 가능
- 인스턴스의 __proto__ 접근자 프로퍼티를 통해 프로토타입 교체 가능
- __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 프로퍼티가 없으므로 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴
//프로토타입을 교체하면 constructor 프로퍼티와 생성자 함수 간의 연결이 파괴됨
console.log(me.constructor === Person); //false
//프로토타입 체인을 따라 Object.prototype의 constructor 프로퍼티가 검색
console.log(me.constructor === Object); //true
- 프로토타입으로 교체한 객체 리터럴에 constructor 프로퍼티를 추가하여 생성자 함수의 prototype프로퍼티를 재설정
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
//프로토타입으로 교체할 객체
const parent = {
//constructor 프로퍼티와 생성자 함수 간의 연결을 설정
constructor : Person,
sayHello() {
console.log(`Hi My name is ${this.name}`);
}
};
//생성자 함수의 prototype 프로퍼티와 프로토타입 간의 연결을 설정
Person.prototype = parent;
//me 객체의 프로토타입을 parent 객체로 교체
Object.setPrototypeOf(me, parent);
//me.__proto__ = parent;
me.sayHello(); //Hi! My name is Lee
//constructor 프로퍼티가 생성자 함수를 가리킴
console.log(me.constructor === Person); //true
console.log(me.constructor === Object); //false
//생성자 함수의 prototype 프로퍼티가 교체된 프로토타입을 가리킴
console.log(Person.prototype === Object.getPrototypeOf(me)); //true
19.10 instanceof 연산자
- 우변의 생성자 함수의 prototype에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true,
그렇지 않은 경우에는 false로 평가
객체 instanceof 생성자 함수
//생성자 함수
function Person(name) {
this.name = name;
}
const me = new Person('Lee');
//Person.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가
console.log(me instanceof Person); //true
//Object.prototype이 me 객체의 프로토타입 체인 상에 존재하므로 true로 평가
console.log(me instanceof Object); //true
- instanceof 연산자를 함수로 표현
function isInstanceof(Instance, constructor) {
//프로토타입 취득
const prototype = Object.getPrototypeOf(instance);
//재귀 탈출 조건
//prototype이 null이면 프로토타입 체인의 종점에 다다른 것
if(prototype === constructor.prototype || isInstanceof(prototype, constructor));
}
console.log(isInstanceof(me,Person)); //true
console.log(isInstanceof(me,Object)); //true
console.log(isInstanceof(me,Array)); //false
19.11 직접 상속
19.11.1 Object.create에 의한 직접 상속
- Object.create 메서드는 명시적으로 프로토타입을 지정하여 새로운 객체를 생성
- Object.create 메서드는 추상 연산 OrdinaryObjectCreate를 호출
/*
지정된 프로토타입 및 프로퍼티를 갖는 새로운 객체를 생성하여 반환
@param {Object} prototype - 생성할 객체의 프로토타입으로 지정할 객체
@param {Object} [properitesObject] - 생성할 객체의 프로퍼티를 갖는 객체
@param {Object} - 지정된 ㅣ프로토타입 및 프로퍼티를 갖는 새로운 객체
*/
Object.crate(prototype[, propertiesObject])
- Obejct.create 메서드는 첫 번재 매개변수에 전달한 객체의 프로토타입 체인에 속하는 객체를 생성
- 객체를 생성하면서 직접적으로 상속 구현
//프로토타입이 null인 객체를 생성. 생성된 객체는 프로토타입 체인의 종점에 위치
//obj -> null
let obj = Object.create(null);
console.log(Object.getPrototypeOf(obj) === null); //true
//Object.prototype을 상속받지 못함
console.log(obj.toString()); //TypeError
//obj-> Object.prototype -> null
obj = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(obj) === Object.prototype); //true
//obj-> Object.prototype -> null
obj = Object.create(Object.prototype, {
x: {value: 1, writable: true, enumerable: true, configurable:true}
});
console.log(obj.x); //1
console.log(Object.getPrototypeOf(obj) === Object.prototype); //true
const myProto = {x:10};
//임의의 객체를 직접 상속받음
//obj -> myProto->Object.prototype->null
obj = Object.create(myProto);
console.log(obj.x);
console.log(Object.getPrototypeOf(obj) === myProto); //true
//생성자 함수
function Person(name) {
this.name = name;
}
//obj-> Person.prototype -> Object.prototype -> null
obj = Object.create(Person.prototype);
obj.name = 'Lee';
console.log(obj.name); //Lee
console.log(Object.getPrototypeOf(obj) === Person.prototype); //true
19.11.2 객체 리터럴 내부에서 __proto__에 의한 직접 상속
- ES6에서는 객체 리터럴 내부에서 __proto__ 접근자 프로퍼티를 사용하여 직접 상속 구현 가능
const myProto = {x:10};
//객체 리터럴에 의해 객체를 생성하면서 프로토타입을 지정하여 직접 상속 가능
const obj = {
y:20;
//객체를 직접 상속받음
//obj->myProto->Object.prototype->null
__proto__ : myProto
};
console.log(obj.x, obj.y); //10 20
console.log(Object.getPrototypeOf(obj) === myProto); //true
'JavaScript' 카테고리의 다른 글
[Deep dive] 20장 strict mode (0) | 2023.08.11 |
---|---|
[Deep dive] 19장 프로토타입 (3) (0) | 2023.08.11 |
[Deep dive] 19장 프로토타입 (1) (0) | 2023.08.10 |
[Deep dive] 18장 함수와 일급객체 (0) | 2023.08.10 |
[Deep dive] 17장 생성자 함수에 의한 객체 생성 (0) | 2023.08.10 |