본문 바로가기

FrontEnd/Javascript

프로토타입을 이용한 객체의 상속

 

JS에서 상속을 할수 있는 방법은 2가지가 있다. 프로토타입을 이용한 상속과 extends를 이용한 클래스 상속. 여기서는 프로토타입을 이용한 상속을 알아본다.

프로토타입을 이용한 상속은 xxx.__proto__=yyy 를 통하여 계속하여 이루어질수 있다.

 

//최고조상이 될 car객체
const car={
    wheels:4,
    drive(){//이름지어보기. 지어짐. 하지만 이렇한 형식으로 객체안의 함수를 정의할 수 있음. 
        console.log("Driving...");
    }
}

//car객체를 상속할 bmw, audi, Lamborghini
const bmw={
    color:"red",
    navigation:1,
};

const audi={
    color:"blue",
}

const Lamborghini={
    color:"Purple",
}

//bmw를 상속할 x5
const x5={
    color:"black",
    name:"x5",
}
//아래와 같이 코딩하면....
bmw.__proto__=car;
x5.__proto__=bmw;

console.log(x5);

이와 같은 상속관계를 얻을 수 있다. 브라우저에서 출력해 보면
이와 같은 결과를 얻을 수 있다. [[Prototype]]의 이름이 전부 Object로 같은데 이것은 자료형을 나타내는 것이다. 1번째는 bmw, 2번째는 car, 3번째는 최고조상인 Object.prototype이다.

 

for in 반복문을 사용하면 조상객체들이 가지고 있는 프라퍼티를 모두 포함하여 반환해줌. 

자신의 __proto__에 있는 프라퍼티까지 모두 반환해 주는 for in 반복문

 

참고로 Object.keys(x5), Object.values(x5)의 결과는 다음과 같다.

__proto__객체와는 관계없이 해당객체(x5)에만 있는 프라퍼티를 반환하는 Object.keys(), Object.values()

 

hasOwnProperty() 역시 조상객체를 보지않고 자신에게만 있는 프라퍼티를 본다.

for(pro in x5){
    if(x5.hasOwnProperty(pro)){
        console.log("O  "+pro);
    }
    else{
        console.log("X  "+pro);
    }
}

를 브라우저를 통해 확인해 보면 다음과 같다.

 

객체리터럴을 통해 생성된 객체는 아래와 같이 "객체리터럴을 가리키는 객체변수.__proto__=xxx 를 해주면 된다.

bmw.__proto__=car;
x5.__proto__=bmw;

하지만 생성자 함수를 사용할 때는 다음과 같이 해준다.

(여기서 잠깐!!! 객체리터럴과 함수생성자간에 프로토타입을 다루는 차이는 순전히 아래 프로토타입 체인이 다르기 때문이다. 이 그림을 잘 기억해 두자.

)

총 2가지 방법이 있다. 첫번째는 Bmw.prototype이 상위 객체로 car객체를 갖게끔 Bmw.prototype.__proto__=car; 와 같이 코딩해주는 것이다.

const car={
    wheel:4,
    drive(){
        console.log("Driving...");
    }
}

const Bmw=function (color){
    this.color=color;
};

Bmw.prototype.__proto__=car;

let x5=new Bmw("Green");
console.log(x5);

1번째 [[prototype]]은 Bmw.prototype이고 2번째 [[prototype]]은 car.prototype이다.

2번째 방법은 car라는 상위 객체를 만들지 않고 Bmw.prototype에 아주 정형적인 프라퍼티, 메서드를 박아 버리는것이다. 어떻게? Bmw.prototype.wheels=4;  Bmw.prototype.drive=function(){console.log("drive...");} 처럼.

const Bmw=function (color){
    this.color=color;
};

Bmw.prototype.wheel=4;
Bmw.prototype.drive=function(){
    console.log("Driving...");
}

let x5=new Bmw("Green");
console.log(x5);

결과는 아래와 같다.

보통 생성자함수를 사용하면 첫번째 방법보다는 이 2번째 방법을 더 흔히 이용하는 것 같다.

JS에도 역시 isntanceOf 연산자가 존재한다. instanceOf연산자를 통해 해당인스턴스가 해당 생성자함수(클래스)의 인스턴스인지를 알려준다.

console.log(x5 instanceof Bmw); //true

한편 아래와 같은 코드도 있고 이 코드가 무엇을 의미하는지는 DeepDive에 아주 잘 나와있다.

console.log(x5.constructor===Bmw); //true

 

const Bmw=function (color){
    this.color=color;
};

// //1번)
// Bmw.prototype.wheel=4;
// Bmw.prototype.drive=function(){
//     console.log("Driving...");
// }

//2번)
Bmw.prototype={
    wheel:4,
    drive(){
        console.log("driving...");
    },
}

let x5=new Bmw("Green");
console.dir(x5);

console.log(Bmw.prototype.constructor===Bmw);//2번 과 같이 할시 false
console.log(x5.constructor===Bmw);//2번 과 같이 할시 false

/*
난잡하다고 하여 2번과 같이 여러 속성들은 묶어서 객체리터럴의 속성으로 모두 넣어버리면 
Bmw의 생성자가 사라진다. 
내가 Deepdive에서 공부했던 것처럼 constructor프라퍼티는 기본적으로 Bmw.prototype에 들어있다.
그런데 Bmw.prototype을 객체리터럴로 다시 정의해
버리니 생성자가 사라져 버리는 것이 당연하다. 한가지 주목할 것이 Bmw객체를 위와 같이 생성하면
x5인스턴스 자체가 color프라퍼티는 갖는다는 것이다. 뭐 당연하긴 하다.
혹시 그럼에도 불구하고 객체 리터럴로 Bmw.prototype을 정의하고 싶다면 아래와 같은 방법이 있다*/

/*
Bmw.prototype={
    constructor:Bmw
    wheel:4,
    drive(){
        console.log("driving...");
    },
}*/

 

다음으로 함수객체의 멤버변수의 값을 함부로 외부에서 바꿀수 없게 하는 JS의 방법을 소개하고자 한다. Java에서는 외부에서 클래스 안의 멤버변수의 값의 변경을 막기 위해 접근제한자를 사용한다. 하지만 JS에서는 좀더 원시적인(?) 방법을 사용한다. 방법은 아래와 같다.

const Bmw=function(color){
    const c=color;
    this.getColor=function(){
        return c;
    }
}