📝

6. class

6-1. 클래스 정의

6-1-1. 생성자 함수

생성자 함수는 사용자 정의 객체 타입 또는 내장 객체 타입의 인스턴스를 생성하는 new 연산자와 함께 호출해서 객체를 생성하여 반환하는 함수를 말한다. 이후 프로퍼티 혹은 메서드를 추가하여 객체를 완성시킬 수 있다. 생성자 함수는 새로운 객체를 만들고 사용자가 요구하는 함수들을 구현할 수 있게 해주는데, 이렇게 생성된 객체를 인스턴스라고 한다.
 
const player = new Object(); player.name = 'Choi'; player.sayWinner = function () { console.log('Ladies and gentlemen, Champion is ' + this.name); }; player.sayWinner();
 
결과는 다음과 같다.
 
Ladies and gentlemen, Champion is Choi
 

6-1-1-1. 객체 리터럴에 의한 객체 생성 방식

필히 객체 생성자 함수를 사용해 빈 객체를 생성하는 것은 아니다. 객체를 생성하는 방법에는 생성자 함수와 객체 리터럴을 이용해서 만드는 방법이 있고 그 중 객체 리터럴을 이용하여 생성하는 방식이 가장 직관적이고 간편하다. 하지만, 단 하나의 객체만 생성하기 때문에 동일한 프로퍼티를 갖는 객체들을 생성해야 하는 경우에는 같은 프로퍼티를 계속해서 작성해야 하기 때문에 비효율적이다.
 
const rabbit = { type: '토끼', name: '빙키', sound: '깡총', say() { console.log(this.sound); } }; console.log(rabbit.say()); const frog = { type: '개구리', name: '개리', sound: '개굴', say() { console.log(this.sound); } }; console.log(frog.say());
 
결과는 다음과 같다.
 
깡총 개굴
 
위 코드에서 rabbit 객체와 frog 객체는 프로퍼티 구조가 동일하다. 객체 데이터인 type, name, sound 프로퍼티 값은 모두 다르지만 say() 메서드는 같은 것을 볼 수 있다. 이렇게 프로퍼티 구조가 같음에도 불구하고 매번 작성할 때마다 동일한 프로퍼티와 메서드를 나타내야 한다는 점에서 수 많은 객체를 생성할 경우에는 다소 불편하다.

6-1-1-2. 생성자 함수에 의한 객체 생성 방식

우리는 흔히 여러개의 문서 파일을 생성할 때 기존의 양식을 복사하여 사용할 수 있는 템플릿을 이용한다. 마찬가지로 생성자 함수를 이용하면 프로퍼티 구조가 동일한 객체들을 여러 개 생성할 수 있다.
 
function Animal(type, name, sound) { this.type = type; this.name = name; this.sound = sound; this.say = function() { console.log(this.sound); }; } const rabbit = new Animal('토끼', '빙키', '깡총'); const frog = new Animal('개구리', '개리', '개굴'); rabbit.say(); frog.say();
 
결과는 다음과 같다.
 
깡총 개굴
 
생성자 함수는 Java, Python, C++ 등의 클래스 기반 객체지향 언어와는 다르게 형식이 정해져 있지 않아서 일반 함수와 동일한 방법으로 정의할 수 있지만, new 연산자와 함께 호출하고 함수의 첫 글자는 대문자로 기술하는 것이 일반적인 관례이다.

6-1-2. 프로토타입

프로토타입의 사전적 의미는 ‘원형, 그대로’를 의미한다. 같은 생성자 함수로부터 만들어진 객체들은 모두 원래의 모습인 프로토타입 객체를 공유한다. 위 생성자 함수의 예제코드에서 살펴보았듯이 같은 객체 생성자 함수를 사용할 때 함수 또는 값을 재사용 하는데, 이것이 바로 프로토타입이다.
프로토타입은 부모(상위) 객체의 역할을 하는데 다른 객체에 프로퍼티를 공유할 수 있다. 프로토타입을 상속받은 자식(하위) 객체는 부모 객체의 프로퍼티를 자신의 것으로 사용할 수 있다.
 
notion imagenotion image
 
생성자 함수는 기본적으로 prototype 프로퍼티를 가지고 있는데, 이는 생성자 함수가 만들어지는 시점에 같이 생성되는 프로토타입을 말하고 프로토타입도 constructor 프로퍼티를 가져 생성자 함수를 말하고 있다. 고로 둘의 관계가 어느 하나 단독으로 존재할 수 없고 쌍으로 존재하기 때문에 뗄 수 없는 짝꿍의 개념이다.[1]
  • 생성자 함수의 prototype 프로퍼티 ⇒ 프로토타입
  • 프로토타입의 constructor 프로퍼티 ⇒ 생성자 함수
 
자바스크립트는 프로토타입 체인을 사용해서 상속을 구현하여 불필요한 중복을 제거하는데, 코드 재사용이 속도 향상 및 유지보수 등 개발에 엄청난 효율성을 가져다 주기 때문에 이 점이 매우 중요하다.

6-1-3. 클래스 문법

6-1-3-1. Syntactic Sugar(문법적 설탕)인가?

자바스크립트는 프로토타입 기반의 객체지향 언어이다. 따라서 클래스가 필요 없어도 생성자 함수와 프로토타입을 통해 객체지향 언어에서 사용하는 상속을 사용할 수 있다. 하지만, 다른 클래스 기반의 언어에 익숙한 프로그래머들은 위 기반의 방식에 혼란을 느끼게 되었다.
ES6부터 도입된 클래스는 다른 클래스 기반의 언어에 익숙한 프로그래머들에게 자바스크립트 학습에 대한 도움을 줄 수 있도록 매우 비슷한 객체 생성 프로세스를 제시한다. 그렇다고 해서 자바스크립트가 기존의 프로토타입 기반의 객체지향 모델을 져버리지는 않고, 기존 프로토타입 기반의 패턴을 클래스 기반 패턴처럼 사용할 수 있도록 해주는 편의문법(문법적 설탕)이라고 볼 수 있다. 하지만, ES5에서 함수를 상속받거나 배열을 상속받는 클래스로 만들 수 없기 때문에 근본적으로 생성하는 방식에서부터 사용하는 방식까지 ES6 이전과 이후가 달라 ES6의 클래스에 대한 개념이 절대로 ES5를 흉내낼 수 없다는 근거로 이를 다르게 주장하는 측면도 존재한다.
 
주의해야할 점은 클래스와 생성자 함수간 차이점은 분명 존재한다.
 
  • new 연산자가 없으면 클래스에서 오류 발생, 생성자 함수에서는 일반 함수로서 호출된다.
  • 상속을 지원하는 extendsuper 키워드를 클래스에서는 지원, 생성자 함수에서는 미지원한다.
  • 클래스 내 모든 코드에 strict mode(엄격 모드)가 지정되어 있으며, 이를 해제할 수 없다. 반면, 생성자 함수는 strict mode(엄격 모드)가 지정되어 있지 않다.
 
간단히 요약해보면, 클래스가 생성자 함수보다 더욱 엄격한 규칙을 따르며 생성자 함수에서 제공하지 않는 기능을 클래스가 제공한다는 점이다.
 

6-1-3-2. 클래스 사용

클래스를 정의하기 위해 class를 선언하여야 하는데, class 키워드와 클래스 이름을 사용해야 한다. 클래스의 이름은 생성자 함수와 동등하게 첫 번째 글자를 대문자로 사용하는 파스칼 케이스를 사용하는 것이 일반적이다. 물론 사용하지 않아도 에러가 발생하지는 않는다.[2]
 
class Player {}
 

6-1-3-3. 표현식

클래스 표현식은 이름을 가질 수도 있고, 갖지 않을 수도 있다. 표현식으로 정의할 수 있다는 얘기는 클래스가 값으로 사용할 수 있다는 것이다. 이는 또다른 의미로 클래스가 일급 객체(first-class)의 특징을 갖는다는 것을 의미한다. 아래는 일급 객체(first-class)의 특징을 나타낸다.
  • 함수 리터럴을 사용하여 생성할 수 있다.
  • 변수나 프로퍼티, 자료구조에 값으로 할당이 가능하다.
  • 함수의 인자로 전달할 수 있다.
  • 함수의 리턴값으로 사용할 수 있다.
 
더욱 자세하게 살펴보면, 클래스는 함수이다. 따라서 클래스를 값처럼 사용할 수 있다.
 
class Player {} console.log(typeof Player);
 
결과는 다음과 같다.
 
function
 
클래스는 함수와 마찬가지로 이름을 가질 수 있는 기명 클래스 표현식과 갖지 않을 수 있는 익명 클래스 표현식으로 나타낼 수 있다.
 
const Player = class {}; // 익명 클래스 표현식 const Player = class myClass {}; // 기명 클래스 표현식
 

6-1-4. 정의 방식 비교

생성자 함수는 function 뒤에 함수명을 적는 반면, 클래스는 class 키워드 뒤에 클래스명을 적어 정의한다.

6-1-4-1. 생성자 함수

 
var Player = (function () { function Player(name) { this.name = name; } // 프로토타입 메서드 Player.prototype.sayWinner = function () { console.log('Ladies and gentlemen, Champion is ' + this.name); }; // 정적 메서드 Player.sayWinner = function () { console.log('Champion!'); }; return Player; }()); Player.sayWinner();
 
결과는 다음과 같다.
 
Champion!
 

6-1-4-2. 클래스

 
class Player { constructor(name) { this.name = name; } // 프로토타입 메서드 sayWinner() { console.log('Ladies and gentlemen, Champion is ${this.name}'); } // 정적 메서드 static sayWinner() { console.log('Champion!'); } } Player.sayWinner();
 
결과는 다음과 같다.
 
Champion!
 

6-2. 클래스 호이스팅

앞선 5단원 var의 호이스팅 부분에서도 언급을 하였지만, 클래스의 선언문으로 정의한 클래스는 소스코드 평가 과정에서 실행하기 이전에 먼저 평가되어 함수 객체를 생성하는데 이것이 바로 호이스팅이다.
쉽게 말해 모든 선언문을 먼저 찾아내서 실행하고 난 뒤에 이후 모든 선언문을 제외한 소스코드부터 순차적으로 실행하는데, 이 때 클래스 선언문이 최상단으로 끌어올려져 동작하는 것을 의미한다.
이렇게 생성된 함수 객체는 생성자 함수로서 호출할 수 있는 함수인데 프로토타입과 더불어 같이 생성된다. 생성자 함수와 프로토타입은 어느 하나 단독으로 존재할 수 없고 항상 같이 존재하기 때문이다.
주의해야할 점은 클래스는 정의하기 이전에 참조할 수 없다.
 
console.log(Player); class Player {}
 
결과는 다음과 같다.
 
notion imagenotion image
notion imagenotion image
 
클래스 선언문도 변수 선언, 함수 정의와 마찬가지로 호이스팅이 발생하는데, 주의점으로 클래스는 let, const 키워드로 선언한 변수처럼 호이스팅된다. 따라서 클래스 선언문 이전에 일시적 사각지대(Temporal Dead Zone : TDZ)에 빠져 호이스팅이 발생하지 않는 것처럼 동작한다.
호이스팅은 선언이 먼저 메모리 공간에 저장되었다는 것을 의미하기 때문에 let, const, function, class 등을 이용한 선언문의 모든 식별자는 호이스팅이 되는데 그 이유는 모든 선언문은 런타임 이전에 먼저 실행되기 때문이다.

6-3. 인스턴스

6-3-1. 인스턴스란?

인스턴스는 클래스 기반으로 만든 객체이다. 때에 따라 인스턴스를 객체라고 부르기도 한다. 함수의 선언이 있고 호출이 있어야 동작하는 것처럼 클래스도 인스턴스를 생성해야 클래스에 들어있는 함수들을 사용할 수 있다. 흔히 사람들은 클래스를 붕어빵 틀 인스턴스를 붕어빵으로 비유한다. 즉, 하나의 클래스로 여러 인스턴스(객체)를 생성할 수 있다는 뜻이다. 각 붕어빵(인스턴스)마다 들어있는 팥의 양이나, 익힌 정도는 다를 수 있지만 물고기 모양을 하고 있다는 점, 안에 팥이 들어있다는 점 등 한 붕어빵 틀에서 나왔기 때문에 붕어빵들은 공통된 점을 가지고 있다.클래스는 인스턴스를 생성하기 위해 만들어진다. 클래스에서는 new 연산자와 함께 호출해야 인스턴스 생성이 가능하다.
 
class Fruit {} // 인스턴스 생성 const berry = new Fruit(); console.log(berry);
 
new 연산자 없이 클래스를 호출시 에러가 발생한다.
 
notion imagenotion image
 

6-4. 메서드

클래스에서는 constructor(생성자), 프로토타입 메서드, 정적 메서드 이렇게 세 가지 메서드만 사용이 가능하다. 클래스 메서드 사이에는 세미콜론(;)을 쓰지 않도록 주의해야 한다.

6-4-1. constructor(생성자)

constructor는 클래스의 인스턴스를 생성하고 가장 처음 호출되어 초기화 처리를 하는 특수한 메서드이다. 이름을 바꿀 수 없다는 큰 특징을 가지고 있다. 메서드 이름이 constructor지만 호출 시에는 constructor라는 이름을 사용하지 않고 new 연산자와 클래스 이름으로 호출한다.
 
class Fruit { constructor(name) { this.name = name; } } const fruits = new Fruit('cherry'); console.log(typeof Fruit) console.dir(Fruit);
아래 콘솔 창을 보면 class의 자료형은 함수(function)라고 나온다. 또한 Fruit 클래스의 prototype 프로퍼티가 가리키는 프로토타입 객체의 contructor 프로퍼티는 클래스 Fruit 자신을 향하고 있다. 결국 클래스가 인스턴스를 생성하는 생성자 함수라는 것이다.
 
notion imagenotion image
 
클래스의 constructor 내부에 추가한 name 프로퍼티가 클래스가 생성한 인스턴스의 프로퍼티로 추가된 것을 확인할 수 있다. class 내부에 있는 constructor 메서드는 생성된 함수 객체나 클래스가 생성한 인스턴스 콘솔 창 어디에도 보이지 않는다. 이는 constructor가 클래스가 평가되면서 객체의 코드 일부가 되기 때문이다.
 
💡
클래스의 constructor 메서드와 프로토타입 constructor 프로퍼티는 헷갈릴 수 있지만, 서로 연관이 없다. 프로토타입의 constructor 프로퍼티는 생성자 함수를 말하며 모든 프로토타입이 가지고 있는 프로퍼티이다.
 
notion imagenotion image
 
constructor는 몇가지 특징을 가지고 있다.
  • 클래스 내 constructor는 한 개만 존재할 수 있다. constructor가 2개 이상 class에 포함되면 에러가 발생한다.
    • class Fruit { constructor(name) { this.name = name; } constructor(num) { this.num = num; } }
       
      notion imagenotion image
       
  • constructor는 생략이 가능하지만 프로퍼티가 추가되어 초기화된 인스턴스를 생성하려면 constructor에 매개변수를 선언하고 초깃값을 전달해야 인스턴스 생성 시 프로퍼티가 추가되기 된다. 인스턴스 생성과 동시에 프로퍼티 추가를 통해 인스턴스 초기화를 원하면 constructor를 생략해서는 안 된다.
  • 반환문은 사용하지 않아야 한다. 생성자 함수에서 new 연산자와 함께 클래스가 호출하면 암묵적으로 인스턴스를 반환하기 때문에 return 문은 쓰지 말아야 한다.
    •  
      class Fruit { constructor(name) { this.name = name; return {}; } } const fruits = new Fruit('cherry'); console.log(fruits);
      결과는 다음과 같다.
       
      {}
       

6-4-2. 프로토타입 메서드

생성자 함수는 프로토타입의 메서드를 생성하려면 명시적으로 프로토타입 메서드를 추가해야 한다.
 
function Fruit(name) { this.name = name; } Fruit.prototype.sayYum = function () { console.log(`${this.name} is Yummy`) }; const fruits = new Fruit('cherry'); fruits.sayYum();
 
결과는 다음과 같다.
 
cherry is Yummy
 
생성자 함수와는 다르게 클래스에서는 아무것도 추가하지 않은 메서드는 기본적으로 프로토타입 메서드가 된다. 클래스의 prototype 프로퍼티에 메서드를 추가하지 않아도 자동적으로 프로토타입 메서드가 된다.
 
class Fruit{ constructor(name) { this.name = name; } sayYum() { console.log(`${this.name} is Yummy`) } } const fruits = new Fruit('cherry'); fruits.sayYum();
 
결과는 다음과 같다.
 
cherry is Yummy

6-4-3. 정적 메서드

생성자 함수의 경우 정적 메서드를 생성하기 위해서는 메서드를 추가해야 한다.
 
function Fruit(name) { this.name = name; } //정적메서드 Fruit.sayYum = function () { console.log('Yummy'); }; Fruit.sayYum();
 
결과는 다음과 같다.
 
Yummy
 
클래스에서는 static 키워드를 메소드에 붙이면 정적 메소드가 된다.
 
class Fruit { constructor(name){ this.name = name; } static sayYum() { console.log('Yummy'); } } Fruit.sayYum();
결과는 다음과 같다
 
Yummy
 
정적 메서드의 가장 큰 특징으로는 인스턴스를 생성하지 않고도 호출이 가능하다는 점이다. 정적 메서드는 클래스에 바인딩 된 메서드가 되기 때문에 클래스 정의 평가 시 함수 객체가 되므로 클래스 정의 이후 인스턴스 생성 없이도 호출이 가능하다.
 
💡
바인딩(binding)이란? 프로그램에 사용된 구성 요소의 실제 값 또는 프로퍼티 등 각종 값들이 확정되어 더 이상 변경할 수 없는 상태가 되는 것.
 
고로 정적 메서드는 클래스로 호출한다. 정적 메서드를 인스턴스로 호출하려고 한다면 오류가 난다.
 
const fruits = new Fruit('cherry'); fruits.sayYum();
 
notion imagenotion image
 
정적 메서드와 프로토타입 메서드의 차이를 정리하자면 정적 메서드는 클래스로 호출해야 하고, 프로토타입 메서드는 인스턴스로 호출해야 한다.
 
class Fruit { // 정적 메소드 static sayYum() { console.log('Yummy'); } // 프로토타입 메서드 sayHi() { console.log('Hi'); } } const fruits = new Fruit('cherry'); fruits.sayHi(); // Hi Fruit.sayYum(); // Yummy

6-5. 프로퍼티

6-5-1. 프로퍼티란?

설명에 앞서 MDN에서 말하는 프로퍼티란 무엇인지 알아보자.[3]
프로퍼티에는 두 종류가 있다.
  • 인스턴스 프로퍼티(Instance property) : 주어진 특정한 인스턴스(객체)의 데이터를 가진다.
  • 스태틱 프로퍼티(Static property) : 모든 객체 인스턴스들과 공유되는 데이터를 가진다.
또한 프로퍼티는 이름과 값을 가진다. (key와 value)
  • 이름 : 문자열이거나 심볼 (key)
  • 값 : 원시값(primitive), 메서드(method) 또는 객체 참조(object reference)(value)
 
위에서 설명한 것을 코드로 실습해보며 살펴보자. 우선, 간단한 객체를 작성해 보자.
 
// 우리는 객체하면 중괄호안에 키와 값이 쌍으로 여러 개 있는 형태를 떠올린다. const user = { // 여기서 key : value 쌍을 객체의 프로퍼티, 엄밀하게 데이터 프로퍼티라고 한다. name: 'sungjae', dept: 'FE', age: 10 }
 
어렵게 생각하지 않아도 좋다. 우선은 해당하는 객체가 가진 특성을 프로퍼티라고 떠올리면 된다.
💡
user라는 객체의 특성이, 이름은 성재이고, 부서는 FE이며 나이는 10살이다.
 

깊게 들여다 보는 프로퍼티

조금 더 깊게 살펴보면 자바스크립트에는 프로퍼티가 생길 때 해당 프로퍼티의 상태를 의미하는 프로퍼티 어트리뷰트를 자동으로 정의한다.
프로퍼티 어트리뷰트를 확인하려면, Object의 메서드인 getOwnPropertyDescriptor, getOwnPropertyDescriptors로 가능하다. 두 메소드의 반환값으로는 어떠한 객체를 반환한다.
객체리터럴로 프로퍼티를 생성해보고, 프로퍼티들의 속성도 알아보자.
 
const user = { name: 'sungjae', // 프로퍼티 하나 dept: 'FE', // 프로퍼티 둘 age: 10 // 프로퍼티 셋 }
 
notion imagenotion image
 
반환된 객체에는 값과 변경 가능여부, 열거 가능여부, 재정의 가능여부를 알려준다.
  • value : 해당 프로퍼티의 값을 의미한다.
  • writable : 변경 가능여부를 의미한다.
  • enumerable : 열거 가능여부를 의미한다.
  • configurable : 재정의 가능여부를 의미한다.
value를 제외하고는 전부 다 true로 생성되었다.
 
우리는 위에서 객체를 만들면서 3개의 프로퍼티를 생성했다. 그렇다면 수정과 삭제도 가능한지 알아보자.
 
const user = { name: 'sungjae', // 프로퍼티 하나 dept: 'FE', // 프로퍼티 둘 age: 10 // 프로퍼티 셋 } user.name = 'kwaksungjae'; // 수정 delete user.age; // 프로퍼티의 삭제는 delete로 가능하다. console.log(user); // 수정과 삭제가 되었는지 확인
 
notion imagenotion image
 
결과를 확인해보면 수정과 삭제가 가능한 것을 확인할 수 있다.
생성, 수정, 삭제 자체가 기본적으로 가능하다 정도로만 알고 있어도 좋지만 왜 수정이 가능한지(writable), 왜 삭제가 가능한지(configurable), 왜 for..in문으로 순회가 가능한지(enumerable) 그 원론적인 이유가 있었음을 알고 넘어가는 것이 좋다.

6-5-2. 인스턴스 프로퍼티

설명하기에 앞서 다시 상기시켜보자면 클래스에서 살펴볼 프로퍼티의 종류는 두 가지이다.
프로퍼티에는 두 종류가 있다.
  • 인스턴스 프로퍼티(Instance property) : 주어진 특정한 인스턴스(객체)의 데이터를 가진다.
  • 스태틱 프로퍼티(Static property) : 모든 객체 인스턴스들과 공유되는 데이터를 가진다.
 
인스턴스 프로퍼티란, 클래스가 만들어 낼 인스턴스(객체)의 프로퍼티이다. constructor 내부에서 정의해주어야 하고, this는 클래스가 생성할 객체를 가리킨다는 특징이 있다.
자바스크립트 스터디에 소속된 학생(인스턴스)을 찍어내는 클래스 코드를 보며 의미를 살펴보자.
 
class Student { constructor(id, name, topics) { this.id = id; // 값을 받아와서 정의 this.name = name; // 값을 받아와서 정의 this.topic = topics; // 값을 받아와서 정의 this.subject = 'javascript study' // 생성 당시에 정의 } } let sungjae = new Student(1, '성재', ['조건문', '클래스', '구조분해할당']);
 
notion imagenotion image
 
이처럼 프로퍼티의 값을 받아와서 정의할 수 있고 생성할 당시에 값을 할당할 수 있다. (subject는 값을 받아오지 않았지만 설정이 되어있다.)

6-5-3. 접근자 프로퍼티(accessor property)

프로퍼티에는 두 종류가 있다.
  • 인스턴스 프로퍼티(Instance property) : 주어진 특정한 인스턴스(객체)의 데이터를 가진다.
  • 스태틱 프로퍼티(Static property) : 모든 객체 인스턴스들과 공유되는 데이터를 가진다.
뜬금없이 무슨 접근자 프로퍼티인가 싶지만, 앞서 말한 인스턴스 프로퍼티와 관련이 있으므로 접근자 프로퍼티에 대해서도 알아보자. 우선은 접근자 프로퍼티의 의미를 알아보자.
💡
class가 만들어 낼 인스턴스 프로퍼티에 접근할 때 사용하는 메서드(함수)로 구성된 프로퍼티이다.
 
  • 여기서 말하는 접근이란, 값을 조회하거나 저장하는 것을 말한다.
  • 접근자 함수를 getter함수, setter함수라고도 부른다.
  • 접근자 함수이지만 괄호를 안쓴다. 즉, 값처럼 사용을 한다.
  • 접근자 프로퍼티는 값을 갖지 않는다. 다만 접근할 뿐이다.(반면, 데이터 프로퍼티는 값을 가진다.)
 
메서드 앞에 get이나 set을 붙여주면, 해당 메서드가 getter함수, setter함수가 되는 것이다. 그리고 해당 메소드의 이름이 바로 접근자 프로퍼티가 된다. 자주 보지 못했던 개념적인 설명들은 역시 글로는 이해하기가 어렵다. 값을 수정해주는 setter함수부터 값을 조회하는 getter함수까지 차근차근 코드로 이해해보자.

6-5-3-1. getter함수 & setter함수

 
class Student { constructor(id, name, topics) { this.id = id; this.name = name; this.topic = topics; this.subject = 'javascript study' } set name(value) { // name(value){...} 라는 함수(메소드)앞에 set이 있으므로 setter함수 this.name = value; } } let sungjae = new Student(1, '성재', ['조건문', '클래스', '구조분해할당']); sungjae.name = '곽성재';
 
name이라는 프로퍼티를 수정해주는 setter함수인 name() {..내용생략}을 정의해주면서, 성재로 저장했던 프로퍼티를 곽성재로 바꾸고자 한다. 그러나 다음과 같은 에러가 발생한다.
 
notion imagenotion image
 
이는 constructor의 this.nameset name()을 호출하고, 호출된 set name() 내부의 this.name이 자기 자신을 계속해서 호출하게 되기 때문에 call stack size exceeded 에러가 발생하게 된 것이다.
 
class Student { constructor(id, name, topics) { this.id = id; this.name = name; this.topic = topics; this.subject = 'javascript study' } set name(value) { // name(value){...} 라는 함수(메소드)앞에 set이 있으므로 setter함수 this._name = value; // 자기 자신을 계속 호출하지 못하도록 underscore를 써준다. } } let sungjae = new Student(1, '성재', ['조건문', '클래스', '구조분해할당']); sungjae.name = '곽성재'; console.log(sungjae.name) // undefined
 
notion imagenotion image
 
notion imagenotion image
 
처음 생성당시에는 성재가 들어있고, setter로 인해 곽성재로 수정이 된 것을 볼 수 있다.
underscore를 이용해 setter에서의 오류를 잡을 수 있었으나, 우리는 sungjae.name에 접근하면 undefined를 보게된다. 당연하게도, 우리는 _name에 값을 주었으니 접근(읽기) 또한 _name으로 해주어야 한다.
 
class Student { constructor(id, name, topics) { this.id = id; this.name = name; this.topic = topics; this.subject = 'javascript study' } set name(value) { // name(value) {...} 라는 함수(메소드)앞에 set이 있으므로 setter함수 this._name = value; } get name() { // name() {...} 라는 함수(메소드)앞에 get이 있으므로 getter함수 return this._name; } } let sungjae = new Student(1, '성재', ['조건문', '클래스', '구조분해할당']); sungjae.name = '곽성재'; // 이전과는 다르게 setter함수를 호출 sungjae.name; // 이전과는 다르게 getter함수를 호출 // 두 접근자 프로퍼티 모두 함수이지만 괄호를 쓰지 않고, 마치 값처럼 사용한다.
 
조금 엄밀히 말하자면, 우리는 _name을 수정하고 읽어내고 있는 것이다.
setter함수의 특징은 할당을 해줄 때 하나의 값만 할당받는 것이다. 다음은 배열을 활용하여 마치 두 개의 값을 받을 수 있는 것처럼 작성한 setter함수이다.
💡
underscore(_)를 반드시 사용하지 않아도 되지만, 관례상 많이 쓴다.
 
class Student { constructor(id, name, topics) { this.id = id; this.name = name; this.topic = topics; this.subject = 'javascript study' } set nameAndId(value) { [this.name, this.id] = value; // 이처럼 우리는 할당 받을 때 하나만을 받게 된다. } } let sungjae = new Student(1, '성재', ['조건문', '클래스', '구조분해할당']); sungjae.nameAndId = ['곽성재', 10101010];
 
지금 단계에선 접근자 프로퍼티의 깊은 탐구보다는, getter함수는 읽을 때 쓰는 것이고 setter함수는 변경할 때 쓰는 것이라고 이해하기만 해도 좋다.

6-5-4. 클래스 필드

자바스크립트에서 클래스 필드를 처음으로 학습하고 다른 클래스 기반의 객체지향 언어를 학습한다면, 그 의미에 차이가 있음을 알고있어야 한다. 자바스크립트에서 클래스 필드 정의는 클래스 몸체에서(constructor 내부가 아닌) 프로퍼티를 정의할 수 있는 방법이다.
우리는 위에서 프로퍼티를 설정할 때 constructor라는 특수한 공간에서만 정의했다. 그러나 최신 표준에서는 constructor내부가 아닌 그냥 class의 내부에서 작성해도 된다. 하지만 사용자로부터 값을 입력받아서 생성하고 싶다면 여전히 constructor내부에서 this와 함께 혼용해서 사용해야한다.
 
class Student { // constructor외부에서 정의가 가능하다 subject = 'javascript study' constructor(id, name, topics) { this.id = id; this.name = name; this.topic = topics; } } // 생성자 함수 앞에 위치할지 말 지는 상관없다. class Student { constructor(id, name, topics) { this.id = id; this.name = name; this.topic = topics; } // constructor외부에서 정의가 가능하다 subject = 'javascript study' } let sungjae = new Student(1, '성재', ['조건문', '클래스', '구조분해할당']);
 
notion imagenotion image
 
다만, class 몸체에서 프로퍼티를 작성할 때 this는 사용하지 않는다.
 
class Student { this.subject = 'javascript study' // Uncaught SyntaxError: Unexpected token '.'에러가 발생한다. constructor(id, name, topics) { this.id = id; this.name = name; this.topic = topics; } }
 
💡
class에서 thisconstructor내부나 method에서만 사용한다.
 

6-5-4-1. private 필드 정의

자바스크립트에서 우리는 객체를 만들었고 해당 객체의 프로퍼티나 메소드를 쉽게 읽고 수정해 왔다.
그러나 다른 클래스 기반의 객체지향 언어에서는 이렇게 모든 프로퍼티와 메소드에 개방적이지 않고 감추고 싶은 것은 감출 수 있게 접근 제한자라는 것이 존재한다.
최신 표준의 자바스크립트에서도 원하는 정보를 감출 수 있도록 private 필드 정의를 지원한다. 사용방법은 클래스 몸체에서 #을 붙여 프로퍼티를 정의한다.
 
class Student { subject = 'javascript study' #id; // 값은 받아와서 할당하기위해 선언만 해준다. 이대로 값을 받아오지 않는다면 undefined. constructor(id, name, topics) { this.#id = id; this.name = name; this.topic = topics; } } let sungjae = new Student(1, '성재', ['조건문', '클래스', '구조분해할당']);
 
이제 private하게 정의한 id에 접근해보자.
 
console.log(sungjae.id) // undefined
 
이처럼 감추고 싶은 프로퍼티를 정의하고자 한다면, #을 사용하여 정의할 수 있다. 이 때 유의할 점은, constructor내부에서만 #으로 정의한다면 에러가 발생하는 것이다.
 
class Student { subject = 'javascript study' constructor(id, name, topics) { this.#id = id; this.name = name; this.topic = topics; } } // Private field '#id' must be declared in an enclosing class // private 필드는 클래스의 내부에서 반드시 선언해야 한다고 알려준다.
 
우리는 #을 이용해 정보를 감출 수 있었지만 여전히 getter, setter를 이용하면 정보의 조회, 수정이 가능하다.
 
class Student { subject = 'javascript study' #id; constructor(id, name, topics) { this.#id = id; this.name = name; this.topic = topics; } get id() { return this.#id; } set id(val) { this.#id = val; } } let sungjae = new Student(1, '성재', ['조건문', '클래스', '구조분해할당']);
// getter함수 추가하기 전 console.log(sungjae.id); // undefined console.log(sungjae.#id); // '#id' must be declared in an enclosing class // getter함수 추가한 후 console.log(sungjae.id); // 1 sungjae.id = 10101; // setter함수의 추가로 수정이 가능하다. console.log(sungjae.id); // 10101

6-5-4-2. static 필드 정의

프로퍼티에는 두 종류가 있다.
  • 인스턴스 프로퍼티(Instance property) : 주어진 특정한 인스턴스(객체)의 데이터를 가진다.
  • 스태틱 프로퍼티(Static property) : 모든 객체 인스턴스들과 공유되는 데이터를 가진다.
static의 의미는, 클래스라는 함수 자체에 무언가를 설정해주는 것이다. 지금까지 우리는 클래스가 만들어낸 객체에 무언가를 설정해왔다. 하지만, static은 클래스라는 함수 자체에 설정해 주는 것이다. 그것이 함수이면 스태틱 메소드가 되고 그것이 어떠한 값이라면 스태틱 프로퍼티가 된다.
스태틱한 무언가를 생성하기 위해서는 앞에 static이라는 단어를 붙인다. Student라는 클래스가 가지는 특성은 사람이어야 하고, 입문자 수준이라는 클래스의 특성을 설정해보자.
 
class Student { // static을 붙여서 선언해준다. static isHuman = true; static level = 'beginner'; subject = 'javascript study' #id; constructor(id, name, topics) { this.#id = id; this.name = name; this.topic = topics; } get id() { return this.#id; } set id(val) { this.#id = val; } } let sungjae = new Student(1, '성재', ['조건문', '클래스', '구조분해할당']);
 
프로퍼티들을 생성했으니 콘솔창에서 확인해보자.
 
console.log(sungjae.isHuman); // undefined console.log(sungjae.level); // undefined console.log(Student.isHuman); // true console.log(Student.level); // 'beginner'
 
이처럼, 인스턴스 프로퍼티와 스태틱 프로퍼티의 가장 큰 차이점은 접근을 어디에서 하느냐에 있다.
  • 접근을 객체에서 해주어야 한다 == 개별의 객체들만이 가져야 할 속성이다. ⇒ 인스턴스 프로퍼티로 설정
  • 접근을 클래스에서 해주어야 한다 == 클래스 자체가 가져야 할 속성이다. ⇒ 스태틱 프로퍼티로 설정
프로퍼티에 대한 깊은 학습이 필수는 아니지만 생성할 객체의 목적과 상황에 따라 공통적인 속성인지 개별적인 속성인지 또는 감춰야 할 속성인지 등을 고민하는 시간이 더욱 필요해보인다.
끝으로 기억해야할 만한 사항들을 짧게 정리해보았다.
 
💡
1. 프로퍼티는 객체 또는 클래스의 속성 혹은 상태이다.
2. 프로퍼티는 값을 받거나 직접 할당할 수 있다.
3. 프로퍼티의 조회는 객체에 .을 붙이거나 getter함수를 이용한다.
4. 프로퍼티의 수정은 객체에 .을 붙여 재할당하거나 setter함수를 이용한다.
5. 반드시 constructor 내부에서 정의하지 않아도 된다. (최신사양)
6. #을 이용해 외부로부터 접근이 불가능private필드를 지원한다. (최신사양)
7. 클래스 자체에 속성 혹은 상태를 지정하려면 static을 이용한다. (최신사양)

6-6. 상속에 의한 확장

클래스 확장은 기존의 상속을 이용해서 클래스의 기능을 확대, 확장시킨다는 의미를 가지고 있다. 클래스 확장의 개념과 관련된 용어를 정리해보고 예시와 함께 클래스 상속을 이용해서 어떻게 객체가 생성되는지 과정을 살펴보자.

6-6-1. 클래스 확장이란 무엇일까?

6-6-1-1. 이전까지의 class 상속

기존의 클래스 상속은 프로토타입 체인과 new 연산자를 사용해서 생성한 인스턴스를 생성하고 생성된 인스턴스가 클래스의 프로퍼티와 메서드를 사용할 수 있는 것을 말하였다. 다음의 코드가 실행되면 new 연산자를 통해 인스턴스에 프로퍼티가 생성되고 프로토타입 체인을 통해 class의 메서드를 이용할 수 있게 된다.
 
class NormalClass { constructor(arg1, arg2) { this.normalArg1 = arg1; this.normalArg2 = arg2; } normalMethod() { console.log("노말 메소드 예시입니다."); } } const normal = new NormalClass(10, 20); console.dir(normal);
 
NormalClass에 의해 생성된 객체 (normal)의 프로토타입 체인NormalClass에 의해 생성된 객체 (normal)의 프로토타입 체인
NormalClass에 의해 생성된 객체 (normal)의 프로토타입 체인
 
이러한 관계를 간략하게 표현해 보았을 때 다음과 같이 표현할 수 있다.
알잘딱깔센 javascript: 기존 클래스의 프로토타입 체인 모식도알잘딱깔센 javascript: 기존 클래스의 프로토타입 체인 모식도
알잘딱깔센 javascript: 기존 클래스의 프로토타입 체인 모식도
 

6-6-1-2. 클래스 확장 정의

기존의 클래스는 클래스 → 생성한 인스턴스로 클래스의 프로퍼티를 생성해 주고 메서드를 사용 가능하게 해주었다면 클래스 확장은 클래스 → 또 다른 클래스로 프로퍼티와 메서드의 사용 권한을 상속해 준다고 할 수 있다.
클래스 확장으로 클래스(클래스 B)가 또 다른 클래스(클래스 A)로부터 속성들을 상속받고 상속받은 클래스(클래스 B)가 생성하는 인스턴스는 new 연산자를 통해 클래스 A, B 모두의 프로퍼티를 생성하고 프로토타입 체인을 통해서 클래스 A, B의 메서드에 대해 접근을 할 수 있게 된다.
 
알잘딱깔센 javascript: 클래스 확장의 프로토타입 체인 모식도알잘딱깔센 javascript: 클래스 확장의 프로토타입 체인 모식도
알잘딱깔센 javascript: 클래스 확장의 프로토타입 체인 모식도
 
이러한 방식을 클래스 A를 B가 상속받아 A의 기능을 확장했다고 말한다. 코드를 통해 클래스 확장을 나타내기 위해선 아래와 같이 extends 키워드를 사용하고 이를 통해 클래스와 클래스 간의 관계가 상속(클래스 확장) 관계를 가진다는 것을 나타낸다.
 
// 클래스 A의 속성을 클래스 B가 상속 받는다. 클래스B extends 클래스A
클래스 확장 문법
 
extends 키워드 앞에는 기능을 상속받을 클래스를 적어주고 extends 키워드 뒤에는 기능을 상속해 줄 클래스를 적어주면 된다. 클래스 확장의 예시 및 프로토타입 체인은 어떨지 궁금하다면 6-6-3. 클래스 확장의 예시 및 인스턴스 생성 과정을 통해서 자세히 알아보겠다.

6-6-2. 슈퍼 클래스 & 서브 클래스

클래스 확장은 클래스 → 또 다른 클래스로 프로퍼티와 메서드의 사용 권한을 상속해 준다고 하였다.
이때, 사용 권한을 상속해 주는 클래스를 슈퍼 클래스, 상속받는 클래스를 서브 클래스라고 한다. 슈퍼 클래스와 서브 클래스는 상속 관계에서 각각의 클래스를 부르는 명칭이라고 할 수 있다. 이들은 다음과 같은 특징을 가진다.
 
// 클래스 A : 슈퍼 클래스 // 클래스 B : 서브 클래스 클래스B extends 클래스A
extends를 통한 슈퍼 클래스와 서브 클래스 관계 정의
 

6-6-2-1. 슈퍼 클래스

다른 명칭
베이스 클래스, 부모 클래스
의미
상속을 해줌으로써 기능이 확장된 클래스
constructor 함수 선언
슈퍼 클래스 내에선 constructor 함수는 생략되어도 된다. 생략 시 cosntructor(){}가 자동으로 생성된다. 이 경우 서브 클래스에서 super()constructor 함수를 호출하여도 constructor 함수 내부에 작성된 코드가 없기 때문에 아무 동작도 하지 않는다.
constructor 함수 호출
서브 클래스의 super()에 의해 호출된다.
서브 클래스 속성 접근
슈퍼 클래스로 생성된 객체는 서브 클래스의 속성에 접근할 수 없다.
extends 키워드
extends 키워드 뒤에 위치한다. 슈퍼 클래스의 자리에는 클래스가 와도 되지만 생성자 함수가 와도 정상적으로 동작한다.
 

6-2-2-2. 서브 클래스

다른 명칭
드라이브드 클래스(파생 클래스), 자식 클래스
의미
상속을 받아 기능을 확장하는 클래스, 생성한 객체에서 상속받은 속성을 사용할 수 있다.
constructor 함수 선언
서브 클래스에서 constructor 함수를 생략할 시 constructor(arguments){super(argu ments)}가 자동으로 생성된다.[4] 서브 클래스에서는 constructor 함수를 적어줄 때는 인스턴스 생성 과정에서 상속받은 클래스의 속성과 메서드 접근 권한을 추가해 주기 위해서 반드시 super() 함수를 호출하여야 한다.
constructor 함수 호출
new 연산자와 함께 함수를 호출했을 때 서브 클래스의 constructor() 함수가 호출(실행) 된다.
슈퍼 클래스 속성 접근
슈퍼 클래스의 constructor 호출 : super(arg, ...) 슈퍼 클래스의 프로퍼티 호출 : super.슈퍼 클래스 프로퍼티 이름 슈퍼 클래스의 메서드 호출 : super.슈퍼 클래스 메서드 이름
extends 키워드
extends 키워드 앞에 위치한다. 서브 클래스의 자리에는 클래스만 올 수 있다. 생성자 함수가 와서는 안 된다.
 
💡
super?
super 키워드는 서브 클래스에서 슈퍼 클래스의 constructor 함수와 메서드를 호출할 수 있게 해주는 키워드이다.
사용 방법
  • constructor 호출 : super(arg, ...) 방식으로 호출
  • 프로퍼티 호출 : super.슈퍼클래스의 프로퍼티이름으로 호출
  • 메서드 호출 : super.메서드이름()으로 호출
주의 사항
  • constructor 함수 내에서 사용될 때는 super 키워드는 한 번만 사용되어야 하고 constructor 함수 내에서 this가 사용되었을 경우 this 보다 우선적으로 super 키워드를 사용해 주어야 한다.
  • 서브 클래스에서 constructor를 명시적으로 적어주는 경우에는 super(arg, ...)를 반드시 호출해 주어야 한다.
 

6-6-3. 클래스 확장의 예시 및 인스턴스 생성 과정

클래스 확장의 개념과 관련된 용어를 정리해 보았다. 이제는 코드를 통해서 클래스 확장을 어떻게 구현하는지, 클래스 확장을 사용할 경우 어떻게 인스턴스가 생성되는지 알아보겠다.
 

6-6-3-1. 클래스 확장의 예시

다음의 코드에서는 Human 클래스(슈퍼 클래스)를 상속받는 Chef 클래스Waiter 클래스가 인스턴스를 생성한다.
 
// Human class(슈퍼 클래스) class Human { constructor(height, weight) { console.log(this); this.height = height; this.weight = weight; } walk() { console.log("걷는 중.."); } see() { console.log("보는 중.."); } } // Chef class(서브 클래스1) class Chef extends Human { constructor(height, weight, orderedMenu) { super(height, weight); // constructor를 명시해주는 경우에 생략해서는 안된다. this.orderedMenu = orderedMenu; } // Chef의 자체 메서드 cook() { console.log(`${this.orderedMenu}를 만드는 중...`); } } // Waiter class(서브 클래스2) class Waiter extends Human { constructor(height, weight, cookedMenu) { super(height, weight); // constructor를 명시해주는 경우에 생략해서는 안된다. this.cookedMenu = cookedMenu; console.log(this); } // Waiter의 자체 메서드 order() { console.log(`주문 받는 중...`); } // walk 메서드 오버라이드, 실행할 메서드를 찾을 때는 프로토타입 체인 상에서 제일 가까운 메서드를 찾아서 실행한다. walk() { console.log(`요리된 ${this.cookedMenu} 서빙하는 중...`); } superWalk() { super.walk(); } } const chef = new Chef(160, 67, "고등어 정식"); const waiter = new Waiter(180, 80, "고등어 정식"); console.dir(chef); chef.cook(); chef.see(); chef.walk(); console.dir(waiter); waiter.order(); waiter.see(); waiter.walk(); waiter.superWalk();
 
코드 실행 결과코드 실행 결과
코드 실행 결과
 
Human을 확장한 Waiter에 의해 생성된 인스턴스(waiter)의 프로토타입 체인Human을 확장한 Waiter에 의해 생성된 인스턴스(waiter)의 프로토타입 체인
Human을 확장한 Waiter에 의해 생성된 인스턴스(waiter)의 프로토타입 체인
 
위 코드는 두 인스턴스 chefwaiter가 만들어져 각 인스턴스의 메서드를 호출하고 그에 따른 결과를 출력하고 있다. 각각의 인스턴스는 Human 클래스를 확장한 클래스(Chef, Waiter)에 의해 생성되었기 때문에 Human 클래스의 속성인 heightweight 프로퍼티를 가지고 있으며 프로토타입 체인에 의해 Human 클래스의 메서드도 사용할 수 있다.
인스턴스 chef의 경우는 자체 속성으로 orderedMenu 프로퍼티와 cook() 메서드를 추가로 가지며 이를 사용할 수 있고 인스턴스 waiter의 경우에는 자체 속성으로 cookedMenu 프로퍼티와 order() 메서드, walk() 메서드, superWalk() 메서드를 추가로 가지며 이를 사용할 수 있다.
이처럼 클래스 확장을 사용하면 공통된 속성을 클래스로 지정하고 이에 추가되는 내용을 클래스 확장을 이용하여 코드의 재사용성을 높이고 효율적인 코드를 작성할 수 있다.
 
waiter 인스턴스의 메서드 오버라이딩waiter 인스턴스의 메서드 오버라이딩
waiter 인스턴스의 메서드 오버라이딩
 
waiterwalk() 메서드 실행 결과를 확인해 보면 waiter에서 walk() 메서드를 사용할 경우에는 Human 클래스의 walk()가 아닌 waiterwalk()가 실행되는 것을 확인할 수 있는데 이는 JavaScr ipt에서 메서드를 호출할 때 프로토타입 체인 상에서 가까운 곳부터 실행할 메서드를 찾아가기 때문에 이러한 결과를 확인할 수 있다. walk() 메서드는 Human 클래스에도 있고 Waiter 클래스에도 있지만 Waiter 클래스의 walk() 메서드가 waiter 인스턴스의 프로토타입 체인 상에서 더 가까이 있기 때문에 이러한 일이 발생하는 것이다.
이렇게 슈퍼 클래스(Human)의 메서드가 서브 클래스(Waiter)의 메서드에 의해 가려지는 현상을 메서드 오버라이딩이라고 한다.[5]
 
superWalk() { super.walk(); }
서브 클래스에서 슈퍼 클래스 메서드 사용하기
 
Waiter 클래스의 walk() 메서드가 아닌 Human 클래스의 walk() 메서드를 사용하고 싶은 경우 위와 같이 super.슈퍼클래스 메서드이름을 통해서 메서드를 호출할 수 있다. 이는 슈퍼 클래스(Human)의 속성들은 서브 클래스(Waiter) 내에서 super 키워드를 사용하여 참조되고 호출될 수 있다는 것을 이용한 방법이다.
 

6-6-3-2. 클래스 확장을 통한 인스턴스 생성 과정

위 예시를 통해서 클래스 확장을 통해 생성된 인스턴스가 인스턴스를 생성한 클래스의 속성뿐만 아니라 확장된 클래스의 속성 또한 사용할 수 있는 것을 확인할 수 있었다. 클래스 확장을 통해 생성된 인스턴스는 어떻게 모두의 속성을 이용할 수 있는 것일까? 질문에 대한 답은 Waiter 클래스의 인스턴스가 생성되는 과정을 살펴보면 알 수 있다. 클래스 확장에 의해 생성되는 인스턴스의 생성 과정은 다음과 같다.
 
  1. 서브 클래스를 new 연산자를 통해 호출하게 되면 기본 클래스 인스턴스 생성과 동일하게 constructor 함수가 실행된다. 하지만 이때 기본 클래스 인스턴스 생성과정과는 다르게 인스턴스 생성을 위한 빈 객체를 생성하지 않는다.
    1.  
      const waiter = new Waiter(180, 80, "고등어 정식");
       
  1. 서브 클래스로 사용되는 클래스의 경우에서는 constructor 함수 내에서 super 키워드를 생략할 수 없는데 서브 클래스에서는 이 super 키워드를 이용해서 슈퍼 클래스의 constructor 함수를 실행시킨다.
    1.  
      class Waiter extends Human { constructor(height, weight, cookedMenu) { super(height, weight); // constructor를 명시해주는 경우에 생략해서는 안 된다. ... } ... }
       
  1. super 키워드에 의해 슈퍼 클래스의 constructor가 실행되고 이때 생성될 waiter 인스턴스를 위해 Javascript에서 자체적으로 빈 객체를 만들고 슈퍼 클래스의 this 값이 이 빈 객체를 가리키게 된다. 이후 constructor 함수를 실행하며 객체에 Human 클래스의 프로퍼티를 추가해 준다. 완성된 객체는 super()의 반환 값으로 서브 클래스의 constructor 함수 내로 반환된다.
    1.  
      // Human class(슈퍼 클래스) class Human { constructor(height, weight) { this.height = height; this.weight = weight; } // constructor 함수 실행 결과로 아래의 객체가 생성된다. // { height : 180, weight : 80 } ... }
       
      💡
      인스턴스 생성을 위해 생성된 빈 객체 생성된 빈 객체는 [[Prototype]]으로 Waiter.prototype을 가리키는 객체가 생성되게 된다. Waiter.prototype의 [[Prototype]]은 Human.prototype을 가리키기 때문에 이로 인해서 빈 객체는 해당 Waiter, Human 클래스들에서 제공하는 메서드들을 다 사용할 수 있게 된다.
       
  1. super()의 결과로 반환된 객체는 서브 클래스의 this를 통해 가리키게 된다. 이후 서브 클래스의 constructor 함수 내에서 실행되지 못했던 super() 이후의 코드들이 실행되면서 반환된 객체에 Waiter 클래스의 프로퍼티들이 추가된다.
// Waiter class(서브 클래스2) class Waiter extends Human { constructor(height, weight, cookedMenu) { super(height, weight); // constructor를 명시해주는 경우에 생략해서는 안 된다. this.cookedMenu = cookedMenu; } // constructor 함수 실행 결과로 아래의 객체가 생성된다. // { height : 180, weight : 80, cookedMenu : "고등어 정식"} ... }
 
  1. 이렇게 완성된 객체는 new 연산자와 호출된 Class의 결과물로 반환된다.
    1.  
      const waiter = new Waiter(180, 80, "고등어 정식"); console.dir(chef);
       
console.dir(chef); 출력 결과console.dir(chef); 출력 결과
console.dir(chef); 출력 결과
 
이러한 과정을 통해 클래스 확장의 인스턴스가 생성되고 이렇게 생성된 인스턴스는 자신의 클래스 속성뿐만 아니라 상속받은 다양한 클래스의 속성까지 사용할 수 있게 된다.
 

Reference


  1. https://developer.mozilla.org/ko/docs/Learn/JavaScript/Objects/Object_prototypes
  1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes
  1. https://developer.mozilla.org/en-US/docs/Glossary/property/JavaScript
  1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Functions/arguments
  1. https://ko.wikipedia.org/wiki/메소드_오버라이딩