상속

</>·2022년 4월 9일
3
post-thumbnail

목표

  • 25장의 내용을 최대한 이해하고 정리하기

25. 클래스 - 상속(extends)

※ 본문의 목차는 책의 목차와 다릅니다.

25-8. 클래스 상속과 생성자 함수 상속

  • 클래스 기반 상속은 기존 클래스를 상속받아 새로운 클래스를 확장(extends)하여 정의하는 것이다.
  • 클래스와 생성자 함수는 인스턴스를 생성할 수 있는 함수라는 점에서 유사하지만 클래스는 상속을 통해 기존 클래스를 확장할 수 있는 문법이 제공되지만 생성자 함수는 제공하지 않는다.
class Animal {
  constructor(age, weight) {
    this.age = age;
    this.weight = weight;
  }
  
  move() { return "move"; }
}

// 상속
class Lion extends Animal {
  hunt() { return "hunt"; }
}

const lion = new Lion(4, 21);

console.log(lion instanceof Lion);
console.log(lion instanceof Animal);

console.log(lion.move());
console.log(lion.hunt());

// 결과
true
true
"move"
"hunt"

25-9. extends 키워드

class Animal {}

// 상속
class Lion extends Animal {}
  • 상속을 통해 클래스를 확장하려면 extends 키워드를 통해 정의한다.
  • 상속을 통해 확장된 클래스를 서브클래스(위 코드에서는 Lion) 또는 자식클래스이라 부른다.
  • 서브클래스에게 상속된 클래스를 수퍼클래스(위 코드에서는 Animal) 또는 부모클래스라고 부른다.

function Animal(age) {
  this.age = age;
}

class Lion extends Animal {}
const lion = new Lion(7);
console.log(lion.age);

// 결과
7
  • 또한, extends 키워드를 사용해서 클래스 뿐만 아니라 생성자 함수도 상속받을 수 있다.
  • 단, extends 키워드 앞에는 반드시 클래스가 와야 한다.

25-10. 동적 상속

// 생성자 함수
function Animal() {}

// 클래스
class Plant {}

let flag = true;
class Lion extends (flag ? Animal : Plant) {}

flag = false;
class Tree extends (flag ? Animal : Plant) {}

const lion = new Lion();
const tree = new Tree();

console.log(lion instanceof Animal);
console.log(lion instanceof Plant);
console.log(tree instanceof Animal);
console.log(tree instanceof Plant);

// 결과
true
false 
false
true
  • extends 키워드 다음에는 클래스 뿐만 아니라 삼항 연산자와 같은 표현식을 사용해 동적으로 상속받을 대상을 결정할 수 있다.

25-11. 서브클래스의 constructor

  • 클래스는 constructor를 생략하면 constructor가 암묵적으로 정의된다.
class Animal {}
class Animal {
  // 암묵적으로 정의
  constructor() {}
}
  • 마찬가지로 서브클래스도 constructor를 생략하면 암묵적으로 정의된다.
class Animal {}

class Monkey extends Animal {}
class Animal {
  // 암묵적으로 정의
  constructor() {}
}

class Monkey extends Animal {
  // 암묵적으로 정의
  constructor(...agrs) {
    super(...args);
  }
}
  • 서브클래스는 수퍼클래스와 다르게 agrs라는 매개변수와 함께 정의된다.
  • agrs 는 new 연산자와 함께 클래스를 호출할 때 전달한 인수의 리스트이다.
  • super()는 수퍼클래스의 constructor()를 호출하여 인스턴스를 생성한다.

Rest 파라미터

  • 매개변수에 ...을 붙이면 Rest 파라미터가 되는데 함수에 전달된 인수들의 목록을 배열로 전달받는다.

25-12. super 키워드

  • super 키워드는 다음과 같은 역할을 한다.
    • 함수처럼 호출해 수퍼클래스의 생성자(constructor)를 호출 한다.
    • super를 참조하면 수퍼클래스의 메서드를 호출 할 수 있다.

25-12-1. super 호출

class Animal {
  constructor(a, b) {
    this.a = a;
    this.b = b;
  }
}

class Monkey extends Animal {
  constructor(a, b, c) {
    super(a, b);
    this.c = c;
  }
}

const monkey = new Monkey(1, 2, 3);
console.log(monkey);

// 결과
Monkey {a: 1, b: 2, c: 3}
  • new 연산자를 통해 클래스를 호출하면서 전달된 인수 1, 2, 3은 Monkey 클래스의 constructor에 전달된다.
  • 그 후, super를 호출해 Animal 클래스의 constructor에 일부가 전달된다.
  • 이처럼 인스턴스 초기화를 위해 전달된 인수는 수퍼클래스와 서브클래스에 배분되고 상속 관계의 두 클래스는 서로 협력하여 인스턴스를 생성하게 된다.

  • 단, super를 호출할 때 주의할 사항이 있다.
  1. 서브클래스에서 constructor를 생략하지 않는 경우 즉, constructor를 정의할 경우에는 반드시 super를 호출해야 한다.
class Animal {}

class Rabbit extends Animal {
  constructor() {}    // Error
}

const rabbit = new Rabbit();

// 결과
"Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor"
  1. 서브클래스의 constructor에서 super를 호출하기 전까지 this를 참조할 수 없다.
class Animal {}

class Rabbit extends Animal {
  constructor(a) {
    this.a = a;    // Error
    super();
  }
}

const rabbit = new Rabbit(1);

// 결과
"ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor"
  1. super는 반드시 서브클래스의 constructor에서만 호출해야 한다.
class Animal {
  constructor() {
    super();    // Error
  }
}

class Rabbit extends Animal {
  constructor(a) {
    super();
  }
}

const rabbit = new Rabbit(1);

// 결과
"Uncaught SyntaxError: 'super' keyword unexpected here"

25-12-2. super 참조

  • 메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.
  1. 서브클래스의 프로토타입 메서드 내에서 super를 사용해 수퍼클래스의 프로토타입 메서드를 참조할 수 있다.
class Parent {
  constructor(job1, job2) {
    this.mom = job1;
    this.dad = job2;
  }
  
  getParentJob() {
    return `mom is ${this.mom} and dad is ${this.dad}`
  }
}

class Child extends Parent {
  constructor(name, age1, age2) {
    super(age1, age2);
    this.name = name;
  }
  
  getParentJob() {
    return `${this.name}'s ${super.getParentJob()}`
  }
}

const james = new Child("james", "reporter", "programmer");
console.log(james.getParentJob());

// 결과
"james's mom is reporter and dad is programmer"
  1. 서브클래스의 정적 메서드 내에서 super를 사용해 수퍼클래스의 정적 메서드를 참조할 수 있다.
class Animal {
  static type() {
    return "is Animal";
  }
}

class Tiger extends Animal {
  static type() {
    return `tiger ${super.type()}`
  }
}

console.log(Tiger.type());

// 결과
"tiger is Animal"

25-13. 상속 클래스의 인스턴스 생성 과정

  • 직사각형을 추상화한 Rectangle 클래스와 상속을 통해 색깔을 추가해 확장한 ColorRectangle 클래스를 정의해보면 다음과 같다.
// 수퍼클래스
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  
  getArea() {
    return this.width * this.height;
  }
  
  toString() {
    return `width = ${this.width}, height = ${this.height}`;
  }
}

// 서브클래스
class ColorRectangle extends Rectangle {
  constructor(width, height, color) {
    super(width, height);
    this.color = color;
  }
  
  toString() {
    return super.toString() + `, color = ${this.color}`;
  }
}

const colorRectangle = new ColorRectangle(2, 4, "red");
console.log(colorRectangle);
console.log(colorRectangle.getArea());
console.log(colorRectangle.toString());

// 결과
ColorRectangle {width: 2, height: 4, color: 'red'}
8
"width = 2, height = 4, color = red"
  • 서브클래스 ColorRectangle이 new 연산자와 함께 호출되면 다음 과정을 통해 인스턴스를 생성한다.
    1. 서브클래스의 super 호출
    2. 수퍼클래스의 인스턴스 생성과 this 바인딩
    3. 수퍼클래스의 인스턴스 초기화
    4. 서브클래스 constructor로의 복귀와 this 바인딩
    5. 서브클래스의 인스턴스 초기화
    6. 인스턴스 반환

25-13-1. 서브클래스의 super 호출

  • 상속받지 않는 클래스 는 new 연산자와 함께 호출되었을 때 암묵적으로 빈 객체를 생성해 이를 this에 바인딩한다.
  • 하지만, 서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임한다. 이 때문에 서브클래스의 constructor에서 반드시 super를 호출해야 한다. 호출하지 않으면 에러가 발생한다.
  • super 키워드가 함수처럼 호출되면 수퍼클래스의 constructor가 호출된다.

25-13-2. 수퍼클래스의 인스턴스 생성과 this 바인딩

  • 수퍼클래스의 constructor가 실행되기 전에 빈 객체를 생성하고 this에 바인딩된다.
  • 수퍼클래스의 constructor 내부의 this는 생성된 인스턴스를 가리킨다. 즉, 인스턴스는 수퍼클래스가 생성했지만 new 연산자와 함께 호출된 서브클래스를 가리키게 된다.
  • 프로토타입 관점에서 살펴보면 생성된 인스턴스의 프로토타입은 수퍼클래스의 prototype 프로퍼티가 가리키는 객체 Rectangle이 아니라 서브클래스의 prototype 프로퍼티가 가리키는 객체인 ColorRectangle이다.
class Rectangle {
  console.log(this);		// ColorRectangle {}
  
  // 생략
}

class ColorRectangle extends Rectangle {}

const colorRectangle = new ColorRectangle(2, 4, "red");

25-13-3. 수퍼클래스의 인스턴스 초기화

  • 인스턴스를 생성하고 this에 바인딩 되었으면 이제 초기화 작업을 진행한다.
  • this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스의 프로퍼티를 초기화한다.
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
    
    console.log(this); 		// ColorRectangel {width: 2, height: 4}
  }
}

class ColorRectangle extends Rectangle {}

const colorRectangle = new ColorRectangle(2, 4, "red");

25-13-4. 서브클래스 constructor로의 복귀와 this 바인딩

  • 수퍼클래스의 초기화 작업이 완료되면 super 호출이 종료되고 제어 흐름이 서브 클래스 constructor로 다시 돌아온다.
  • 이 때, super가 반환한 인스턴스가 this에 바인딩된다.
  • 서브클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용한다.
class ColorRectangle extends Rectangle {
  constructor(width, hieght, color) {
    console.log(this);		// Error
    super(width, height);
    console.log(this);		// ColorRectangel {width: 2, height: 4}
  }
}

const colorRectangle = new ColorRectangle(2, 4, "red");
  • 이처럼 super가 호출되지 않으면 인스턴스가 생성되지 않으며, this에 바인딩도 할 수 없다.
  • 서브클래스의 constructor에서 suepr를 호출하기 전에는 this를 참조할 수 없는 이유가 바로 이 때문이다.

25-13-5. 서브클래스 인스턴스 초기화

  • this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 프로퍼티를 초기화한다.
class ColorRectangle extends Rectangle {
  constructor(width, hieght, color) {
    super(width, height);
    this.color = color;
  }
}

25-13-6. 인스턴스 반환

  • 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
class ColorRectangle extends Rectangle {
  constructor(width, hieght, color) {
    super(width, height);
    console.log(this);	// ColorRectangel {width: 2, height: 4}
    this.color = color;
    console.log(this);	// ColorRectangel {width: 2, height: 4, color: "red"}
    
    // return this;
  }
}

const colorRectangle = new ColorRectangle(2, 4, "red");

25-14. 표준 빌트인 생성자 함수 확장

  • extends 키워드로 String, Number, Array 같은 표준 빌트인 객체도 확장할 수 있다.
class MyArray extends Array {
  uniq() {
    return this.filter((v, i, self) => self.indexOf(v) === i);
  }
  
  average() {
    return this.reduce((pre, cur) => pre + cur, 0) / this.length;
  }
}

const myArray = new MyArray(1, 1, 2, 3);
console.log(myArray.uniq());
console.log(myArray.average());

// 결과
MyArray(3) [1, 2, 3]
1.75
  • Array 생성자 함수를 상속받아 확장한 MyArray 클래스가 생성한 인스턴스는 Array.prototype과 MyArray.prototype의 모든 메서드를 사용할 수 있다.
  • 주의할 점은 Array.prototype의 메서드인 map이나 filter를 사용해도 Array 클래스가 아닌 MyArray 클래스 인스턴스가 반환된다.
console.log(myArray.filter(v => v % 2).uniq().average());
  • filter를 사용해도 MyArray 클래스를 반환하기 때문에 uniq와 average 메서드를 연이어 호출할 수 있다.
profile
개발자가 되고 싶은 개발자

0개의 댓글