블로그 내 검색

2011. 12. 13.

prototype property and prototype chain (수정)

# 글을 쓴지 좀 됐는데 방문자가 아직 많네요 ~ 좋습니다 :)
# 곧 글을 대폭 수정할 예정입니다; 오래전에 쓴 글이라 잘못된게 많네요

prototype

1 명사

개요


자바스크립트의 모든 객체에는 자신이 생성될 당시의 원본 객체에 대해 숨겨진 연결을 갖는다.
그 연결의 대상이 되는 객체를 prototype이라고 한다.
자바스크립트의 모든 객체는 거슬러 올라가 보면 Object를 원본 객체로 하여 생성되어 있다.

이 말은 다음과 같이 해석할 수도 있다.
"자바스크립트의 모든 객체는 Object의 prototype에 모두 연결되어 있다."

객체지향적으로 말하면,
"자바스크립트의 모든 객체는 Object를 상속한다" 라고도 할 수 있다.

사실 클래스 기반의 객체지향 언어를 공부한 사람이라면 이미 어리둥절 할 것이다. 객체에서 객체를 만든다니.
이렇게 객체에서 객체를 만드는, 객체를 원형(prototype)으로 하여 객체의 복제 방법으로 새로운 객체를 만들어 내는 방식을 프로토타입 기반 프로그래밍이라고 부른다.


Object-Oriented

객체지향(Object-Oriented)을 하면 보통 프로그래머들은 클래스, 인스턴스, 인터페이스등을 떠올릴 것이다. 이런 개념들로 프로그래밍하는 것을 클래스 기반 객체지향 프로그래밍이라고 한다.
자주 쓰이는 C++, Java, C# 등의 많은 유명한 언어들이 이러한 방식을 가지고 있다.

그러나 객체지향 프로그래밍이 모두 클래스를 쓰는 것은 아니다.

프로토타입 기반 객체지향 프로그래밍이라는 개념도 있다. 이 방식은 객체지향의 개념을 다른 방식으로 프로그래밍한다. 객체의 원형인 프로토타입이 있고 그것을 사용하여 새로운 객체를 만든다. 새 객체를 만들었다면 그것을 바로 쓸 수도 있고 다시 그 객체에 무언가를 더 추가하여 그 객체를 사용하여 또 다른 새로운 객체를 만들어낸다.
Rua, Ecmascript(javscript, actionscript 등을 포함)하는 언어가 이러한 방식이다.


prototype property

자바스크립트에서는 클래스가 없고(class-less) 프로토타입 지향(prototype-oriented) 방식의 프로그래밍이라고 설명했다.

이것을 나타내는게 prototype 프로퍼티이다.
모든 '함수' 객체는 prototype 이라는 프로퍼티를 가진다.

코드로 prototype을 하나하나 살펴보자

var IamFunc = function() {
    var a = 1;
    function inner() {}
}
// 모든 함수는 prototype 프로퍼티를 가지고 있다.
typeof IamFunc.prototype // object

// 객체는 prototype 프로퍼티를 가지고 있지 않지만,
// prototype에 숨겨진 연결이 있어 참조만은 가능하다.
// 일부 엔진 구현에서는 __proto__ 속성으로 객체도 prototype 속성에 접근할 수 있다
var aObject = new IamFunc();

// 객체 리터럴은 생성자가 내장 함수인 Object를 통해 만들어진 객체 표현식이다.
var obj = {
    name: "object",
    method: function() {
        return "method";
    }
}

typeof IamFunc.prototype; // object;
typeof aObject.prototype; // undefined;
typeof aObject.constructor.prototype; // object; == IamFunc.prototype
typeof obj.prototype; // undefined
코드를 보면 알겠지만, 객체에는 prototype 프로퍼티가 없다. 오로지 함수 객체에만 존재한다.
그리고 prototype 객체 또한 객체이기에 javascript Object의 기본 속성을 그대로 가지고 있고, 추가적으로 constructor라는 자신을 참조하는 속성을 가지고 있다.
constructor는 자기 자신인 함수를 참조하는 속성이고 함수는 prototype 객체의 프로퍼티로 constructor를 가지고 있으니 순환 참조(?) 관계다.

// 앞으로 생성할 객체의 프로토타입이 될 객체를 만든다.
var carProto = {
    name: "object",
    model: "basic",
    madeby: "unknown",
    color: "free",
    getModel: function() {
        return this.model;
    },
    toString: function() {
        return "this is the " + 
            this.name + ", model is " + 
            this.model + " year. color is " + 
            this.color;
    }
}

// Grandure 객체 생성자를 만든다.
var Grandure = function() {
    this.model = "2011";
    this.name = "grandure";
    this.madeby = "hyundai";
    this.color = "black";
}
// 프로토타입 프로퍼티에 carProto를 지정한다.
Grandure.prototype = carProto;

// Tico 객체 생성자를 만든다.
var Tico = function() {
    this.model = "1996";
    this.name = "tico";
    this.madeby = "daewoo";
    this.color = "white";
}
// 프로토타입 프로퍼티에 carProto를 지정한다.
Tico.prototype = carProto;

// 객체 생성 후 toString 호출.
var myCar = new Grandure();
var yourCar = new Tico();
myCar.toString(); // "this is the grandure, model is 2011 year. color is black"
yourCar.toString(); // "this is the tico, model is 1996 year. color is white"
위 코드에서는 prototype 객체를 기반으로 새로운 객체를 생성하고 있다. 
prototype 객체의 모든 프로퍼티 및 메서드를 이어받아 새로운 객체가 만들어진다.
// 원형 프로토타입을 수정한다.
carProto.toString = function() {
    return "this is the " + this.name + ", made in " + this.madeby;
}

// 두 객체에 변화된 toString이 즉각적으로 적용된다.
myCar.toString(); // "this is the grandure. made in hyundai"
yourCar.toString(); // "this is the tico. made in daewoo"
만일 어떤 함수의 prototype 속성을 변경하면, 그 함수의 prototype을 기반으로 해서 생성된 모든 객체에 즉각적으로 그 변화된 사항이 적용된다. 주의할 점은 이미 생성된 객체에서는 prototype에서 상속된 객체를 덮어쓸 수 없다.(read-only) 객체에는 prototype 프로퍼티가 없기 때문이다. 속성을 덮어쓰는 시도를 하더라도 객체 자체의 속성이 새로 지정될 뿐, prototype에는 변화가 없다.
// toString을 수정한다.
yourCar.toString = function() {
    return "this car is very small";
}

// 변경되었다
yourCar.toString(); // "this car is very small"

// 원본은 변경되지 않는다.
carProto.toString(); // "this is the object, made in unknown"

prototype-chain (prototype-hidden-link)

객체는 prototype에 숨겨진 연결을 갖는다고 했다. 어떠한 방식일까.
객체에서 
prototype을 참조하는 방식은 다음과 같다. 코드로 풀어보자.

내장 객체 Object에 함수 swim을 추가한다.

var swimmable = new Object();
swimmable.swim = function(){ 
    return "swimming now";
}
Duck이라는 이름의 함수를 선언하고 위에 선언한 내장 객체를 프로토타입으로 지정한다.
이 객체에 name이라는 속성과 getName이라는 함수를 추가한다.
그리고 Duck을 생성자로 새로운 객체를 만든다. 이 aDuck이라고 이름짓자.

var Duck = function(){}
Duck.prototype = swimmable;
Duck.prototype.comment = "I can Swim!";
Duck.prototype.getComment = function() {
    return this.comment;
}
var aDuck = new Duck();
 이제 다시 Goose라는 함수를 선언한 뒤, 위에서 생성된 aDuck을 프로토타입으로 지정하고, age라는 속성과, getAge라는 함수를 추가한 뒤 Goose를 생성자로 새로운 객체를 생성한다. 이 객체는 aGoose라고 붙인다.

var Goose = function() {
    this.age = 5;
}
Goose.prototype = aDuck;
Goose.prototype.getAge = function() {
    return this.age;
}
var aGoose = new Goose();
이제 Penguin 함수를 만들고, 위에서 생성된 aGoose을 프로토타입으로 지정한다.
그리고 객체를 만들어 aPenguin 이라고 한다. 그 뒤 aPenguin에 getHometown이라는 메서드를 추가한다.

var Penguin = function(home) {
    this.hometown = home;
}
Penguin.prototype = aGoose;
Penguin.prototype.getHometown = function() {
    return this.hometown;
}
var aPenguin = new Penguin("Antarctica");
훌륭한(?) 오리같은(?) 펭귄 객체가 만들어졌다.
이 펭귄의 메서드는 주어진게 없다.

aPenguin.swim(); // swimming now
aPenguin.getComment(); // I can Swim!
aPenguin.getAge(); // 5
aPenguin.getHometown(); // Antarctica
하지만 헤엄(swin)도 칠줄 알고 나이가 몇인지(getName), 어디 사는지(getHometown)도 알 수 있다.
만일 swim을 호출했을때 언어 처리기에서 어떻게 처리하는지 보자.

swim 함수가 호출될 경우 aPenguin에는 그러한 
함수가 없으므로 자신의 생성자의 prototype을 참조(prototype-chain)한다. 그러나 Penguin의 prototype에도 swim은 없다. 그렇다면 부모였던 Goose의 프로토타입을 참조해보지만 역시 없다 그렇다면 Duck의 프로토타입을 참조한다. Duck의 프로토타입은 swimmable라는 객체로 swim 함수를 가지고 있다.
그렇기에 성공적으로 swim이 실행되어 펭귄이 헤엄을 칠수 있게 된다.

여기서 재미있는 점이 있다.
펭귄(penguin)은 오리가 아니다. 거위(Goose)는 오리과가 맞지만 말이다.
하지만 자바스크립트 언어에서는 어떠한 객체가 오리처럼 행동한다면 그것은 오리라고 가정할 수 있다. (
Duck-typing)


사족


사실 클래스 기반의 언어에 익숙한 사람은 이러한 뭔가 이질적인 문법과 개념에 익숙치 않을 수 있다.
하지만 이러한 방식이 있다는 것을 이해하고 익숙해진다면 색다른 프로그래밍에 흥미를 느낄거라고 생각한다.

사족이지만, 이러한 프로토타입의 방식에 의문을 품거나, 비효율적이라고 생각한 ECMA 인터내셔널의 표준 제안자들은 자바스크립트 계열 언어의 4번째 개편안에서 클래스 기반의 특성을 다수 첨가하려다가 많은(특히 프로그래머) 사람들에게 반발을 샀으며 뜨거운 논쟁까지 불러일으켰다고 한다. 결국 4번째 개편안은 버려지게 되었고 지금 한창 5가 제안중이다. (이미 나왔나;)

특히 더글라스 크록포드는 이 상황에서


"현업 개발자가 아닌 언어 디자이너와 비평가들은 언어를 개선하려는 시도조차 하지 말라"

라고 했다고.

댓글 3개:

  1. 아직 공부중이지만, 해당 글로 많은 도움을 받았습니다. 좋은글 감사합니다.

    답글삭제
  2. 안녕하세요. 진짜 쉽게 잘 설명해주셨네요. 넷상에서 잘못되고 이상한데 집착하는 설명을 많이 봐서리.
    저도 결과적으로 메소드를 죄다 샹위 프로토 객체에 밀어넣어야 메모리가 절약되는거 보고..
    거참 희한하네. 왜 이런 식으로 (결과론적으로 이원화된) 구조를 만든건지 참 희한하다고 생각했었는데
    마지막에 보니 그런 사정이 있었군요. 현재 코드를 죄다 수정해야 되니 반발해서 그런건가요.ㅋㅋ;

    답글삭제