[JS] 클래스

2022. 8. 11. 23:36학습/JavaScript

반응형

JS 클래스

클래스

자바스크립트에는 '상속'이라는 개념이 존재하지 않습니다.

특히 프로토타입 기반과 클래스 기반의 언어는 서로 다른 구조를 갖고 있기 때문에 이를 클래스처럼 동작하려는 시도가 있었고 결국 ES6에는 클래스 문법이 추가가 되었습니다.

클래스의 개념

사물의 공통 속성을 모아 놓은 집단을 클래스라는 추상적인 개념으로 나타냅니다.

그리고 그 클래스 간 포함 관계를 가지며 분류되며 상위 클래스, 하위 클래스로 표현이 됩니다.

하지만 이들은 추상적인 개념이고 이를 바탕으로 구체화, 실존하는 개체를 인스턴스라고 합니다.

"사람"을 떠올리면 두 발, 두 손, 얼굴 등등 여러 요소를 갖고 있는 모습을 연상할 수 있습니다.

하지만 그저 사람이라고 생각하면 정확하지 않고 모호합니다. 여기서 실존 인물인 "톰 크루즈"를 생각하면 추상적인 개념이 구체화되고 실존하게 되죠.

"사람" 이라는 것은 클래스이고 "톰 크루즈"를 인스턴스로 이해하면 됩니다.

또한 "톰 크루즈"는 '사람'의 클래스를 갖추기도 하고 '배우'라는 클래스 속성을 갖추기도 할 것입니다. 이처럼 하나의 인스턴스가 다양한 클래스의 속성을 갖출 수도 있습니다.

프로그래밍 언어에서의 클래스는 사용하기에 따라 추상적인 대상일 수 있고 구체적인 개체가 될 수도 있습니다.

자바스크립트의 클래스

Array 생성자 함수를 new 연산자와 함꼐 호출하면 인스턴스가 생성됩니다.

Array를 일종의 클래스라고 한다면 Array의 프로토타입 객체가 인스턴스에 상속된다고 볼 수 있습니다.

그리고 이미 Array의 프로토타입 객체를 제외하고는 상속되지 않는 것도 알고 있습니다.

클래스 구성 요소를 차용하여 인스턴스에 상속되는지(참조하는지) 여부에 따라 스태틱 멤버, 인스턴스 멤버로 나누었습니다.

자바스크립트에서는 인스턴스에서도 직접 메서드를 정의할 수 있기 때문에 인스턴스 메서드로 지칭하는 것은 프로토타입의 메서드인지 인스턴스의 메서드인지 헷갈리게 만듭니다.

그렇기 때문에 프로토타입 메서드라고 부르는것이 혼동을 피하기 위한 맞는 명칭으로 생각합니다.


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)); // undefined
console.log(Rectangle.isRectangle(rect1)); // true

  • rect1.getArea()
    • 이 경우 rect1.__proto__.getArea() 에 접근을 합니다.
    • 이는 인스턴스에서 호출할 수 있는 메서드, 프로토타입 메서드라고 합니다.
  • rect1.isRectangle(rect1)
    • rect1 에는 isRectangle 이라는 값이 존재하지 않습니다.
    • rect1.__proto__ 에도 없으며 rect1.__proto__.__proto__ 에도 존재하지 않습니다.
    • 이처럼 인스턴스에서 직접 접근할 수 없는 메서드를 스태틱 메서드라고 합니다.
      • 이는 생성자함수를 this로 해야만 접근이 가능합니다.
      • Rectangle.isRectangle(rect1)

클래스 상속

클래스 상속은 객체지향에서 중요한 부분이므로 클래스 상속을 JS로 구현하는 것은 큰 관심이었습니다.

ES6가 되어서 클래스 문법이 생겼지만 그 과정을 보여드리려고 합니다.

이전에 다중 프로토타입을 말한 적이 있으며 이를 통해서 클래스의 상속을 흉내낼 수 있다고 말했었습니다.

ES6에서 클래스가 도입이 되었고 그것은 prototype을 기반한 것입니다.

이는 프로토타입 체이닝을 잘 연결함으로서 구현했다는 것을 말합니다.


var Grade = function() {
    var args = Array.prototype.slice.call(arguments);
    for (var i = 0; i< args.length; i++) {
        this[i] = args[i]
    }
    this.length = args.length;
};
Grade.prototype = [];
var g = new Grade(100, 80);

여기서 문제 두 가지 문제가 존재합니다.

  1. length가 삭제 가능함
  2. Grade.prototype가 빈 배열을 참조 함


delete g.length;
g.push(70);
console.log(g); //Grade [70, 1: 80]

g.length를 삭제하고 값을 추가할 시, lengthgprototype 인 빈 배열([])에 접근을 합니다.

그리고 빈 배열의 길이를 보고 인덱스 0에 값을 추가하게 됩니다.

이는 클래스에 있는 값이 인스턴스 동작에 영향을 주는 부분입니다. 이러한 영향은 클래스의 추상성을 해치게 됩니다.

이를 위해서 클래스가 구체적인 데이터를 지니지 않게 해야합니다.

예시


var Rectangle = function (width, height) {
    this.width = width;
    this.heigth = height;
};
Rectangle.prototype.getArea = function() {
    return this.width * this.heigth;
};
var rect = new Rectangle(3, 5);
console.log(rect.getArea()); //12

var Square = function (width) { 
    this.width = width;
};
Square.prototype.getArea = function () { // 
    return this.width * this.width;
};
var sq = new Square(5);
console.log(sq.getArea()); //25

여기서 SquareRectangle 이 비슷한 부분을 갖고 있습니다.

Rectangle 의 하위클래스로 Square 를 삼을 수 있을겁니다.

그렇다면 getArea를 상위 클래스인 Rectangle 에서만 선언하고 하위 클래스에서는 width 만 넣어주면 됩니다.


var Square = function (width) {
    Rectangle.call(this, width, width);
};
Square.prototype = new Rectangle();

getArea 메서드를 상속하기 위해서 Square.prototypeRectangle 의 인스턴스를 부여 했습니다.

하지만 여전히 클래스에 있는 값이 인스턴스에 영향을 줄 수 있습니다.

먼저 Squareprototype에 접근하면 widthheightundefined가 할당되있습니다.

아까 grade 와 같이 width를 삭제한다면 __proto__를 거처서 잘못된 값을 나타낼 것입니다.

또한 constructor가 Rectangle을 가리키고 있다는 점이 문제가 있습니다.

이는 Square의 인스턴스의 프로토타입 체이닝이 결국 Rectangle을 가리키게 되기 때문입니다.

결국 이러면 기본 메서드 상속을 위해서 하위 클래스의 생성자 함수 프로토타입에 상위 클래스의 인스턴스를 부여하는 것은 문제의 여지를 갖게됩니다.

클래스가 구체적인 데이터를 지니지 않게 하는 방법

클래스의 추상성을 해치는 일 없도록 프로퍼티의 구체적인 데이터를 삭제해야 됩니다.

  1. 인스턴스를 생성 후 프로퍼티를 제거
  2. 
    delete Square.prototype.width;
    delete Square.prototype.height;
    Object.freeze(Square.prototype);
    
    
  3. 빈 함수를 활용
    1. 하위 클래스 프로토타입에 상위 클래스의 인스턴스를 할당합니다.
    2. 아무런 프로퍼티를 생성하지 않는 빈 생성자 함수( Bridge )를 만듭니다.
    3. Bridge 의 프로토타입이 상위 클래스의 프로토타입을 바라보게 합니다.
    4. 하위 클래스의 프로토 타입에는 Bridge 의 인스턴스를 할당합니다.
    이렇게 하면 하위 클래스의 프로토 타입은 값이 없기 때문에 이상한 값을 가리키지 않습니다.
  4. 
    var Rectangle = function (width, height) {
        this.width = width;
        this.heigth = height;
    };
    Rectangle.prototype.getArea = function() {
        return this.width * this.heigth;
    };
    var Square = function (width) {
        Rectangle.call(this, width, width);
    };
    var Bridge = function () {};
    Bridge.prototype = Rectangle.prototype;
    Square.prototype = new Bridge();
    Object.freeze(Square.prototype);
    
    
  5. ES5에 도입된 Object.create 활용
  6. 
    Square.prototype = Object.create(Rectangle.prototype);
    Object.freeze(Square.prototype);
    
    

결국 이 세가지 아이디어는

SubClass.prototype__proto__SuperClass.prototype을 참조하고 SubClass.prototype에는 불필요한 인스턴스 프로퍼티가 남지 않게 하는 방법입니다.

constructor 복구하기

위 방식을 시도하더라도 constructor 가 여전히 상위 클래스를 가리키고 있습니다.

이는 다시 constructor 가 하위 클래스를 바라보도록 해주면 되면 해결이 됩니다.


SubClass.prototype.constructor = SubClass;

ES6의 클래스 및 클래스 상속

예시


var ES6 = class {
    constructor (name) {
        this.name = name;
    }
    static staticMethod () {
        return this.name;
    }
    method () {
        return this.name + 'method';
    } // prototype 객체 내부에 할당되는 메서드
};

var es6Instance = new ES6('es6');
console.log(ES6.staticMethod()); //ES6
console.log(es6Instance.method()); //es6method

상속


var Rectangle = calss {
    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());
    }
};

따로 클래스 상속에 대해서 기술되어있지만 더 자세히 공부를 원하시면 밑 링크를 추천드립니다.

 

 

클래스

 

ko.javascript.info

이 글은 "코어자바스크립트" 내용에 기반하여 작성한 글입니다.

잘못된 부분이나 이해가 잘못된 부분에 대해서는 댓글로 써 주시면 감사하겠습니다!

반응형

'학습 > JavaScript' 카테고리의 다른 글

[JS] 프로토타입  (0) 2022.08.08
[JS] 클로저  (0) 2022.08.07
[JS] 콜백함수  (0) 2022.08.07
[JS] this  (0) 2022.08.06
[JS] 실행 컨텍스트  (0) 2022.07.31