[코어 자바스크립트] 07. 클래스(1)

1. 클래스와 인스턴스 1) 클래스 ex) 음식 - 고기 - 채소 - 과일 - 배 - 사과 - 바나나 → 음식이라는 범주 안에 고기, 채소, 과일 / 과일이라는 범주 안에 배, 사과, 바나나 → 음식과 과일은 어떤 사물

data-science-study.tistory.com

 

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

    - 클래스가 값을 가지면 그 하위 클래스에서 delete 등의 변화가 발생했을 때, 원하지 않는 값이 참조되는 등 상위 클래스가 하위 클래스에 관여하는 안전성의 문제가 계속 발생

    - 따라서, 클래스가 구체적인 값을 지니지 않도록 해야 함

    - 가장 쉬운 방법은 일단 만들고 나서, 프로퍼티를 일일히 지운 뒤, 새로운 프로퍼티를 추가할 수 없도록 freeze

delete Square.prototype.width;
delete Square.prototype.height;
Object.freeze(Square.prototype);

    - 프로퍼티가 많은 경우, 반복 작업을 없애고 범용적인 함수로 만들어 쓰기

var extendClass1 = function (SuperClass, SubClass, subMethods) {

  // SubClass의 prototype 내의 프로퍼티 삭제
  SubClass.prototype = new SuperClass();
  for (var prop in SubClass.prototype) {
    if (SubClass.prototype.hasOwnProperty(prop)) {
      delete Subclass.prototype[prop];
    }
  }
  
  // 메서드가 있다면, SubClass에 넘겨주기
  if (subMethods) {
    for (var method in subMethods) {
      SubClass.prototype[method] = submethod[method];
    }
  }
  
  // 그 후 더이상 SubClass의 prototype에 새로운 프로퍼티를 추가할 수 없도록 freeze
  Object.freeze(SubClass.prototype);
  return SubClass;
};

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

      → extnedClass1에 상위 클래스인 Rectangle과 Rectangle을 상속받는 SubClass인 Square를 익명함수로 전달

      → SubClass에 있는 SuperClass에서 상속받은 프로퍼티를 삭제하여 애초에 상위 클래스의 프로퍼티를 사용하도록 설정(width를 두 번 전달할 때, Square의 width, width가 아닌 Rectangle의 width, height로 전달되어, Square의 width가 삭제되어도 Rectangle의 width, height로써 저장되어 있는 값이 변하지 않아 결과도 변하지 않게 됨)

      → 메서드는 인스턴스가 사용가능하도록 SubClass에 넘겨주기

 

    - 두 번째 방법으로, 아무 프로퍼티를 생성하지 않는 빈 생성자 함수(Bridge)를 만들어서 그 prototype이 SuperClass의 prototype을 바라보게 하고, SubClass의 인스턴스에는 Bridge의 인스턴스 할당

var Rectangle = function (width, height) {
  this.width = width;
  this.height = height;
};
Rectangle.prototype.getArea = function () {
  return this.width * this.height;
};
var Square = function (width) {
  Rectangle.call(this, width, width);
};
var Bridge = function () {};
Bridge.prototype = Rectangle.prototype;
Square.prototype = new.Bridge();
Object.freeze(Square.prototype);

      → Rectangle 자리를 Bridge가 대체하며 인스턴스를 제외한 프로토타입 경로에 구체적인 데이터 x

    - 범용성을 고려하여 함수로 작성하면 다음과 같이 작성할 수 있음

var extendClass2 = (function () {
  var Bridge = function () {};
  return function (SuperClass, SubClass, subMethods) {
    Bridge.prototype = SuperClass.prototype;
    SubClass.prototype = new Bridge();
    if (subMethods) {
      for (var method in sybMethods) {
        SubClass.prototype[method] = SubMethods[method];
      }
    }
    Object.freeze(SubClass.prototype);
    return SubClass;
  };
})();

 

    - 세 번째 방법, Object.create 사용

...

Square.prototype = Object.create(Rectangle.prototype);
Object.freeze(Square.prototype);

...

      → 이 방법으로 생선한 SubClass의 prototype의 __proto__는 SuperClass의 Prototype을 바라보지만, SuperClass의 인스턴스가 되지는 않음

 

  - 위 세가지 방법 모두 SubClass.prototype의 __proto__가 SuperClass.prototype을 참조하고, SubClass.prototype에는 불필요한 인스턴스 프로퍼티가 남아있지 않아야 함

 

 

 3) constructor 복구하기

    - SubClass의 인스턴스의 constructor는 여전히 SuperClass를 가리킴

    - 따라서, SubClass.prototype.constructor가 원래의 SubClass를 바라보도록 해주어야 함

var extendClass1 = function (SuperClass, SubClass, subMethods) {
  SubClass.prototype = new Superclass();
  for (var prop in SubClass.prototype) {
    if (SubClass.prototype.hasOwnProperty(prop)) {
      delete SubClass.prototype[prop];
    }
  }
  
  SubClass.prototype.constructor = SubClass;
  
  if (SubMethods) {
    for (var method in subMethods) {
      SubClass.prototype[method] = subMethods[method];
    }
  }
  Object.freeze(SubClass.prototype);
  return SubClass;
}
var extendClass2 = (function () {
  var Bridge = function () {};
  return function (SuperClass, SubClass, subMethods) {
    Bridge.prototype = SuperClass.prototype;
    SubClass.prototype = new Bridge();
    
    SubClass.prototype.constructor = SubClass;
    
    if (subMethods) {
      for (var method in sybMethods) {
        SubClass.prototype[method] = SubMethods[method];
      }
    }
    Object.freeze(SubClass.prototype);
    return SubClass;
  };
})();
var extendClass3 = function (SuperClass, SubClass, subMethods) {
  SubClass.prototype = Object.create(Superclass.prototype);
  
  SubClass.prototype.constructor = SubClass;
  
  if (SubMethods) {
    for (var method in subMethods) {
      SubClass.prototype[method] = subMethods[method];
    }
  }
  Object.freeze(SubClass.prototype);
  return SubClass;
}

    - 세가지의 각 방법에서 SubClass의 prototype을 SuperClass의 prototype으로 지정한 다음에 SubClass.prototype.constructor를 SubClass로 다시 선언해주는 코드를 통해 SubClass의 constructor가 다시 자시능ㄹ 바라보도록 함

    - 상위 클래스는 이제 완전히 상속만을 위해 추상화 됨

 

 

 4) 상위 클래스에의 접근 수단

    - 하위 클래스에서 상위 클래스의 메서드 실행 결과를 바탕으로 추가 작업 수행하려고 할 때, 하위 클래스에서 상위 클래스의 prototype의 메서드에 접근할 수 있는 수단

var extendClass = function (SuperClass, SubClass, subMethods) {
  SubClass.prototype = Object.create(SuperClass.prototype);
  SubClass.prototype.constructor = SubClass;
  
  // 새롭게 super 메서드의 동작 추가
  SubClass.prototype.super = function (propName) {
    var self = this;
    if (!propName) return function () {         // 인자가 없다면 SuperClass 생성자 함수에 클로저를 통해 접근
      SuperClass.apply(self, arguments);
    }
    var prop = SuperClass.prototype[propName];
    if (typeof prop !== 'function') return prop;  // 사용하려는 SuperClass의 프로퍼티의 타입이 함수가 아니면 사용하려는 값을 그대로 반환
    return function () {                               // 함수라면, 클로저를 통해 메서드에 접근
      return prop.apply(self, arguments);
    }
  };
  // 추가된 내용 끝
  
  if (subMethods) {
    for (var method in subMethods) {
      SubClass.prototype[method] = subMethods[method];
    }
  }
  Object.freeze(SubClass.prototype);
  return SubClass;
};

    - super 메서드: SuperClass의 생성자 함수에 접근하고자 할 때는 this.super(), SuperClass의 prototype에 접근하고자 할 때는 this.super(propName)

    - 기존 this에 바인딩하는 문법을 썼다면 이제 this.super()(width, width)라고 함으로써 가독성 증가

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

var Square = extendClass(
  Rectangle,
  function (width) {
    this.super()(width, width);
  }, {
      getArea: function () {
        console.log('size is :', this.super('getArea')());
      }
  }
);

var sq = new Square(10);
sq.getArea();
console.log(sq.super('getArea')());

 

 

4. ES6의 클래스 및 클래스 상속

    - ES6의 클래스

// ES5
var ES5 = function (name) {
  this.name = name;
};

ES5.staticMethod = function () {
  return this.name + ' staticMethod';
};

ES5.prototype.method = function () {
  return this.name + ' method';
};

var es5Instance = new ES5('es5');
console.log(ES5.staticMethod());        // ES5 staticMethod
console.log(es5Instance.method());    // es5 method
// ES6
var ES6 = class {

  // 생성자 함수
  constructor (name) {
    this.name = name;
  }
  
  // static 메서드로, 생성자 함수(클래스) 자신만 호출 가능)
  static staticMethod () {
    return this.name + ' staticMethod';
  }
  
  // prototype 객체 내부에 할당되는 메서드, 인스턴스가 프로토타입 체이닝을 통해 호출 가능
  method () {
    return this.name + ' method';
  }
};

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

 

    - ES6의 클래스 상속

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

var Square = class extends Rectangle {  // Square가 Rectangle의 상속을 받게 하기 위하여 extends 추가
  constructor (width) {
    super(width, height);              // constructor 내부의 super는 SuperClass의 constructor 실행
  }
  
  getArea () {
    console.log('size is :', super.getArea());   // constructor 외부의 super는 객체처럼 사용 가능, 이때 객체는 SuperClass.prototype을 바라보고, this는 super가 아닌 원래의 this
  }
};

+ Recent posts