TIL(20.03.25) Immersive #8 Inheritance Pattern

윤장원·2020년 3월 25일
0

TIL

목록 보기
24/33
post-thumbnail

1. Prototype Chain

proto, constructor, prototype 이 각각 어떤 관계를 가지고 있는지 조사해봅니다.
Object.create 사용법에 대한 복습을 해봅니다.
ES6 class 키워드 및 super 키워드 이용 방법을 알아봅니다.

프로토타입 체이닝에 대해 알기 전에 프로토타입이 무엇인지 알아보겠습니다.

자바스크립트의 프로토타입

자바스크립트는 프로토타입 기반의 프로그래밍입니다.

프로토타입 프로그래밍은 객체의 원형인 프로토타입을 이용하여 새로운 객체를 만들어내는 프로그래밍 기법입니다. 이렇게 만들어진 객체 역시 자기 자신의 프로토타입을 갖습니다. 이 새로운 객체의 원형을 이용하면 또 다른 새로운 객체를 만들어 낼수도 있으며 이런 구조로 객체를 확장하는 방식을 프로토타입 기반 프로그래밍이라고 합니다. 자바스크립트에서의 프로토타입은 자신을 만들어낸 객체의 원형을 뜻합니다.

그러면 자바스크립트에서 프로토타입은 어떻게 사용될까요?

실질적으로 자바스크립트의 프로토타입은 자신을 통해 만들어질 객체의 원형을 뜻합니다. 정확한 용어로는 prototype property(프로토타입 속성) 을 의미합니다.

< 키워드 >

  • Prototype Link : 자신을 만들어낸 객체의 원형

  • Prototype Object : 자신을 통해 만들어질 객체의 원형

예시 코드를 통해 확인해 보겠습니다.

function person() {
  
}

console.dir(person)

위와 같은 결과를 그림으로 표현해 보겠습니다.

  • --proto-- : 자신을 만들어낸 객체의 원형과 연결된 속성입니다.

  • constructor : 생성자로써, 자신을 만들어낸 객체와 연결된 속성입니다.

  • prototype : 자신을 원형으로 만들어진 새로운 객체들과 연결된 속성입니다.

프로토타입 체인을 이용한 상속

자바스크립트 객체는 속성을 저장하는 동안 프로토타입 객체에 대한 링크를 가집니다. 이 뜻은 객체의 어떤 속성에 접근하려할 때 그 객체 자체 속성 뿐만 아리라 객체의 프로토타입, 그 프로토타입의 프로토타입 ... 체인의 끝까지 이를때까지 속성을 탐색합니다.

프로토타입 체이닝에 대해 mdn에서 설명이 잘 되어있어 인용해보겠습니다.

// o라는 객체가 있고, 속성 'a' 와 'b'를 갖고 있다고 하자.
let f = function () {
    this.a = 1;
    this.b = 2;
}
let o = new f(); // {a: 1, b: 2}

// f 함수의 prototype 속성 값들을 추가 하자.
f.prototype.b = 3;
f.prototype.c = 4;

// f.prototype = {b: 3, c: 4}; 라고 하지 마라, 해당 코드는 prototype chain 을 망가뜨린다.
// o.[[Prototype]]은 속성 'b'와 'c'를 가지고 있다. 
// o.[[Prototype]].[[Prototype]] 은 Object.prototype 이다.
// 마지막으로 o.[[Prototype]].[[Prototype]].[[Prototype]]은 null이다. 
// null은 프로토타입의 종단을 말하며 정의에 의해서 추가 [[Prototype]]은 없다. 
// {a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null

console.log(o.a); // 1
// o는 'a'라는 속성을 가지는가? 그렇다. 속성의 값은 1이다.

console.log(o.b); // 2
// o는 'b'라는 속성을 가지는가? 그렇다. 속성의 값은 2이다.
// 프로토타입 역시 'b'라는 속성을 가지지만 이 값은 쓰이지 않는다. 이것을 "속성의 가려짐(property shadowing)" 이라고 부른다.

console.log(o.c); // 4
// o는 'c'라는 속성을 가지는가? 아니다. 프로토타입을 확인해보자.
// o.[[Prototype]]은 'c'라는 속성을 가지는가? 가지고 값은 4이다.

console.log(o.d); // undefined
// o는 'd'라는 속성을 가지는가? 아니다. 프로토타입을 확인해보자.
// o.[[Prototype]]은 'd'라는 속성을 가지는가? 아니다. 다시 프로토타입을 확인해보자.
// o.[[Prototype]].[[Prototype]]은 null이다. 찾는 것을 그만두자.
// 속성이 발견되지 않았기 때문에 undefined를 반환한다.

위의 코드는 끝까지 탐색하는 모습을 나타낸 코드입니다.

메소드 상속

자바스크립트는 객체의 속성으로 함수를 지정 할 수 있고 속성 값을 사용하듯 쓸 수 있습니다.

상속된 함수가 실행 될 때, this라는 변수는 상속된 객체를 가르킵니다. 그 함수가 프로토타입의 속성으로 지정 되었다는 말로 표현 할 수 있습니다.

Javascript 에서 프로토타입을 사용하는 방법

var Human = function(name) {
  this.name = name;
}

Human.prototype.sleep = function() {
  console.log(this.name + ' is sleeping...');
}

var steve = new Human('steve');

var Student = function(name) {
  Human.call(this.name); // Human.apply(this, arguments)
}

Student.prototype = Object.create(Human.prototype);
Student.prototype.learn = function() {};

var john = new Student('john');
john.learn();
john.sleep();
                  

Object.create 메소드를 사용하여 상속 시켰습니다.

하지만 여기에 구조적으로 문제가 있습니다. constructor부분이 연결이 되지 않았습니다.

constructor를 연결하기 위해서

Student.prototype.constructor = Student; 

를 추가해서 처리 해줍니다.

2. Class를 통한 상속

아래에 코드는 class를 통한 상속 예제입니다. class를 통한 상속에는 class, constructor, extends, super 키워드가 있습니다. 각각의 키워드가 어떤 의미를 가지는지 알아 보겠습니다.

class Human {
  constructor(name) {
    this.name = name;
  }
  sleep(){
  }
}

var steve = new Human('steve');

class Student extends Human {
  constructor(name) {
    super(name); //  this부분을 super 키워드로 대체 한다.
  }
  
  learn() {
  }
}

var john = new Student('john');
john.learn();
john.sleep();

< keyword >

Class

constructor

extends

super
  • class

자바스크립트 class는 기존의 prototype 기반의 상속 보다 명료하게 사용 할 수 있다는 장점이 있습니다. class문법은 새로운 객체지향 상속 모델은 아닙니다. 단지 javaScirpt 에서 객체를 생성하고 상속을 다루는데에 있어서 단순하고 명확한 문법을 제공하는것입니다.

class는 함수입니다. 함수는 함수 표현식과 함수 선언으로 정의가 가능합니다. 따라서 class도 class표현식과 class선언 두 가지 방법이 있습니다.

class선언

class 선언 
class Human {
  constructor(name) {
    this.name = name;
  }
  sleep(){
  }
}

여기서 하나 의문점이 생길수 있습니다. 함수 선언과 클래스 선언이 똑같이 이루어질까?? 다른 점은 없을까 하는 의문이 생깁니다.

const p = new Rectangle(); // ReferenceError

class Rectangle {}

그 의문점을 위의 코드가 해결 해 줍니다. 함수로 선언을 했을 경우에는 호이스팅이 일어나 위의 코드가 에러가 나지 않습니다. 하지만 class선언에서는 호이스팅이 일어나지 않습니다.

위의 코드를 보면 class를 나중에 선언해 주었기 때문에 ReferrenceError가 나오게 되었습니다.

Class 표현식

클래스 표현식은 클래스를 정의하는 다른 방법입니다. Class표현식은 이름을 가질 수 있고, 갖지 않을 수 있습니다. 이름을 가진 class 표현식의 이름은 클래스의 body에 대해 local scope에 한해 유효합니다. 여기서 클래스 body는 중괄호로 묶여 있는 부분을 뜻합니다.

// unnamed 이름 없음 
let Rectangle = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

console.log(Rectangle.name); // "Rectangle"
// named 이름 생성
let Rectangle = class Rectangle2 {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};
console.log(Rectangle.name); // "Rectangle2"

Constructor(생성자)

Constructor 메소드는 class로 생성된 객체를 생성하고 초기화하기 위한 특수한 메소드입니다.

또한 Constructor 라는 이름을 가진 특수한 메소드는 클래스안에 한 개만 존재 할 수있습니다.

만약 클래스에 여러개의 Constructor 메소드가 존재하면 SyntaxError가 발생 합니다.

constructor는 부모 클래스의 constructor를 호출하기 위해 super 키워드를 사용합니다.

Instance properties

인스턴스의 속성은 반드시 클래스 메소드에 정의가 되어야만 합니다.

class Rectangle {
  constructor(height, width) {    
    this.height = height;
    this.width = width;
  }
}

extend를 통한 클래스 상속

extends 키워드는 클래스선언, 클래스 표현식에서 다른 클래스의 자식 클래스를 생성하기 위해 사용합니다.

코드를 통해 알아 보겠습니다.

class Animal { 
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Dog extends Animal {
  speak() {
    console.log(this.name + ' barks.');
  }
}

만약 자식 클래스(subclass)에 constructor가 있다면 this를 사용하기 전 가장 먼저 super()를 호출해야 합니다.

super

super키워드는 부모 객체의 함수를 호출하는 경우에 사용합니다.

constructor(생성자)에서 super 키워드는 하나만 사용하거나 this키워드가 사용 되기 전에 호출이 되어야 합니다. 또한 super키워드는 부모 객체의 함수를 호출하는데 사용 될 수 있습니다.

코드를 통해 알아보겠습니다.

class Polygon {
  constructor(height, width) {
    this.name = 'Polygon';
    this.height = height;
    this.width = width;
  }
  sayName() {
    console.log('Hi, I am a ', this.name + '.');
  }
}

class Square extends Polygon {
  constructor(length) {
    this.height; // 참조오류가 발생합니다. super가 먼저 호출되어야 합니다.
    
    // 여기서, 부모클래스의 생성자함수를 호출하여 높이값을 넘겨줍니다.
    // Polygon의 길이와 높이를 넘겨줍니다.
    super(length, length);
    
    // 참고: 파생 클래스에서 super() 함수가 먼저 호출되어야
    // 'this' 키워드를 사용할 수 있습니다. 그렇지 않을 경우 참조오류가 발생합니다.
    this.name = 'Square';
  }

  get area() {
    return this.height * this.width;
  }

  set area(value) {
    this.area = value;
  } 
}

위의 코드를 보면 this.height 부분에서 참조오류가 나는 것을 볼 수 있습니다.그 이유는 super 키워드를 사용하기 이전에 this를 사용했기 때문입니다. this.name 부분은 super키워드를 사용한 후 이기 때문에 사용 가능합니다.

super를 통한 상위 클래스 호출

class Cat { 
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(this.name + ' roars.');
  }
}

Lion클래스에서 super 키워드를 사용하여 cat class의 함수를 호출 하는 것을 볼 수 있습니다.

Reference

http://insanehong.kr/post/javascript-prototype/

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super

https://developer.mozilla.org/ko/docs/Learn/JavaScript/Objects

http://mygumi.tistory.com/312

profile
어제보다 오늘 더 노력하는 프론트엔드 개발자

0개의 댓글