[JS] 클래스

서로·2024년 10월 17일
2

JS

목록 보기
12/15
post-thumbnail

➊ 클래스

클래스는 공통 요소를 지니는 집단을 분류하기 위한 개념으로,
객체 지향 프로그래밍에서 특정 객체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀이다.

자바스크립트는 객체 지향 프로그래밍을 지원하지만
다른 언어와 달리 프로토타입 기반 언어이며 상속 개념이 존재하지 않는다.
(ES6부터 클래스 문법이 추가되었다.)

ES5에는 클래스의 개념이 존재하지 않았지만
프로토타입을 일반적인 의미에서의 클래스 관점에서 접근해보면
비슷하게 해석할 수 있는 요소가 있다.

생성자 함수의 프로토타입 객체 내부 메소드들은
프로토타입 체이닝에 의해 __proto__을 생략하고 참조하여
인스턴스에서 마치 자신의 멤버인 것처럼 사용할 수 있다.
이는 인스턴스에서 프로토타입의 메소드를 상속받았다고 볼 수 있다.

① 스태틱 멤버와 프로토타입 멤버

생성자 함수를 new 키워드로 호출하면 인스턴스가 생성된다.
이때 인스턴스는 스태틱 멤버와 프로토타입 멤버를 갖게 된다.

아래의 예제를 살펴보자!

var Rectangle = function (width, height) {
    this.width = width;
    this.height = height;
};

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

Rectangle.isRectangle = function (instance) {
    return instance instanceof Rectangle &&
        instance.width > 0 && instance.height > 0;
};

var rect1 = new Rectangle(3, 4);
console.log(rect1.getArea());   // 12
console.log(rect1.isRectangle(rect1));  // Error
console.log(Rectangle.isRectangle(rect1));  // true

Rectangle 이라는 이름의 생성자 함수를 정의하였다.
RectangleprototypegetArea 메소드를 정의하였고,
생성자 함수인 Rectangle에 바로 isRectangle 메소드를 정의하였다.

결론부터 말하자면 getArea프로토타입 멤버이며 isRectangle스태틱 멤버이다.
프로토타입 멤버는 인스턴스에서 __proto__을 생략하고 마치 자신이 상속받은 것처럼 사용할 수 있다.
그러나 스태틱 멤버는 인스턴스에서 직접 호출할 수 없으며
생성자 함수 자체를 this로 바인딩하여 호출할 수 있다.

따라서 위 예제 코드에서 rect1.isRectangle(rect1)는 에러가 발생하게 된다.
인스턴스에서 직접 스태틱 멤버를 호출했기 때문이다.

② 인스턴스 멤버 (참고)

var Rectangle = function (width, height) {
    this.width = width;
    this.height = height;
    // 인스턴스 멤버
    this.print = function () {
        console.log('사각형입니다.');
    };
};
// 프로토타입 멤버
Rectangle.prototype.getArea = function () {
    return this.width * this.height;
};
// 스태틱 멤버
Rectangle.isRectangle = function (instance) {
    return instance instanceof Rectangle &&
        instance.width > 0 && instance.height > 0;
};

생성자 함수 내에서 this 키워드를 사용하여 정의되는 멤버는 인스턴스 멤버이다.
위 예제에서는 this.width, this.height, this.print 메소드가 해당된다.

생성자 함수의 인스턴스가 생성될 때마다 해당 인스턴스에 대해 고유하게 속성과 메소드가 생성되며
별도의 메모리를 차지하기 때문에 많은 인스턴스를 생성할 경우 비효율적이다.

생성자 함수의 prototype 속성을 통해 정의되는 멤버는 프로토타입 멤버이다.
위 예제에서 getArea 메소드에 해당한다.

모든 인스턴스는 동일한 프로토타입 객체를 공유하며 프로토타입 체이닝을 통해 접근할 수 있다.
메소드가 여러 번 정의되지 않기 때문에 메모리 사용 측면에서 효율적이다.

➋ 프로토타입 체인을 이용한 클래스 상속 구현

ES6에 등장한 클래스에서 일정 부분은 사실 프로토타입을 활용하고 있는 것이기 때문에
ES5 체제 하에서 프로토타입 체인을 이용해 클래스 상속을 구현하는 방식을 공부해두면
JS의 클래스에 대해서도 잘 이해할 수 있다.

프로토타입 체인을 이용해 클래스를 흉내내기 위한 방법으로는 크게 3가지가 존재한다.

SuperClass의 인스턴스를 SubClass.prototype에 할당
Bridge 활용
Object.create 이용

위의 방법을 차례대로 살펴보도록 하자.

① SuperClass의 인스턴스를 SubClass.prototype에 할당

SuperClass의 인스턴스를 SubClass.prototype에 할당하고,
이 인스턴스의 프로퍼티를 모두 지운 다음에 더는 새로운 프로퍼티를 추가할 수 없게 함으로써 상속을 구현할 수 있다.

var extendClass1 = function (SuperClass, SubClass, subMethods) {
    SubClass.prototype = new SuperClass();
    SubClass.prototype.constructor = SubClass; // constructor 복구

    for (var prop in SubClass.prototype) {
        if (SubClass.prototype.hasOwnProperty(prop)) {
            delete SubClass.prototype[prop];
        }
    }

    // subMethods가 제공되면 추가
    if (subMethods) {
        for (var method in subMethods) {
            SubClass.prototype[method] = subMethods[method];
        }
    }

    // 프로토타입 동결
    Object.freeze(SubClass.prototype);
    return SubClass;
};

// 사용 예시
var Square = extendClass1(
    Rectangle,
    function (width) {
        Rectangle.call(this, width, width);
    }, { // getArea 메소드를 prototype 객체 안에 정의
        getArea: function () {
            return this.width * this.height;
        }
    }
);

② Bridge 활용

이 방법은 더글라스 크락포드가 제시해서 대중적으로 널리 알려진 방법이다.
SubClass에 직접 SuperClass의 인스턴스를 할당하는 대신
아무런 프로퍼티도 생성하지 않는 빈 생성자 함수(Bridge)를 하나 더 만들어서
prototypeSuperClassprototype을 바라보게끔 한 다음,
SubClassprototype에는 Bridge의 인스턴스를 할당하게 하는 것이다.

즉, 빈 함수에 다리 역할을 부여하는 것과 같다.

var extendClass2 = (function () { // 즉시 실행 함수
    var Bridge = function () {};
    return function (SuperClass, SubClass, subMethods) {
        Bridge.prototype = SuperClass.prototype;
        SubClass.prototype = new Bridge();
        SubClass.prototype.constructor = SubClass; // constructor 복구

        // subMethods가 제공되면 추가
        if (subMethods) {
            for (var method in subMethods) {
                SubClass.prototype[method] = subMethods[method];
            }
        }

        // 프로토타입 동결
        Object.freeze(SubClass.prototype);
        return SubClass;
    };
}) ();

이처럼 즉시 실행 함수 내부에서 Bridge을 선언해서 이를 클로저로 활용함으로써 메모리에 불필요한 함수 선언을 줄였다.

③ Object.create 이용

Object.create을 이용하면 SubClassprototype__proto__
SuperClassprototype을 바라보되 SuperClass의 인스턴스가 되지 않는다.
앞서 소개한 두 방법보다 훨씬 간단하면서 안전하다.

var extendClass3 = function (SuperClass, SubClass, subMethods) {
    SubClass.prototype = Object.create(SuperClass.prototype);
    SubClass.prototype.constructor = SubClass; // constructor 복구

    // subMethods가 제공되면 추가
    if (subMethods) {
        for (var method in subMethods) {
            SubClass.prototype[method] = subMethods[method];
        }
    }

    // 프로토타입 동결
    Object.freeze(SubClass.prototype);
    return SubClass;
};

➌ ES6의 클래스 상속

ES6에서 등장한 클래스 문법을 사용하여 상속을 구현하면 다음과 같다.
extends 키워드를 이용하면 쉽게 SquareRectangle을 상속받도록 할 수 있다.

var Rectangle = class {
    constructor (width, height) {
        this.width = width;
        this.height = height;
    }

    getArea () {
        return this.width * this. height;
    }
};

var Square = class extends Rectangle {
    constructor (width) {
        super(width, width);
    }

    getArea() {
        console.log('size is : ', super.getArea());
    }
};
profile
읽기 쉬운 코드와 글을 작성해요 📝

0개의 댓글