[JavaScript] 클래스 정의 API ㅡ 2/3

총 5개의 클래스가 존재하는데 먼저 가장 간단한 Coordinate 클래스를 정의하면 다음과 같습니다.

var Coordinate = Class({
    name: "Coordinate",
    construct: function(x, y) {
        this._x = x;
        this._y = y;
    },
    methods: {
        getX: function() {
            return this._x;
        },
        getY: function() {
            return this._y;
        },
        toString: function() {
            return "(" + this._x + ", " + this._y + ")";
        }
    }
});

앞서 언급했듯이 자바스크립트에서 클래스의 정의는 개발자 스스로에 대한 약속이 필요하다고 하였습니다. 그 약속에 해당하는 부분이 바로 4,5번 코드에 있는 _x, _y 변수입니다. 변수명 앞에 밑줄(_)을 하였음으로 해서 이 변수는 private 변수라는 약속을 정한 것입니다. 개발자 스스로의 약속이므로 이 약속을 깼다고 하여도 자바스크립트는 어떤한 오류를 제공하지 않습니다. 물론 이러한 약속에 대해서 경고를 제공하도록 할 수 있으나 그렇게 되면 클래스 정의 API가 더욱 복잡해지게 될 것입니다. 그리고 7번의 methods 객체에 필요한 매서드를 추가하였습니다.

이제 다음은 추상 클래스인 Shape 클래스에 대한 코드입니다.

var aShape = Class({
    name: "aShape",
    methods: { 
        draw: function() { throw "abstract method"; }
    }
});

코드를 살펴보면, 4번에 draw라는 추상 매서드를 제공하였습니다. draw 매서드 역시 추상 매서드다라는 약속이지 자바스크립트 언어 차원에서 추상 매서드로 강제하지 않습니다. 대신 이 매서드를 호출하게 되면 예외를 발생하도록 하였습니다.

다음으로 위의 추상클래스인 Shape를 구현하는 Rectangle와 Circle 클래스에 대한 코드를 살펴보겠습니다.

var Rectangle = Class({
    name: "Rectangle",
    construct: function(w, h) {
        this._width = w;
        this._height = h;
    },
    methods: {  
        draw: function() { 
            alert("draw Rectangle");
        },
        getWidth: function() {
            return this._width;
        },  
        getHeight: function() {
            return this._height;
        }
    },
    requires: [aShape]
});

var Circle = Class({
    name: "Circle",
    construct: function(x, y, r) {
        this._x = x;
        this._y = y;
        this._r = r;
    },
    methods: {
        draw: function() {
            alert("draw Circle");
        },
        getCenter: function() {
            return new Coordinate(this._x, this._y);
        },
        getRadius: function() {
            return this._r;
        }
    },
    requires: [aShape] 
});

동시에 Rectangle와 Circle 클래스에 대한 코드를 보이는 이유는 이 두개가 서로 동일한 구조를 갖고 있음을 강조하기 위함입니다. 이 두 클래스는 Shape의 추상 매서드인 draw를 분명히 구현하고 있으며 각기 자신에게 맞는 매서드를 추가적으로 정의하고 있습니다. 여기서 추상매서드를 구현하고 있는지에 대한 검사는 18번과 39번의 코드에서처럼 requires 속성을 통해 검사를 해주게 됩니다. 만약 이 requires 속성을 통해 추상 매서드를 구현하지 않았을 경우 예외를 친절하게 예외를 발생해 주도록 클래스 정의 API를 구현하였습니다.

[JavaScript] 클래스 정의 API ㅡ 1/3

자바스크립트는 C++이나 C#/Java 등에서와 같이 클래스(Class)를 직접적으로 정의할 수 있는 API를 제공해 주지 않습니다. 그러나 자바스크립트에서는 프로토타입(Prototype)을 이용하여 클래스의 특성을 제공할 수 있습니다. 그러나 이러한 프로토타입 기반의 클래스 정의에 대한 API는 개발자가 직접 만들어 사용해야 합니다. 게다가 비록 프로토타입을 이용해 클래스 개념을 자바스크립에서 사용할 수 있다고는 하여도 C++이나 C#/Java와 같은 완전한 클래스를 정의하지는 못합니다. 대신 개발자는 스스로 엄격한 약속을 통해 이러한 부족함으로 매꿔야만 합니다.
아래의 코드는 자바스크립트에서 클래스를 정의하기 위한 API의 한가지 예입니다. 자바스크립트를 이용해 클래스를 정의하는 API는 매우 다양하고 많습니다. 그중 제가 필요에 의해 아래의 제시한 클래스 정의 API는 데이비드 플래너건의 자바스크립트 완벽가이드라는 책을 많은 부분 참고했으며 불필요한 부분을 제거한 예입니다.

function Class(x) {
    var classname = x.name;
    var superclass = x.extend || Object;
    var constructor = x.construct || function() {};
    var methods = x.methods || {};
    var statics = x.statics || {};
    var requires = x.requires || [];
    var proto = new superclass();

    for(var p in proto) if(proto.hasOwnProperty(p)) delete proto[p];
 
    proto.constructor = constructor;
    proto.superclass = superclass;
    proto.classname = classname; 
    constructor.prototype = proto;
 
    for(var p in methods) proto[p] = methods[p];
    for(var p in statics) constructor[p] = statics[p];

    for(var i=0; i        var c = requires[i];
        for(var p in c.prototype) {
            if(typeof c.prototype[p] != "function" || 
                p == "constructor" || p == "superclass") continue;
   
            if(p in proto && typeof proto[p] == "function" && 
                proto[p].length == c.prototype[p].length) continue;
    
            throw new Error(classname + " class requires " + p + " method.");
        }
    } 

    return constructor;
}

위의 클래스 정의 API는 다음과 같이 클래스의 4가지 본연의 기능에 충실할 것을 전제로 하였습니다.

  • 클래스를 정의하는 방법 제공할 것
  • 클래스 상속을 지원할 것
  • 추상 클래스을 구현할때 해당 추상 클래스의 모든 매서드를 구현하고 있는지 검사할 것
  • 객체가 어떤 클래스의 타입인지 검사할 수 있도록 할 것

위의 전제 조건들에 대해서 실제 위의 클래스 정의 API를 대한 사용예를 통해 살펴보겠습니다. 위의 클래스 정의 API를 통해 다음과 같은 클래스 계층을 구성하고자 합니다.


사용자 삽입 이미지Shape는 draw라는 매서드를 갖는 추상 클래스입니다. 그리고 이 Shape를 구현하는 클래스로 Rectangle과 Circle 클래스가 있습니다. 여기에 PositionedRectangle라는 클래스는 Rectangle를 상속받습니다. 또한 Coordinate라는 클래스가 존재하며 이 클래스는 PositionedRectangle와 Circle 클래스 사용합니다.

[JavaScript] 주의해야 할 ‘변수에 대한 호이스팅(hoisting)’

자바스크립트에서 변수에 대한 호이스팅이라는 개념이 있습니다. 함수 안에 정의된 모든 변수는 선언된 그 위치에 상관없이 함수의 윗부분으로 끌어올려(hoist)된다라는 개념입니다. 여기서 주의할 점은 자바스크립트에서 함수도 객체 변수이며 이 함수가 호이스팅될때입니다.. 함수를 정의하고 선언하는 방법을 크게 2가지로 구분할 수 있습니다.

  • 표현식에 의한 정의
  • 선언문에 의한 정의

먼저 표현식에 의한 정의의 예는 다음과 같습니다.

var bar = function() {
     // 표현식에 의한 함수 정의 
}; 

다음은 선언문에 의한 정의의 예입니다.

function foo() { 
    // 선언문에 의한 함수 정의
} 

이러한 함수 정의가 함수 안에서 이루어질때 호이스팅에 차이가 발생하게 됩니다.

function test() { 
    alert(typeof foo); 
    alert(typeof bar); 

    function foo() { 
        // 선언문에 의한 함수 정의 
    } 
    
    var bar = function() {
         // 표현식에 의한 함수 정의 
    }; 
} 

test();

위의 코드를 실행해 보면 처음에는 “function”이라는 메세지가 표시되고 다음에는 “undefined”라는 메세지가 표시됩니다. 이는 표현식에 대한 함수 정의시에 함수 객체가 호이스팅될때 함수의 코드부분은 호이스팅되지 않지만 선언문으로 함수가 정의될 경우에는 함수의 코드부분까지도 호이스팅된다는 것을 알 수 있습니다. 

[JavaScript] 클래스 상속

자바스크립트에서 클래스의 상속 개념을 정리해 보고자 합니다. 먼저 크기값(width, height) 만을 갖는 Rectangle라는 클래스가 있고 이 클래스를 상속받으면서 위치값(x, y)를 갖는 PositionedRectangle 클래스를 만들어 보는 예를 통해 정리하고자 합니다. (이 글은 인사이트의 자바스크립트 완벽가이드 내용을 읽고 이해하여 짧게 요약한 글입니다)

가장 먼저 Rectangle이라는 클래스 정의 코드입니다.
function Rectangle(w, h)
{
    this.width = w;
    this.height = h;
}

Rectangle.prototype.area = function()
{
    return this.width * this.height;
}

Rectangle.prototype.toString = function() 
{
    return "[" + this.width + ", " + this.height + "]";
}

생성자 함수에서 크기값에 대한 인자 w, h를 받습니다. 그리고 area라는 매서드와 toString이라는 매서드를 정의하고 있습니다. 이후에 Rectangle 클래스를 상속받아 toString 함수를 재정의할때 부모 클래스의 toString을 호출하는 방법을 살펴보도록 하겠습니다.

자, 이제 PositionedRectangle이라는 클래스를 Rectangle 클래스를 상속받아 정의하는 코드를 보겠습니다.
function PositionedRectangle(x, y, w, h)
{
    Rectangle.call(this, w, h);
    
    this.x = x;
    this.y = y;
}

3번 코드에서 상속받을 Rectangle 클래스를 지정합니다. 그리고 PositionedRectangle의 부모 클래스가 Rectangle이라는 것을 명확히 하도록 다음 코드를 추가합니다.

PositionedRectangle.prototype = new Rectangle();

위의 코드의 문제점은 PositionedRectangle의 생성자 함수까지도 Rectangle로 지정된다는 점입니다. PositionedRectangle의 생성자 함수를 다시 자기의 것으로 지정되도록 다음 코드를 추가합니다.

PositionedRectangle.prototype.constructor = PositionedRectangle;

또 한가지 문제가 있는데, 그것은 중복된 데이터입니다. PositionedRectangle는 Rectangle로부터 상속을 받았으므로 Rectangle의 width와 height 데이터를 갖습니다. 그런데 이 값이 각각 2개씩 중복(PositionedRectangle에 하나, PositionedRectangle.prototype에 하나)되어 갖고 있습니다. 해서 중복되지 않도록 제거해 주는 코드가 필요합니다.

delete PositionedRectangle.prototype.width;
delete PositionedRectangle.prototype.height;

이제 옵션으로 PositionedRectangle 자신의 함수를 추가하는 코드입니다.

PositionedRectangle.prototype.contains = function(x y)
{
    return (x > this.x && x < this.x + this.width 
        && y > this.y && y < this.y + this.height);
}

끝으로 부모 클래스의 메서드 함수를 재정의할때 부모 클래스의 메서드 함수를 호출하는 방법입니다. toString 함수를 예로 정리하면, PositionedRectangle에 다음처럼 toString 함수를 재정의할 수 있습니다.

PositionedRectangle.prototype.toString = function()
{
    return "(" + this.x + ", " + this.y + ") " 
        + Rectangle.prototype.toString.apply(this);
}

[JavaScript] 모조 클래스

자바스크립트에서 클래스(엄격히 말해 모조 클래스)를 작성하는 내용에 대한 정리입니다. 일반적으로 다음과 같이 5단계에 걸쳐 클래스를 정의합니다.

  1. 생성자 함수 정의
  2. 인스턴스 프로퍼티 정의
  3. 인스턴스 매서드 정의
  4. 클래스 매서드 정의
  5. 클래스 프로퍼티 정의

위의 5단계를 Complex라는 클래스를 자바스크립트 방식으로 정의해 보는 예를 통해 정리하면, 먼저 생성자 함수의 정의는 다음과 같습니다.

function Complex(real, imaginary)
{
    // ..
}

클래스의 이름은 첫자를 대문자로 하여 비록 함수이지만 클래스라는 의미를 강조합니다. 다음으로 인스턴스 프로퍼티를 정의하면…

function Complex(real, imaginary)
{
    this.x = real;
    this.y = imaginary;
}

생성자 함수 안에 this라는 키워드를 통해 새로운 인스턴스 변수를 추가합니다. 이제 인스턴스 매서드를 정의하는데, 매서드는 하나의 클래스에 대한 모든 인스턴스가 공유하도록 하기 위해 prototype 객체를 사용하며 예로 다음과 같습니다.

Complex.prototype.magnitude = function()
{
    return Math.sqrt(this.x * this.x + this.y * this.y);
}

이제 클래스 매서드를 추가하는 방법입니다. 클래스 매서드는 인스턴스를 대상으로 하는 것이 아니므로 this를 사용할 수 없으며 생성자 함수(자바스크립트에서 클래스) 이름으로 접근할 수 있습니다. 예로 다음과 같습니다.

Complex.sum = function(a, b)
{
    return new Complex(a.x+b.y, a.y+b.y);
}

끝으로 클래스 프로퍼티입니다.

Complex.ZERO = new Complex(0, 0);

이 글은 인사이트 출판사의 자바스크립트 완벽가이드를 참고로 하여 정리한 글입니다.