7 클래스

클래스

음식 > 과일 > 포도 > 샤인머스캣

음식, 과일, 포도는 추상적인 개념 : 클래스

샤인머스캣은 클래스의 속성을 지니면서 실존한다. (먹을 수 있다) : 인스턴스

자바스크립트와 클래스

자바스크립트는 프로토타입 언어라 클래스 개념이 존재하지는 않지만, 클래스와 비슷한 관점에서 해석해 볼 수는 있다.

인스턴스에 상속되는 메서드를 인스턴스 메서드, 인스턴스에 상속되지 않는 메서드를 스태틱 메서드라고 한다. 자바스크립트 진영에서는 인스턴스 메서드를 프로토타입 메서드라고 더 많이 부른다.

var Person = function (name) {
  this.name = name;
}
// 프로토타입 메서드
Person.prototype.getName = function () {
  return this.name;
}
// 스태틱 메서드
Person.sayHello = function () {
  console.log("안녕!");
}

클래스 상속

가볍게 읽자! 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);
g.push(90);
console.log(g); // Grade {0: 100, 1: 80, 2: 90, length: 3}

6장의 프로토타입 체인에서 봤던 코드. Array를 상속한 Grade라고 해석할 수 있지만 세부적으로 봤을 때 문제가 있다.

  1. length 프로퍼티가 삭제 가능

    length 프로퍼티는 Grade의 프로퍼티로 존재함. Array의 length는 configurable이 false라 삭제할 수 없지만 Grade의 length 프로퍼티는 삭제할 수 있다.

  2. Grade.prototype이 빈 배열을 참조

    length 프로퍼티가 삭제된 상태에서 다시 length를 참조하는 작업을 하면, 자바스크립트 엔진은 __proto__ 를 타고 Array.prototype.length에 접근한다. 빈 배열을 참조하고 있었기 때문에 length는 0일 것이고, 실제 Grade의 내용과는 관계없이 length가 설정된다.

  3. g의 constructor가 Array를 가리키고 있다.

    g.constructor를 호출하면 프로토타입 체이닝을 따라서 g.__proto__.__proto__ 의 constructor, Array를 갖게 된다. g는 Grade의 인스턴스이기 때문에 constructor 로도 Grade를 갖는 것이 더 적절할 것이다.

클래스는 틀이다. 따라서 클래스에 있는 값이 인스턴스의 동작에 영향을 줘서는 안 된다. 따라서 상속 개념을 잘 흉내 내려면 클래스가 구체적인 데이터를 지니지 않게 해야 한다. 클래스가 구체적인 데이터를 지니지 않게 하는 방법은 2가지가 있다.

  1. 일일이 지워주기 : subclass.prototype에 접근해서 모든 프로퍼티를 delete로 제거하면 된다.

  2. Bridge 생성자 함수 활용하기 : subclass.prototype에 직접 superclass의 인스턴스를 할당하는 대신 빈 생성자 함수 Bridge 인스턴스를 연결하고, Bridge.prototype에 superclass.prototype을 연결하는 방식이다.

  3. Object.create를 이용하는 방법

    // 생략
    Square.prototype = Object.create(Rectangle.prototype);

    이렇게 하면 subclass의 prototype의 __proto__ 가 superclass의 prototype을 바라보지만, superclass의 인스턴스가 되지는 않기 때문에 앞선 두 방법보다는 간단하고 안전하다.

추가로 constructor를 복구하는 방법 역시 필요하다. (원래의 subclass를 바라보도록)

욕심을 내면 super 또한 apply를 이용해서 구현할 수 있다.

클래스 문법

기본 문법

var Person = class {
  constructor (name) {
    this.name = name;
  }
  
  static sayHello () {
    console.log("안녕!");
  }
  
  introduce() {
    console.log(this.name + "입니다!")
  }
}

상속

var player = class extends Person {
  constructor(name, backNumber) {
    super(name);
    this.backNumber = backNumber
  }
}