자바스크립트의 객체는 [[prototype]]
이라는 숨김 프로퍼티를 가진다
이 숨김 프로퍼티 값은 null
이거나 다른 객체에 대한 참조가 되는데, 다른 객체를 참조하는 경우 참조 대상을 '프로토타입(prototype)'이라 부른다
object
에서 프로퍼티를 읽으려 할 때 해당 프로퍼티가 없으면 자동으로 프로토타입에서 프로퍼티를 찾는 동작 방식
[[Prototype]]
프로퍼티는 내부 프로퍼티이면서 숨김 프로퍼티이지만 다양한 방법을 사용해 개발자가 값을 설정할 수 있다
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal;
👉[[prototype]]
의 getter이자 setter
__proto__
!= [[Prototype]]
하위 호환성 때문에 여전히 __proto__
를 사용할 순 있지만 비교적 근래에 작성된 스크립트에서는 __proto__
대신 Object.getPrototypeOf
나 Object.setPrototypeOf
를 써서 프로토타입을 get하거나 set한다
__proto__
는 브라우저 환경에서만 지원하도록 자바스크립트 명세서에 규정되었으나 실상은 서버 사이드를 포함한 모든 호스트 환경에서 __proto__
를 지원한다
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal; // (1)
alert(rabbit.eats); // true, (2)
alert(rabbit.jumps); // true
(1) > animal
이 rabbit
의 프로토타입이 되도록 설정
(2) > alert
함수가 rabbit.eats
프로퍼티를 읽으려 했으나 rabbit
엔 eats
라는 프로퍼티가 없다.
이 때 [[Prototype]]
이 참조하는 객체인 animal
에서 eats
를 얻어낸다
👉 rabbit
의 프로토타입은 animal
이다
👉 rabbit
은 animal
을 상속 받는다
이렇게 프로토타입에서 상속받은 프로퍼티를 상속 프로퍼티(inherited property)라고 한다
let animal = {
eats: true,
walk() { alert('동물이 걸어요'); }
};
let rabbit = {
jumps: true,
__proto__: animal
};
// rabbit의 프로토타입인 animal에서 상속받은 메서드 walk
rabbit.walk(); // 동물이 걸어요
let animal = {
eats: true,
walk() {
alert('동물이 걸어요');
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
let longEar = {
earLength: 10,
__proto__: rabbit
};
// 프로토타입 체인을 통해 상속받은 메서드 walk
longEar.walk(); // 동물이 걸어요
alert(longEar.jumps); // true (rabbit에서 상속받음)
⚠️ 프로토타입 체이닝의 제약사항
__proto__
를 이용해 닫힌 형태로 다른 객체를 참조하면 에러가 발생__proto__
의 값은 객체나 null
만 가능하고 다른 자료형은 무시된다[[Prototype]]
만 있을 수 있다 = 객체는 두 개의 객체를 상속받지 못한다프로퍼티를 추가, 수정하거나 지우는 연산은 객체에 직접 해야한다
let animal = {
eats: true,
walk() {} // rabbit은 이제 이 메서드를 사용하지 않음
};
let rabbit = {
__proto__: animal
};
rabbit.walk = function() {
alert('토끼가 뛰어요');
};
rabbit.walk(); // 토끼가 뛰어요
위 처럼 객체 rabbit
에 walk
메서드를 직접 할당한 후 rabbit.walk()
를 호출하면 프로토타입의 메서드가 실행되지 않고 객체 rabbit
에 추가한 메서드가 실행된다
let user = {
name: 'Jinju',
surname: 'Baek',
set fullName(value) {
[this.name, this.surname] = value.split(' ');
},
get fullName() {
return `${this.name} ${this.surname}`;
}
};
let admin = {
__proto__: user;
isAdmin: true
};
alert(admin.fullName); // Jinju Baek (1)
// setter 함수가 실행되는 부분
admin.fullName = 'John Doe'; // (2)
alert(admin.fullName); // John doe
alert(user.fullName); // Jinju Baek
(1) > admin.fullName
은 프로토타입 user
의 getter 함수를 호출
(2) > 프로토타입의 setter 함수를 호출
setter 함수의 this
에 어떤 값이 들어갈까?
프로퍼티 this.name
과 this.surname
에 값을 쓰면 그 값은 user
에 저장될까 아니면 admin
에 저장될까?
👉 this
는 프로토타입에 영향을 받지 않는다
메서드를 객체에서 호출했든 프로토타입에서 호출했든 상관없이 this
는 .
앞에 있는 객체가 된다.
admin.fullName
으로 setter 함수를 호출하면 this
는 user
가 아닌 admin
이 된다
for..in
은 상속 프로퍼티도 순회대상에 포함시킨다
let animal = {
eats: true
};
let rabbit = {
jumps: true,
__proto__ : animal
};
// Object.keys는 객체 자신의 key만 반환한다
alert(Object.keys(rabbit)); // jumps
// for..in은 객체 자신의 key와 상속 프로퍼티 key 모두를 순회
for(let prop in rabbit) alert(prop); // jumps, eats
❗key-value를 순회하는 메서드 대부분은 상속 프로퍼티를 제외하고 동작한다
Object.keys
, Object.values
같이 객체의 key-value를 대상으로 무언가를 하는 메서드 대부분은 상속 프로퍼티를 제외하고 동작한다
프로토타입에서 상속받은 프로퍼티는 제외하고, 해당 객체에서 정의한 프로퍼티만 연산 대상에 포함한다
[[Prototype]]
이 있는데, 이 프로퍼티는 객체나 null
을 가리킨다obj.__proto__
를 사용하면 프로토타입에 접근할 수 있다__proto__
는 [[Prototype]]
의 getter와 setter로 쓰이는데 요즘엔 잘 쓰이지 않는다obj
에서 프로퍼티를 읽거나 메서드를 호출하려는데 해당하는 프로퍼티나 메서드가 없으면 자바스크립트는 프로토타입에서 프로퍼티나 메서드를 찾는다method
라도 obj.method()
를 호출하면 method
안의 this
는 호출 대상 객체인 obj
를 가리킨다for..in
반복문은 객체 자체에서 정의한 프로퍼티뿐만 아니라 상속 프로퍼티도 순회 대상에 포함한다