레이블이 상속인 게시물을 표시합니다. 모든 게시물 표시
레이블이 상속인 게시물을 표시합니다. 모든 게시물 표시

2007년 11월 19일 월요일

Prototype 1.6.0의 addMethods()에대한 고찰

요즘에 회사에서 JavaScript OOP 주제로 스터디를 하고 있는데요. 최근에 정식버전이 나온 prototype 1.6.0의 상속기능에 대한 주제로 K과장님(실명을 밝혀도 되려나? ^^;;;)께서 발표를 하셨는데 그 때 잘이해가 안되던 Class.addMethods()에 대해서 다시 정리해보게 되었습니다.
사용자 삽입 이미지

※ 본 포스트에 사용된 Animal클래스는 http://prototypejs.org/api/class/addMethods에 나와있는 클래스입니다.

1. firebug를 실행시켜보면 addMethods의 파라미터(source)는 클래스가 넘어오고 properties는 클래스가 가지고 있는 프로퍼티인 클래스 메소드, Function의 메소드와 prototype 등의 배열이 됩니다.

Object.keys(Animal) 을 출력했을 때 다음의 결과를 얻었습니다.

addMethods, superclass, subclasses, prototype, argumentNames, bind,
 bindAsEventListener, curry, delay, wrap, methodize, defer

2.
ancestor: ancestor는 this.superclass && this.superclass.prototype이 되게 됩니다. 이 때 && 연산의 결과를 알아보기 위해서 http://prototypejs.org/api/class/addMethods의 Animal 클래스로 테스트를 해봤습니다.

Animal을 출력했을 때는

function klass() {this.initialize.apply(this, arguments);}

Animal.prototype을 출력했을 때는
({initialize:(function (name, sound) {this.name = name;this.sound = sound;}),
 speak:(function () {alert(this.name + " says: " + this.sound + "!");})})

의 값을 얻었습니다.

그리고, Animal && Animal.prototype의 결과로는 Animal.prototype의 결과 값을 얻었습니다.

({initialize:(function (name, sound) {this.name = name;this.sound = sound;}),
 speak:(function () {alert(this.name + " says: " + this.sound + "!");})})


(궁금증, 개인적으로는 연산 결과로 logical 연산 값인 true나 false가 나올 줄 알았습니다. 왜그런지 아시는 분 있으신가요?)


3. for문과 if문의 부분은 $super가 첫 번째 argument로 정의되어 있는 Function을 찾는 경우를 의미합니다. (부모 클래스로부터 자식이 function을 상속받아서 $super로 부모의 메소드를 호출할 수 있는 경우가 되겠죠)

4. 이제 가장 궁금했던 value 부분을 살펴볼 것인데요. 가장 바깥의 Object.extend를 호출하는 부분은 value에 toString과 valueOf 메소드를 추가해주는 코드입니다. Object.extend의 첫 번째 파라미터로 넘겨지는 부분만 따로 때어내면 다음과 같습니다.
            (
                function(m) {
                      return function() {
                          return ancestor[m].apply(this, arguments)
                      };
                }
            )(property).wrap(method),

(이 코드가 어떻게 호출되면 과연 부모의 코드를 호출하게 되는 것일까 많은 고민을 했었습니다. -_-;;)

5. 먼저, 가장 처음 실행되는 부분만 따로 때서 생각해보면 (function(m){...})(property) 부분은 인터프리터에서 코드가 읽히면서 실행이 되게 되는데요. 인자로 받은 property의 값은 function(m)의 파라미터인 m으로 넘어가게 됩니다.
Animal.prototype.speak = function () { alert(this.name + " says: " + this.sound + "!"); }
이 코드가 for 루프로 들어가게 되면 m의 값이 'speak'로 넘어오게 되고 ancestor[m]은 function () { alert(this.name + " says: " + this.sound + "!"); }이 되게 됩니다. 즉, 실행된 코드는 실행시점에서 받은 파라미터를 넘겨서 Animal의  speak를 호출하게됩니다. (이 값이 나중에 Animal을 상속받는 자식의 메소드로 저장됩니다.)

            (
               function(m) {
                     return function() {
                         return ancestor[m].apply(this, arguments)
                     };
               }
           )(property)

여기까지 실행된 값은
function () { return ancestor[m].apply(this, arguments); } 됩니다.

6. 이제 마지막으로 wrap메소드를 살펴보면 다음과 같은 메소드로 구성됩니다. 위의 코드의 wrap()이 호출될 때 인자로 넘겨지는 wrapper는 Animal 클래스의 speak 메소드(function () { alert(this.name + " says: " + this.sound + "!"); })가 됩니다.

    wrap: function(wrapper) {
        // 그리고, __method는 5에서 넘겨지는
        // function() {return ancestor[m].apply(this, arguments);}
        // 가 되게 됩니다.
        var __method = this;
        return function() {
            // 그리고, 이 부모의 메소드가 첫 번째 인자로 넘어가게 됩니다. $super의 파라미터 위치에
            // 부모 메소드가 넘겨지게 됩니다.
            return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
        }
    },

7. 그리고, 이 결과 값이 자식의 prototype[property]로 추가되게 됩니다.


P.S. 좀 정신사납게 적은 글입니다만, 참고용으로만 봐주세요. 전체 코드를 다 본게 아니라서.. 쿨헉~
(''  ;;; 언제 다 본 적 있냐고 물으시면 난감합니다.~~)

테스트 출력에 사용한 샘플 파일입니다. (허접이긴 하지만 참고용으로 올립니다. )

※  3에서 던진 질문의 답을 알아냈습니다. JavaScript에서는 Short-Circuit Evalation 연산을 사용하게 되는데요. (expression1) && (expression2)의 연산에서 앞의 expression1이 false면 뒤의 expression2의 연산을 실행하지 않는 것을 Short-Circuit Evaluation이라 합니다. (시험에도 나왔었던 기억이.. -_-;;;)

Animal && Animal.prototype의 경우에는 Animal이 reference 값이 있으므로 true의 값이 떨어지게 되는데요. 이 때 연산의 결과는 expression1의 값과 상관없이 expression2의 연산의 결과에만 의존하게 되므로 expression2의 연산을 boolean으로 값으로 evaluation하지 않습니다.
사용자 삽입 이미지
※ Wikipedia의 내용을 더 살펴보니 Short-Circuit Evaluation을 실행하는 경우 앞의 expression1이 true인 경우 뒤의 연산을 하지 않는, 즉 마지막 값을 그대로 넘겨주는 언어가 여럿 있군요 ^^