본문 바로가기

카테고리 없음

[JavaScript] 객체의 메소드는 왜 인스턴스가 아닌 프로토타입에 추가해야하는걸까?

목차
1. 들어가며
2. 객체의 의미
3. 자바스크립트에서의 객체
4. 객체의 특징, 상속
5. 객체의 메소드를 프로토타입으로 선언하는 것이 인스턴스에 선언하는 것보다 왜 메모리적으로 나은 방법인가?
6. 결론
7. 참고자료

 

들어가며

모던 자바스크립트 입문 책의 객체 파트에서 프로토타입에 대한 이야기가 나왔다. 그런데 단 한문장으로만 설명되어있는 인스턴스에 메소드를 추가하는 것보다 프로토타입에 추가하는 것이 메모리 낭비를 하지 않는 방법이다 라는 문장이 의심되었다.

 

왜 프로토타입에 추가하는 것이 인스턴스에 메소드를 추가하는 것보다 더 메모리적으로 나은 방법일까?

 

이를 알기 위해서 객체의 의미, 자바스크립트에서는 어떻게 객체를 선언하고, 이 객체를 어떻게 상속하는지에 대해 가볍게 내 언어로 작성해보려고 한다. 객체부터 다루는 이유는 프로토타입이 객체(함수)와 많은 연관이 있기 때문이다.

참고로 이 글에서는 다른 글과는 다르게 함수라고 하지 않고 객체라고 칭해볼 생각이다. 그 이유는 함수라고 한다면 좀 더 명확할 수 있지만 객체 이야기를 하고 있기 때문이다. 그러나 자바스크립트에서 객체를 함수로 선언해왔었기 때문에 함수 역시 내가 말하는 프로토타입에 대한 특성을 가지고 있다.

 

 

객체의 의미

객체란, 사물이다.
한마디로, TV, 리모컨, 컴퓨터, 핸드폰, 마우스 등 고유의 프로퍼티가 있고, 어떠한 기능을 하는 메소드가 있는 사물이다.

 

리모콘

 

예를 들어 자동차는 기어, 속도, 기름량, 기름 종류 등의 프로퍼티를 가지고, 기어변속, 엑셀밟기, 브레이크밟기, 시동켜기 등의 메소드를 가진다. 벤츠, 아우디, 람보르기니는 자동차 객체를 상속받아서 자동차의 프로퍼티와 메소드를 가지면서 자신의 프로퍼티, 메소드를 가질 수 있는 자식 객체로 선언될 수 있다.

 

자동차 객체를 상속받는 벤츠, 아우디, 람보르기니

 

 

자바스크립트에서의 객체

그렇다면 자바스크립트에서 객체는 어떻게 생성하고, 사용할까?

자바스크립트에서 ES5에서 객체를 생성하는 방법과 ES6에서 생성하는 방법이 다르다.
물론 객체를 생성하는 방법은 상당히 많으나 여기서는 비교하기 위해 생성자 함수 방식만을 다루려고 한다.

 

// ES5
function Car5(name, color) {
    this.name = name
    this.color = color
    this.speed = 0
}

Car5.prototype.print = function () {
    console.log(this.name, this.color, this.speed)
}

Car5.prototype.start = function (speed) {
    this.speed = speed
    console.log("5 - This car is started !")
}

Car5.prototype.stop = function () {
    this.speed = 0
    console.log("5 - This car stopped !")
}

 

위는 ES5에서 사용하는 방식이다. 이는 사실 객체라고 딱 말할 수 없고 함수다.

ES5에서의 특징은 constructor가 없고, function 키워드로 작성한다는 것이다.
이때 인스턴스 안에 메소드를 선언해도 되지만 아까 말했듯이 메모리 이슈가 있다.

 

// ES6
class Car6 {
    construnctor(name, color) {
        this.name = name
        this.color = color
        this.speed = 0
    }
}

Car6.prototype.print = function () {
    console.log(this.name, this.color, this.speed)
}

Car6.prototype.start = function (speed) {
    this.speed = speed
    console.log("6 - This car is started !")
}

Car6.prototype.stop = function () {
    this.speed = 0
    console.log("6 - This car stopped !")
}

 

위는 ES6에서 사용하는 방식이다.

ES6는 그와 조금 다르게 class라는 키워드를 세워 좀 더 명확하게 객체라는 것을 알 수 있게 하였고, 내부에 constructor로 생성될 때 어떤 변수가 생성되는지 명확하게 알 수 있게 하였다. 또 ES6에서 class로 객체를 선언하게 되면 인스턴스에 메소드를 추가하는 것이 문법적으로 아예 불가능하다. 반드시 프로토타입에만 메소드를 추가할 수 있게 하였다. 또 constructor가 없으면 new 호출이 불가하며, class의 경우 function과 다르게 호이스팅이 되지 않는다.

 

 

객체의 특징, 상속

객체는 부모 클래스와 자식 클래스로 나눌 수 있다. 위에서 벤츠, 람보르기니, 아우디와 자동차의 관계에 대해 언급했었는데 이를 조금만 바꿔서 컬러만 다르게 자식 클래스를 만들어보려고 한다.

 

// ES6
class RedCar extends Car6 {
    construnctor(name) {
        super(name, "red")
    }
}

위의 코드는 Car6 객체를 상속한 RedCar 객체이다. 이때 name은 생성자가 생길 때 추가되며, 색의 경우는 red로 지정되어있다.

 

const test1 = new RedCar("test1")

test1.print() // test1 red 0

만약 test1이라는 차가 RedCar라면 위와 같다. 이름을 test1로 지정하고 print() 메소드를 호출하면 test1 red 0이라고 출력될 것이다. 그 이유는 print() 메소드는 Car6의 프로토타입에 추가되었기 때문에 상속하여 RedCar라는 객체가 Car6에서 추가한 메소드를 사용할 수 있는 것이다.

 

만약 부모 객체 인스턴스에 메소드를 추가한다면 자식 객체가 그 메소드를 사용할 수 있는가?

아니다.

사용할 수 없다. 인스턴스에 선언된 메소드는 그 객체에서만 사용할 수 있다.

예를 들어 지금 Car6 인스턴스에 방향을 바꿔주는 change라는 메소드가 있다고 하자. 그렇지만 RedCar 객체인 test1은 change를 사용할 수 없다. 사용하려고 하면 선언되지 않은 메소드라는 에러가 발생한다.

 

 

객체의 메소드를 프로토타입으로 선언하는 것이 인스턴스에 선언하는 것보다
왜 메모리적으로 나은 방법인가?

이 질문에 답을 하기 위해서는 인스턴스로 선언했을 때와 프로토타입으로 선언했을 때 어떻게 메모리에 차지하게 되는지 각각을 알 필요가 있다.
여기서는 객체를 다른 것을 가져오려고 한다.

 

// ES6
class Animal {
    constructor(name) {
        this.name = name
        this.doing = ""
    }
}

이 Animal 객체에 doing을 바꾸는 walk 메소드를 넣으려고 한다.

 

1. 만약 인스턴스에 선언하게 된다면

// ES6
class Animal {
    constructor(name) {
        this.name = name
        this.doing = ""
    }

    walk() {
        this.doing = "walk"
    }
}

const animal1 = new Animal("animal1")
const animal2 = new Animal("animal2")
const animal3 = new Animal("animal3")

위와 같은 코드가 나올 것이며, animal1~3은 다음 이미지와 같을 것이다.

 

여기서 중요한 점은 walk() 메소드가 같은 기능을 함에도 불구하고 메모리를 차지하고 있다는 점이다.

 

2. 만약 프로토타입에 선언하게 된다면

// ES6
class Animal {
    constructor(name) {
        this.name = name
        this.doing = ""
    }
}

Animal.prototype.walk = function () {
    this.doing = "walk"
}

const animal4 = new Animal("animal4")

이때 prototype은 prototype link와 prototype object로 이루어진다.

객체(함수)가 정의될 때 2가지 일이 일어나는데 첫번째, 해당 객체에 constructor(생성자) 자격을 부여한다. 생성자 자격이 있어야 new를 통해 객체를 생성할 수 있다. 두번째, 해당 객체의 prototype object를 생성하고 연결한다.

객체가 생성될 때 생성된 객체는 prototype이라는 속성을 가지게 된다. 이 prototype 속성은 prototype object라는 객체에 접근을 가능하게 해준다. 기본속성으로 constructor__proto__ (protoype link) 를 가진다.

이렇게 코드를 작성한다면 animal4의 상태는 다음과 같다.

 

객체 prototype의 constructor와 객체의 prototype이 상호로 연결되어있기 때문에 이것을 프로토타입 체인이라고 부르는데 이 특징 덕분에 객체의 메소드를 프로토타입에 선언해야 메모리 낭비가 되지 않는다는 이야기였던 것이다.

이렇게 프로토타입에 선언하게 되면 중복되는 메소드가 계속 메모리를 차지하는 것이 아니라 같은 메소드를 이용하고 싶다면 animal4.walk()와 같이 사용할 수 있다.

이 메모리 최적화 이슈는 상속도 마찬가지로 적용된다.

 

 

결론

객체(함수)가 정의될 때 해당 객체에 constructor(생성자) 자격을 부여하고, 해당 객체의 prototype object를 생성하고 연결한다.

객체의 메소드는 메모리 낭비를 피하기 위해 인스턴스에 선언하지 않고, 프로토타입에 선언하도록 하자.

 

 

참고자료

 

JavaScript 객체 기본 - Web 개발 학습하기 | MDN

이 글에서는 JavaScript 객체와 관련된 기본적인 문법을 살펴보고 이전 코스에서 학습해서 이미 알고 있는 JavaScript 의 특징들과 우리가 이미 사용하고 있는 기능들이 이미 객체와 관련되어 있다는

developer.mozilla.org

 

상속과 프로토타입 - JavaScript | MDN

Java 나 C++ 같이 클래스 기반의 언어를 사용하던 프로그래머는 자바스크립트가 동적인 언어라는 점과 클래스가 없다는 것에서 혼란스러워 한다. (ES2015부터 class 키워드를 지원하기 시작했으나,

developer.mozilla.org

 

[JS] 프로토타입 이해하기 with 프로토타입 체인

자바스크립트는 Java, Python 처럼 객체지향언어(Object-Oriented Programming, OOP)입니다. 객체지향이란 프로그램을 그저 데이터와 처리방법으로 나누는게 아니고, 프로그램을 다수의 "객체"로 만들고,

kingofbackend.tistory.com

http://insanehong.kr/post/javascript-prototype/

 

Javascript 기초 - Object prototype 이해하기 | Insanehong's Incorrect Note

소개 이번 글에서 다룰 내용은 자바스크립트의 프로토타입 상속(prototypal inheritance) 이라는 확장과 객체의 재사용을 가능하게 해주며 class 기반으로 인스턴스를 생성하지 않는 자바스크립트에서

insanehong.kr

 

 

반응형