Object.getPrototypeOf(Person.prototype) === Object.prototype//true
프로토타입 체인
자바스크립트는 객체의 프로퍼티(메서드 포함)에 접근하려고 할 때 해당 객체에 접근하려는 프로퍼티가 없다면
[[Prototype]]
내부 슬롯의 참조를 따라 자신의 부모역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색한다. 이를 프로토타입 체인이라고 한다.
프로토타입 체인의 최상위에 위치하는 객체는 언제나 Object.prototype
이다.(프로토타입 체인의 종점)
Object.prototype
을 상속받는다.Object.prototype
의 프로토타입 즉, [[Prototype]]
내부슬롯의 값은 null
이다.예시 > me.hasOwnProperty('name')
: 먼저 스코프체인에서 me식별자를 검색한 다음, me 객체의 프로토타입 체인에서 hasOwnProperty메서드를 검색한다.
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
delete me.sayHello();
// 인스턴스에는 sayHello가 없으므로 프로토타입 메서드 호출
me.sayHello(); //Hi! My name is Lee
//하지만 하위객체를 통해 프로토타입의 프로퍼티를 변경/삭제하는 것은 불가능
delete me.sayHello();
// 프로토타입 메서드 그대로 호출
me.sayHello(); //Hi! My name is Lee
프로토타입이 소유한 프로퍼티를 프로토타입 프로퍼티, 인스턴스가 소유한 프로퍼티를 인스턴스 프로퍼티
프로토타입 프로퍼티와 같은 이름의 프로퍼티를 인스턴스에 추가하면 프로토타입 체인을 따라 프로토타입 프로퍼티를 검색하여 프로토타입 프로퍼티를 덮어쓰는 것이 아니라 인스턴스 프로퍼티로 추가한다.
이처럼 상속 관계에 의해 프로퍼티가 가려지는 현상을 ”프로퍼티 섀도잉”이라 한다.
하위 객체를 통해 프로토타입에 get 액세스는 허용되나 set 액세스는 허용되지 않는다. ⇒ 프로토타입 프로퍼티를 변경/삭제하려면 하위 객체를 통해 프로토타입 체인으로 접근이 아닌 프로토타입에 직접 접근해야한다.
프로토타입은 임의의 다른 객체로 변경할 수 있다.
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');
console.log(me.consturctor === Person); //false
console.log(me.constructor === Object); //true
①에서 Person.prototype
에 객체 리터럴을 할당했는데, 이는 Person
생성자 함수가 생성할 객체의 프로토타입을 객체 리터럴로 교체한 것이다.
constructor
프로퍼티가 없다.Person
이 아닌 Object
로 바뀐다.constructor
프로퍼티와 생성자 함수간의 연결이 파괴되는데,constructor
프로퍼티를 추가하여 프로토타입의 constructor
프로퍼티를 되살릴 수 있다.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
하지만 이런 방식들의 프로토타입 교체는 객체 간의 상속관계를 동적으로 변경하는 것은 꽤나 번거롭고 직접 교체하지 않는 것이 좋다.
→ 직접 상속이 더 편리하고 안전 or 클래스 사용
instanceof
연산자는 이항 연산자로서 좌변에 객체를 가리키는 식별자, 우변에 생성자 함수를 가리키는 식별자를 피연산자로 받는다.TypeError
가 발생한다.객체 instanceof 생성자 함수
prototype
에 바인딩된 객체가 좌변의 객체의 프로토타입 체인 상에 존재하면 true
로 평가됨instanceof
연산자는 프로토타입의 constructor프로퍼티가 가리키는 생성자 함수를 찾는 것이 아니라 생성자 함수의 prototype
에 바인딩된 객체가 프로토타입 체인 상에 존재하는지 확인한다.Object.create(생성할 객체의 프로토타입으로 지정할 객체,
생성할 객체의 프로퍼티 키와 프로퍼티 디스크립터 객체로 이뤄진 객체)
//두 번째 인수는 옵션이므로 생략 가능하다.
Object.create 메서드는 첫 번째 매개변수에 전달된 객체의 프로토타입 체인에 속하는 객체를 생성한다. 이 메서드의 장점은 다음과 같다.
__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 }
});
*/
console.log(obj.x, obj.y); // 10 20
console.log(Object.getPrototypeOf(obj) === myProto); // true
// 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHello = function () {
console.log(`Hi! My name is ${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
in
연산자는 객체 내에 특정 프로퍼티가 존재하는지 여부를 확인한다./**
* key: 프로퍼티 키를 나타내는 문자열
* object: 객체로 평가되는 표현식
*/
key in object
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
console.log('toString' in person); //true //Object.prototype의 메서드
in
연산자는 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인한다.
console.log(person.hasOwnProperty('name'));//true
console.log(person.hasOwnProperty('age'));//false
console.log(person.hasOwnProperty('toString'));//false
for .. in
문을 사용한다[[Enumerable]]
의 값이 true
인 프로퍼티를 순회하며 열거한다.(toString과 같은 Object.prototype의 프로퍼티가 열거되지 않는다.)const person = {
name: 'Lee',
address: 'Seoul',
__proto__: { age: 20 }
};
for (const key in person) {
// 객체 자신의 프로퍼티인지 확인한다.
if (!person.hasOwnProperty(key)) continue;
console.log(key + ': ' + person[key]);
}
// name: Lee
// address: Seoul
for...in
문 객체는 상속받은 프로퍼티까지 열거하기 때문에 Object.prototype.hasOwnProperty 메서드를 사용하여 객체 자신의 프로퍼티인지 확인하는 추가 처리가 필요했다. → Object.keys/values/entries 메서드 사용하면 추가 처리 필요xconst person = {
name: 'Lee',
address: 'Seoul',
__proto__: { age: 20 }
};
console.log(Object.keys(person)); // ["name", "address"]
console.log(Object.values(person)); // ["Lee", "Seoul"]
console.log(Object.entries(person)); // [["name", "Lee"], ["address", "Seoul"]]
Object.entries(person).forEach(([key, value]) => console.log(key, value));
/*
name Lee
address Seoul
*/