[TIL] [JavaScript] 클래스와 프로토타입의 상속, 참조

19880 단어 JavaScriptJavaScript

자바스크립트는 프로토타입 기반의 언어이다.
클래스 기반의 언어에서는 상속을 사용하지만, 프로토타입 기반의 언어에서는 어떤 객체를 원형(프로토타입)으로 삼고 이를 복제(참조)함으로써 상속과 비슷한 효과를 얻는다.

프로토타입 체인의 핵심은 엔진이 사용하는 __proto__ 라는 속성이다. __proto__ 속성은 ECMAScript의 스펙 [[Prototype]]이 자바스크립트로 노출된 것으로 예전 스펙이 legacy처럼 남아 있는 것이다.
모던 브라우저 개발자 도구에서도 디버깅 편의상 노출하고 있지만, 개발 코드에서 직접적으로 접근해야하는 것은 피해야 한다.
__proto__를 사용해서 프로토타입을 변경하는 것은 내부 최적화를 깨뜨리기 때문에 __proto__이 참조하는 객체를 확인해야하는 상황이라면 해당 속성을 직접 사용하지 말고 Object.getPrototypeOf()를 이용해서 참조한다.

생성자 함수와 프로토타입

function Person(name, first, second) {
  this.name = name;
  this.first = first;
  this.second = second
}

Person.prototype.sum = function(){
  return this.first + this.second
}

let kim = new Person("kim", 10, 20)
let lee = new Person("lee", 20, 20)
console.log("kim.sum()",kim.sum()) // kim.sum() 30
console.log("lee.sum()",lee.sum()) // lee.sum() 40

Person이라는 생성자 함수에 프로토타입으로 sum이라는 함수를 생성했다.
아래 코드처럼 생성자 함수 안에서 메서드를 만든다면, 인스턴스를 만들 때마다 sum함수가 각 인스턴스의 메모리로 계속 할당되는 문제가 있다.
프로토타입으로 생성을 한다면 메모리 효율성을 높일 수 있고, 프로토링크를 통해서 sum함수가 필요할 때 사용할 수 있다.

function Person(name, first, second) {
  this.name = name;
  this.first = first;
  this.second = second
  this.sum = function(){
  return this.first + this.second
}}


콘솔창에서 각 객체의 속성을 살펴보면, Person 생성자 함수의 프로토타입 프로퍼티로는 프로토타입으로 생성한 sum함수와 constructor 속성을 가지고 있다. constructor는 Person 자기 자신을 가리킨다.
Person으로 생성된 kim과 lee라는 인스턴스는 각각 프로토링크를 통해서 생성자 함수 Person의 프로토타입 프로퍼티에 있는 sum함수에 접근할 수 있다.
간단한 그림으로 나타내면 아래와 같다. (출처 - 생활코딩)

클래스와 생성자 함수

1. 클래스를 통한 상속

  • extends 키워드, constructor 메서드와 super 키워드를 사용한다.
  • super() : 부모 클래스의 속성을 생성하는 constructor로 부모와 자식 클래스간의 중복되는 프로퍼티는 super키워드로 간략하게 작성할 수 있다.
  • super.method() : 부모 클래스의 메서드를 사용할 수 있다.
class Person {
  constructor(name, first, second){
    this.name = name;
    this.first = first;
    this.second = second;
  }
  sum(){
    return this.first + this.second
  }
}

class PersonPlus extends Person {
  constructor(name, first, second, third){
    super(name, first, second);
    this.third = third
  }
  sum() {
    return super.sum() + this.third
  }
  avg(){
    return (this.first + this.second + this.third)/3
  }
}

let kim = new PersonPlus("kim", 10, 20, 30)
console.log("kim.sum()",kim.sum()) //'kim.sum()' 60
console.log("kim.avg()",kim.avg()) //'kim.avg()' 20

2. 생성자 함수를 통한 상속

  • call 메서드로 this값 및 각각 전달된 인수와 함께 함수를 호출한다.
  • call()은 이미 할당되어 있는 다른 객체의 함수, 메서드를 호출하하는 해당 객체에 재할당할 때 사용된다.
  • this는 호출하는 객체를 참조한다.
function Person(name, first, second) {
  this.name = name;
  this.first = first;
  this.second = second
}

Person.prototype.sum = function(){
  return this.first + this.second
}

function PersonPlus(name, first, second, third){
  Person.call(this, name, first, second);
  this.third = third
}

PersonPlus.prototype.avg = function()
 {
  return (this.first + this.second + this.third)/3
}

let kim = new PersonPlus("kim", 10, 20, 30)
console.log("kim.sum()",kim.sum()) // TypeError: kim.sum is not a function
console.log("kim.avg()",kim.avg()) // 'kim.avg()' 20
  • call() 메서드로 Person 함수를 호출했지만, PersonPlus가 Person의 상속 관계임을 아직 정의하지 않았기 때문에 Person 생성자 함수의 sum() 함수는 상속되지 않았다.
    해당 함수를 상속받기 위해서는 PersonPlus의 프로토타입의 프로토링크가 Person의 프로토타입을 가리키면 sum() 함수를 실행할 수 있다.
PersonPlus.prototype.__proto__ = Person.prototype
  • __proto__ 로 연결하는 방법은 권장되는 방법이 아니기 때문에 아래와 같이 자식의 프로토타입과 부모의 프토토타입을 연결하는 새로운 객체로 만든다.
  • 또한 자식의 컨스트럭터가 자기 자신을 가리키도록 해야한다.
PersonPlus.prototype = Object.create(Person.prototype);
PersonPlus.prototype.constructor = PersonPlus
console.log("kim.sum()",kim.sum()) // 'kim.sum()' 30
console.log("kim.avg()",kim.avg()) // 'kim.avg()' 20

  • 실리적인 측면에서는 상속이 필요할 때는 프로토타입의 속성을 사용하는 것보단 클래스를 사용하는 것이 낫다.

출처:
코어자바스크립트 - 정재남
생활코딩 - JavaScript 객체 지향 프로그래밍
https://meetup.toast.com/posts/104
https://javascript.info/prototype-methods

좋은 웹페이지 즐겨찾기