안녕하세요. 김용성입니다.
이번 포스팅에서는 JavaScript의 프로토타입과 상속에 대해서 공부해보도록 하겠습니다.
사실 많이 사용되지는 않지만 알아두고 넘어가기에도 나쁘지 않은 개념이죠?
다음과 같이 하나의 객체를 선언해주겠습니다.
const user={
name: 'YongSeong'
}
name이라는 property를 가지고 있고 그 값은 'YongSeong'이라는 user 객체가 하나 생성되었죠?
JavaScript에서는 이러한 객체에 존재하는 메소드가 있습니다.
hasOwnProperty()
바로 hasOwnProperty()라는 메소드인데요. 이 메소드는 해당 객체에 존재하는 property인지 여부를 boolean 값으로 반환해줍니다. 다음 사진과 같이 말이죠.
name이라는 property는 가지고 있으므로 true, age라는 property는 존재하지 않으므로 false를 반환해주는 것을 확인할 수 있습니다. 그렇다면 이 hasOwnProperty()라는 녀석은 어디에 존재하는 녀석일까요?
확인해본 결과 내부의 __ proto __라는 녀석에 존재하는 것을 확인할 수가 있네요. 여기서 proto라는 녀석은 바로 prototype을 의미합니다.
이제부터 이 prototype이라는 녀석이 어떻게 작동되는지 상속을 통해 알아보도록 하겠습니다.
//index.js
const iphone8={
color:'productRed',
os:'ios',
call(){
console.log('calling...')
}
}
const iphoneX={
color:'white',
os:'ios',
call(){
console.log('calling...')
}
}
const iphone12={
color:'black',
os:'ios',
type:'mini',
call(){
console.log('calling...')
}
}
이렇게 아이폰 종류를 통해서 예시를 들어보도록 하겠습니다.
해당 코드를 보시면 현재 3개의 객체가 생성되어 있는 것을 확인하실 수 있는데요. 반복되는 것들이 보이죠?
3개의 객체 모두 다 os는 ios이므로 이 부분과 call이라는 함수는 반복되고 있습니다. 우리는 이렇게 반복되는 부분을 제거하기 위해서 상속이라는 것을 사용할 수 있습니다.
이러한 상속작업은 위에서 언급한 프로토타입을 통해 할 수가 있는데요. 아래코드를 살펴보도록 하겠습니다.
const iphone={
os:'ios',
call(){
console.log('calling...')
}
}
const iphone8={
color:'productRed',
}
const iphoneX={
color:'white',
}
const iphone12={
color:'black',
type:'mini',
}
iphone8.__proto__=iphone;
iphoneX.__proto__=iphone;
iphone12.__proto__=iphone;
console.log("os가 잘 상속되었나?",iphone12.os,iphone8.os,iphoneX.os)
실행결과
os가 잘 상속되었나? ios ios ios
이처럼 iphone이라는 부모 객체를 하나 만들어 준 뒤, .__proto__ 를 통해서 상속을 해줄 수 있습니다.
그렇다면 내부에서는 어떤 작업이 이루어질까요?
이전에 제가 스코프체이닝 관련해서 포스팅한 것이 있는데요. 이와 동일한 방식으로 동작한다고 보시면 됩니다.
예를 들어 iphone12.os를 찾는다고 할때에, 우선적으로 iphone12 내부에 선언된 property를 쭉 훑어봅니다.
만약 이곳에 존재하지 않는다? 그럴 경우에는 prototype 내부를 들여다보게 되는 깃입니다.
위 사진처럼 proto 내부에 call과 os가 각각 자리를 잡은 것을 확인하실 수 있습니다.
상속은 계속해서 이뤄질 수가 있습니다.
이번에는 iphone12의 type property를 제외해버리고, iphone12 mini라는 객체를 상속을 통해 만들어주도록 하겠습니다.
const iphone={
os:'ios',
call(){
console.log('calling...')
}
}
const iphone12={
color:'black',
}
iphone12.__proto__=iphone;
const mini={
color:'productRed',
name:'iphone12_mini',
}
mini.__proto__=iphone12;
이렇게 잘 상속이 된 것을 확인하실 수 있습니다.
여기서 mini.os는 ios로 찍히는 것을 확인할 수 있는데요. 마찬가지로 이 역시 mini라는 객체 내부를 확인한 뒤 해당 property가 없으니 iphone12객체를 확인하고, 거슬러 올라가 iphone이라는 객체를 확인하여 "ios"라는 결과값을 가져오게 되는 것입니다.
이러한 프로토타입 간의 관계를 'prototype chaining'이라고 부릅니다.
다음과 같이 코드를 추가해주겠습니다.
for(p in mini){
console.log(p)
}
console.log(Object.keys(mini))
이렇게 mini 내부에 있는 property를 반복문을 통해 호출해주면 상속된 property까지 가져오지만, Object 메소드를 활용하면 상속된 property는 가져오지 못한다는 점을 알아두시길 바랍니다.
이러한 점을 활용해서 반복문을 통해 property를 가져올 때 상속받은 property인지 아닌지 판별해줄 수가 있는데요.
처음에 말씀드린 hasOwnProperty()는 자체 property에 대해서만 true값을 반환하므로 다음과 같이 코드를 작성하면 판별할 수 있습니다.
for (p in mini){
if(mini.hasOwnProperty(p)){
console.log("자체 property",p)
}else{
console.log("상속받은 property",p)
}
}
비슷한 객체를 간단하게 만들기 위해서는 생성자 함수를 사용하는 것이 좋은 방법인데요.
const iphone={
os:'ios',
call(){
console.log('calling...')
}
}
const iphone12=function(type,color){
this.type=type
this.color=color
}
const mini=new iphone12("mini","productRed")
mini.__proto__=iphone
이처럼 생성자를 활용해서 쉽게 상속을 받을 수 있다는 점도 확인하실 수 있습니다.
만약 mini라는 객체 말고 max, pro 등의 객체를 만들 때 그때마다 일일이 _proto__ 함수를 사용한다면 좀 보기 좋지 않겠지요?
그럴 때 우리는 prototype 함수를 사용할 수 있습니다. prototype.${propName} 을 통해서 prototype property를 쉽게 생성해줄 수가 있어요.
const iphone12=function(type,color){
this.type=type
this.color=color
}
iphone12.prototype.os='ios'
iphone12.prototype.call=function(){
console.log("prototype을 이용한 call...")
}
const mini=new iphone12("mini","productRed")
const max=new iphone12("max","blue")
//mini.__proto__=iphone 일일이 상속해줄 필요가 없음
//max.__proto__=iphone
어떤가요? 한결 간편해지지 않았나요??
이렇게 prototype을 이용하면 상속받는 객체가 여러개일 때 중복코드를 줄일 수가 있습니다.
iphone12.prototype.os='ios'
iphone12.prototype.call=function(){
console.log("prototype을 이용한 call...")
}
위의 코드를 통해 객체에 일일이 상속해야하는 불편함은 덜었지만 만약 iphone12의 prototype이 저렇게 적지 않고, 여러가지 존재한다면 코드가 상당히 지저분해 보이겠죠?
그럴 때는 다음과 같이 묶어서 표현할 수가 있습니다.
iphone12.prototype={
os:'ios',
call(){
console.log("prototype을 묶은 call...")
}
}
그러나 이렇게 묶어서 표현할 시에는 다음과 같이 mini의 constructor가 iphone12으로 지정되지 않는데요.
이러한 경우를 대비해서 묶어서 표현할 때에는 constructor를 반드시 명시해주어야 합니다!
iphone12.prototype={
constructor:iphone12, // constructor 명시
os:'ios',
call(){
console.log("prototype을 묶은 call...")
}
}
오늘은 JavaScript의 프로토타입과 상속에 대해 여러가지 알아보았는데요. 도움이 되셨나요?
긴 글 읽어주셔서 감사합니다:)