1. 클래스와 인스턴스

 1) 클래스

    ex)   음식 - 고기

                        - 채소

                        - 과일 - 배

                                    - 사과

                                    - 바나나

          → 음식이라는 범주 안에 고기, 채소, 과일 / 과일이라는 범주 안에 배, 사과, 바나나

          → 음식과 과일은 어떤 사물의 공통 속성을 모아 정의한 것이고, 배, 사과, 바나나는 실존하여 만질 수 있는 것

          → 음식과 과일은 '클래스'이고 클래스 중 음식이 상위 개념, 과일이 하위 개념이므로 음식이 superClass, 과일이 subClass

          → 클래스 내부에서 실존하는 객체는 '인스턴스', 인스턴스는 인스턴스를 포함하고 있는 클래스의 속성을 모두 가짐

 

2. 자바스크립트의 클래스

  - 자바 스크립트는 프로토타입 기반 언어로 클래스의 개념이 존재하지 않지만, 프로토타입 체이닝 등으로 상속을 구현 가능

     (생성자 함수 Array를 new와 함께 사용하여 새로운 인스턴스를 만들면, 인스턴스는 Array의 prototype 내부 요소들을 사용 가능)

  - 이때, 인스턴스에 상속되는 프로퍼티가 있고(prototype 내부의 프로퍼티), 인스턴스에 상속되지 않는 프로퍼티가 있음

    → 인스턴스에 상속이 되지 않는 프로퍼티: 스태틱 멤버(static member(static methods + static properties))

    → 인스턴스에 상속되는 프로퍼티: 프로토타입 메서드

  - 아래 사진에서 prototype 내부의 메서드(push(), pop() 등)만 [1, 2]라는 인스턴스가 사용가능

  - 그 외, from(), isArray(), arguments, length 등은 [1, 2]에서 사용이 불가능

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

// 생성자 함수 Rectangle의 prototype 내에 getArea 메서드 생성
Rectangle.prototype.getArea = function () {
  return this.width * this.height;
};

// 생성자 함수 Rectangle의 prototype 내부에가 아닌 static method로 isRectangle 함수 생성
Rectangle.isRectangle = function (instance) {
  return instance instanceof Rectangle && instance.width > 0 && instance.height > 0;
};

// 생성자 함수 Rectangle로 생성한 instance rect1
var rect1 = new Rectangle(3, 4)

// 인스턴스 rect1에서 Rectangle의 prototype에 있던 getArea함수가 정상 작동함
console.log(rect1.getArea()); // 12

// 인스턴스 rect1에서 Rectangle의 static methos인 isRectangle은 작동x
console.log(rect1.isRectangle(rect1)); // Error

// 생성자 함수 Rectangle에서는 당연히 Rectangle의 static method인 isRectangle이 작동함
console.log(Rectangle.isRectangle(rect1)); // True

  - 클래스는 static method를 통해 호출할 때는, 자신만 사용할 수 있는 것을 사용하는 것으로, 하나의 개체로 취급

  - 앞서 배웠던 것처럼 하위 개념에 자신의 특성을 부여하는 경우에 클래스는 틀의 역할을 하는 추상적인 개념

 

 

3. 클래스 상속

 1) 기본 구현

    - 프로토타입 체인을 활용해 클래스 상속을 구현

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);

    - Grade의 prototype에 []을 참조시켜 임의로 배열로 선언

    - 따라서, Grade는 Array에 프로토타입 체이닝되어 Array의 prototype을 참조할 수 있게됨

Grade가 Array와 Object의 prototype을 참조할 수 있게 되는 과정

  - 하지만 세부적으로 완벽한 superclass와 subclass의 구현이 이루어지지 않음

      → length 프로퍼티가 삭제 가능한 점

      → Grade.prototype에 빈 배열을 참조시킨 점

// 90을 유사 배열 객체인 g에 Array의 메서드 push를 이용하여 잘 적용됨
g.push(90);
console.log(g);  // Grade{ 0: 100, 1: 80, 2: 90, length: 3 }

// 하지만, g의 생성자 함수 Grade를 통해 선언되었던 length는 삭제가 가능
delete g.length;
g.push(70);
console.log(g);  // Grade{ 0: 70, 1: 80, 2: 90, length: 1 }

      → 70을 push했을 때 0번째 인덱스에 70이 들어가고 length가 1이 된 이유

      → g.__proto__, 즉 Grade.prototype이 빈 배열 []을 가리키고, g.length가 없어서 참조한 g.__proto__.length는 Array의 length 메서드가 적용됨

      → 따라서, 빈 배열에 값(70)을 할당하고, 이 배열의 길이를 읽어들인 것

      → Grade.prototype을 빈 배열 []이 아닌 요소가 있는 배열로 정의했다면

Grade.prototype = [ 'a', 'b', 'c', 'd' ];
var g = new Grade(100, 80);

// push는 그대로 정상 작동
g.push(90);
console.log(g);  // Grade{ 0: 100, 1: 80, 2: 90, length: 3 }

// g.length가 delete 되는 것도 그대로
// 하지만, g.lenght가 없어져서 참조하게 되는 g.__proto__가 요소가 이미 4개가 있는 배열이므로
// 70을 push하면 70은 인덱스가 4인 곳에 들어가고, length도 기존 4에서 1이 추가된 5로 출력됨
delete g.length;
g.push(70);
console.log(g);  // Grade{ 0: 100, 1: 80, 2: 90, ___ 4: 70, length: 5 }

  - 이처럼 클래스의 값(메서드 등)이 인스턴스의 동작에 영향을 미치면 클래스의 추상성이 없어짐

  - 클래스는 인스턴스가 사용할 메서드(속성)만 지닌 추상적인 틀로 작용하게끔 작성해야 함

 

  - 다른 예제

// 직사각형을 만드는 생성자 함수, 가로와 세로 길이가 인자로 필요함
var Rectangle = function (width, height) {
  this.width = width;
  this.height = height;
};
Rectangle.prototype.getArea = function () {
  return this.width * this.height;
};
var rect = new Rectangle(3, 4);
console.log(rect.getArea());  // 12


// 정사각형을 만드는 생성자 함수, 가로 길이 하나만 있으면 세로 길이는 똑같으므로 필요 x
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

      → Rectangle과 Square의 공통적인 요소(인자 width, 함수 getArea()의 비슷한 패턴)를 사용하여 상속관계 형성

// Square 함수를 Rectangle 함수와 똑같은 형태로 변형
...

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

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

...


// 변형한 Square 함수를 새로 쓰지 않고 아예 Rectangle 함수를 끌어와서
// call을 통해 Rectangle 함수에 Square 함수의 인자를 전달(width 자리에 width, height 자리에 width 전달)
// prototype 또한 Rectangle 생성자 함수를 사용하여 똑같이 생성
...

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

...

      → new Square로 생성한 인스턴스인 sq의 디렉터리 구조에서 Rectangle의 prototype을 참조하는 것을 확인할 수 있음

Square과 Rectangle의 관계 도식화

      → 앞선 예제의 문제가 아직 존재

      → Square의 prototype에 값이 존재하여 sq.width나 sq.height를 delete 한다면 Square.prototype.width의 값이 대신 계산되어 엉뚱한 결과 출력

      → 또한, sq.constructor는 Rectangle을 가리키므로 정사각형이 아닌 직사각형도 적용이 가능해지는 등 구조적 안전성 감소

+ Recent posts