클래스의 인스턴스 생성 과정
- new 연산자와 함께 클래스를 호출하면 생성자 함수와 마찬가지로 클래스 내부 메서드 [[Construct]]가 호출된다.
- 클래스는 new 연산자 없이 호출할 수 없다.
- 다음과 같은 과정을 거쳐 인스턴스가 생성된다.
1. 인스턴스 생성과 this 바인딩
- new 연산자와 함께 클래스를 호출하면 constructor의 내부 코드가 실행되기에 앞서 암묵적으로 빈 객체가 생성된다.
- 이 빈 객체가 바로 클래스가 생성한 인스턴스다. 이때 클래스가 생성한 인스턴스의 프로토타입으로 클래스의 prototype 프로퍼티가 가리키는 객체가 설정된다.
- 그리고 암묵적으로 생성된 빈 객체, 즉 인스턴스는 this에 바인딩된다.
- 따라서 constructor 내부의 this는 클래스가 생성한 인스턴스를 가리킨다.
2. 인스턴스 초기화
- constructor의 내부 코드가 실행되어 this에 바인딩되어 있는 인스턴스를 초기화한다.
- 즉, this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스의 프로퍼티 값을 초기화한다.
- 만약 constructor가 생략되었다면 이 과정도 생략된다.
3. 인스턴스 반환
- 클래스의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
class Person {
// 생성자
constructor(name) {
// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.
console.log(this); // Person {}
console.log(Object.getPrototypeOf(this) === Person.prototype); // true
// 2. this에 바인딩되어 있는 인스턴스를 초기화한다.
this.name = name;
// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
}
}
프로퍼티
1. 인스턴스 프로퍼티
- 인스턴스 프로퍼티는 constructor 내부에서 정의해야 한다.
- constructor 내부 코드가 실행되기 이전에 constructor 내부의 this에는 이미 클래스가 암묵적으로 생성한 인스턴스인 빈 객체가 바인딩되어 있다.
- constructor 내부에서 this에 인스턴스 프로퍼티를 추가하면 클래스가 암묵적으로 생성한 빈 객체, 즉 인스턴스에 프로퍼티가 추가되어 인스턴스가 초기화된다.
class Person {
constructor(name) {
// 인스턴스 프로퍼티
this.name = name; // name 프로퍼티는 public하다.
}
}
const me = new Person('Lee');
console.log(me); // Person {name: "Lee"}
// name은 public하다.
console.log(me.name); // Lee
2. 접근자 프로퍼티
- 접근자 프로퍼티는 자체적으로 값([[Value]] 내부 슬롯)을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티다.
- 접근자 프로퍼티는 클래스에서도 사용할 수 있다.
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
// fullName은 접근자 함수로 구성된 접근자 프로퍼티다.
// getter 함수
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
// setter 함수
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ');
}
}
const me = new Person('Ungmo', 'Lee');
// 데이터 프로퍼티를 통한 프로퍼티 값의 참조.
console.log(`${me.firstName} ${me.lastName}`); // Ungmo Lee
// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
// 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출된다.
me.fullName = 'Heegun Lee';
console.log(me); // {firstName: "Heegun", lastName: "Lee"}
// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
// 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다.
console.log(me.fullName); // Heegun Lee
// fullName은 접근자 프로퍼티다.
// 접근자 프로퍼티는 get, set, enumerable, configurable 프로퍼티 어트리뷰트를 갖는다.
console.log(Object.getOwnPropertyDescriptor(Person.prototype, 'fullName'));
// {get: ƒ, set: ƒ, enumerable: false, configurable: true}
- 접근자 프로퍼티는 getter 함수와 setter 함수로 구성되어 있다.
- getter 함수
- 인스턴스 프로퍼티에 접근할 떄마다 프로퍼티의 값을 조작하거나 별도의 행위가 필요할 때 사용한다.
- 메서드 이름 앞에 get 키워드를 사용해 정의한다.
- setter 함수
- 인스턴스 프로퍼티에 값을 할당할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용한다.
- 메서드 이름 앞에 set 키워드를 사용해 정의한다.
- 단 하나의 값만 할당받기 때문에 단 하나의 매개변수만 선언할 수 있다.
- 이때 getter와 setter 이름은 인스턴스 프로퍼티처럼 사용된다. 다시 말해 참조하는 형식으로 사용한다.
- 클래스의 메서드는 기본적으로 프로토타입 메서드가 된다. 따라서 클래스의 접근자 프로퍼티 또한 인스턴스 프로퍼티가 아닌 프로토타입의 프로퍼티가 된다.
// Object.getOwnPropertyNames는 비열거형(non-enumerable)을 포함한 모든 프로퍼티의 이름을 반환한다.(상속 제외)
Object.getOwnPropertyNames(me); // -> ["firstName", "lastName"]
Object.getOwnPropertyNames(Object.getPrototypeOf(me)); // -> ["constructor", "fullName"]
3. 클래스 필드 정의 제안
- 자바스크립트의 클래스 몸체에는 메서드만 선언할 수 있다. 따라서 클래스 몸체에 자바와 유사하게 클래스 필드를 선언하면 문법 에러가 발생한다.
class Person {
// 클래스 필드 정의
name = 'Lee';
}
const me = new Person('Lee');
- 하지만 위 예제를 최신 브라우저(Chrome 72 이상) 또는 최신 Node.js(버전 12 이상)에서 실행하면 문법에러가 발생하지 않고 정상 동작한다.
- 자바스크립트에서도 인스턴스 프로퍼티를 마치 클래스 기반 객체지향 언어의 클래스 필드처럼 정의할 수 있는 새로운 표준 사양인 "Class field declarations"가 TC39 프로세스의 stage 3에 제안되어 있다.
- 아직 ECMAScript 정식 표준 사양으로 승급되지 않았지만 최신 브라우저는 표준 사양으로 승급이 확실시되는 이 제안을 선제적으로 미리 구현해 놓았다.
- 따라서 최신 브라우저와 최신 Node.js에서는 클래스 필드를 클래스 몸체에 정의할 수 있다.
- 클래스 몸체에 클래스 필드를 정의하는 경우 this에 클래스 필드를 바인딩해서는 안 된다. this는 클래스의 constructor와 메서드 내에서만 유효하다.
class Person {
// this에 클래스 필드를 바인딩해서는 안된다.
this.name = ''; // SyntaxError: Unexpected token '.'
}
- 클래스 필드를 참조하는 경우 자바스크립트에서는 this를 반드시 사용해야한다.
class Person {
// 클래스 필드
name = 'Lee';
constructor() {
console.log(name); // ReferenceError: name is not defined
}
}
new Person();
- 클래스 필드에 초기값을 할당하지 않으면 undefined를 갖는다.
class Person {
// 클래스 필드를 초기화하지 않으면 undefined를 갖는다.
name;
}
const me = new Person();
console.log(me); // Person {name: undefined}
- 인스턴스를 생성할 때 외부의 초기값으로 클래스 필드를 초기화해야 할 필요가 있다면 constructor에서 클래스 필드를 초기화해야 한다.
class Person {
name;
constructor(name) {
// 클래스 필드 초기화.
this.name = name;
}
}
const me = new Person('Lee');
console.log(me); // Person {name: "Lee"}
- 함수는 일급 객체이므로 함수를 클래스 필드에 할당할 수 있다. 따라서 클래스 필드를 통해 메서드를 정의할 수도 있다.
- 하지만 클래스 필드에 함수를 할당하는 경우, 이 함수는 프로토타입 메서드가 아닌 인스턴스 메서드가 되기때문에 권장하지 않는다.
4. private 필드 정의 제안
- private 필드의 선두에는 #을 붙여준다. private 필드를 참조할 때도 #을 붙여주어야한다.
- 최신 브라우저에서 사용가능
class Person {
// private 필드 정의
#name = '';
constructor(name) {
// private 필드 참조
this.#name = name;
}
}
const me = new Person('Lee');
// private 필드 #name은 클래스 외부에서 참조할 수 없다.
console.log(me.#name);
// SyntaxError: Private field '#name' must be declared in an enclosing class
- private 필드는 클래스 내부에서만 참조할 수 있다.
- 접근자 프로퍼티를 통해 간접적으로 접근이 가능하다.
class Person {
// private 필드 정의
#name = '';
constructor(name) {
this.#name = name;
}
// name은 접근자 프로퍼티다.
get name() {
// private 필드를 참조하여 trim한 다음 반환한다.
return this.#name.trim();
}
}
const me = new Person(' Lee ');
console.log(me.name); // Lee
- private 필드는 반드시 클래스 몸체에 정의해야 한다. private 필드를 직접 constructor에 정의하면 에러가 발생한다.
class Person {
constructor(name) {
// private 필드는 클래스 몸체에서 정의해야 한다.
this.#name = name;
// SyntaxError: Private field '#name' must be declared in an enclosing class
}
}
5. static 필드 정의 제안
- 최신 브라우저에서 static public, static private, static 메서드를 정의할 수 있다.
class MyMath {
// static public 필드 정의
static PI = 22 / 7;
// static private 필드 정의
static #num = 10;
// static 메서드
static increment() {
return ++MyMath.#num;
}
}
console.log(MyMath.PI); // 3.142857142857143
console.log(MyMath.increment()); // 11
상속에 의한 클래스 확장
1. 클래스 상속과 생성자 함수 상속
- 상속에 의한 클래스 확장은 기존 클래스를 상속받아 새로운 클래스를 확장(extends)하여 정의하는 것이다.
- Animal 클래스를 확장한 Bird 클래스를 구현해 보자.
class Animal {
constructor(age, weight) {
this.age = age;
this.weight = weight;
}
eat() { return 'eat'; }
move() { return 'move'; }
}
// 상속을 통해 Animal 클래스를 확장한 Bird 클래스
class Bird extends Animal {
fly() { return 'fly'; }
}
const bird = new Bird(1, 5);
console.log(bird); // Bird {age: 1, weight: 5}
console.log(bird instanceof Bird); // true
console.log(bird instanceof Animal); // true
console.log(bird.eat()); // eat
console.log(bird.move()); // move
console.log(bird.fly()); // fly
- 클래스는 상속을 통해 다른 클래스를 확장할 수 있는 문법인 extends 키워드가 기본적으로 제공된다.
- 하지만 생성자 함수는 상속관련 문법이 제공되지 않는다.
- 상속에 의한 클래스 확장은 코드 재사용 관점에서 매우 유용하다.
2. extends 키워드
- 상속을 통해 클래스를 확장하려면 extends 키워드를 사용하여 상속받은 클래스를 정의한다.
// 수퍼(베이스/부모)클래스
class Base {}
// 서브(파생/자식)클래스
class Derived extends Base {}
- extends 키워드의 역할은 수퍼클래스와 서브클래스 간의 상속 관계를 설정하는 것이다.
- 클래스도 프로토타입을 통해 상속 관계를 구현한다.
- 수퍼클래스와 서브클래스는 인스턴스의 프로토타입 체인뿐 아니라 클래스 간의 프로토타입 체인도 생성한다.
- 이를 통해 프로토타입 메서드, 정적 메서드 모두 상속이 가능하다.
3. 동적 상속
- extends 키워드는 클래스뿐만 아니라 생성자 함수를 상속받아 클래스를 확장할 수도 있다.
- 단, extends 키워드 앞에는 반드시 클래스가 와야 한다.
// 생성자 함수
function Base(a) {
this.a = a;
}
// 생성자 함수를 상속받는 서브클래스
class Derived extends Base {}
const derived = new Derived(1);
console.log(derived); // Derived {a: 1}
- extends 키워드 다음에는 클래스뿐만이 아니라 [[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있다.
- 이를 통해 동적으로 상속받을 대상을 결정할 수 있다.
function Base1() {}
class Base2 {}
let condition = true;
// 조건에 따라 동적으로 상속 대상을 결정하는 서브클래스
class Derived extends (condition ? Base1 : Base2) {}
const derived = new Derived();
console.log(derived); // Derived {}
console.log(derived instanceof Base1); // true
console.log(derived instanceof Base2); // false
4. 서브클래스의 constructor
- 클래스에서 constructor를 생략하면 클래스에 다음과 같이 비어있는 constructor가 암묵적으로 정의된다.
constructor() {}
- 서브클래스에서 constructor를 생략하면 클래스에 다음과 같은 constructor가 암묵적으로 정의된다.
- args는 new 연산자와 함게 클래스를 호출할 때 전달한 인수의 리스트다.
constructor(...args) { super(...args); }
- super()는 수퍼클래스의 constructor(super-constructor)를 호출하여 인스턴스를 생성한다.
✏️ Rest 파라미터란?
매개변수에 ...을 붙이면 Rest 파라미터가 된다.
Rest 파라미터는 함수에 전달된 인수들의 목록을 배열로 전달받는다. (26장)
- 수퍼클래스와 서브클래스 모두 constructor를 생략할 경우 어떻게되는지 살펴보자.
// 수퍼클래스
class Base {}
// 서브클래스
class Derived extends Base {}
- 클래스에는 다음과같이 암묵적으로 constructor가 정의된다.
// 수퍼클래스
class Base {
constructor() {}
}
// 서브클래스
class Derived extends Base {
constructor() { super(); }
}
const derived = new Derived();
console.log(derived); // Derived {}
- 프로퍼티를 소유하는 인스턴스를 생성하려면 constructor 내부에서 인스턴스에 프로퍼티를 추가해야 한다.
5. super 키워드
- super 키워드는 함수처럼 호출할 수도 있고 this와 같이 식별자처럼 참조할 수 있는 특수한 키워드다.
- super는 다음과 같이 동작한다.
1. super를 호출하면 수퍼클래스의 constructor(super-constructor)를 호출한다.
2. super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.
5.1. super 호출
- super를 호출하면 수퍼클래스의 constructor(super-constructor)를 호출한다.
- 다음 예제와 같이 수퍼클래스의 constructor 내부에서 추가한 프로퍼티를 그대로 갖는 인스턴스를 생성한다면 서브클래스의 constructor를 생략할 수 있다.
// 수퍼클래스
class Base {
constructor(a, b) {
this.a = a;
this.b = b;
}
}
// 서브클래스
class Derived extends Base {
// 다음과 같이 암묵적으로 constructor가 정의된다.
// constructor(...args) { super(...args); }
}
const derived = new Derived(1, 2);
console.log(derived); // Derived {a: 1, b: 2}
- 다음 예제와 같이 수퍼클래스에서 추가한 프로퍼티와 서브클래스에서 추가한 프로퍼티를 갖는 인스턴스를 생성한다면 서브클래스의 constructor를 생략할 수 없다.
// 수퍼클래스
class Base {
constructor(a, b) { // ④
this.a = a;
this.b = b;
}
}
// 서브클래스
class Derived extends Base {
constructor(a, b, c) { // ②
super(a, b); // ③
this.c = c;
}
}
const derived = new Derived(1, 2, 3); // ①
console.log(derived); // Derived {a: 1, b: 2, c: 3}
- super를 호출할 때 주의할 사항은 다음과 같다.
1. 서브클래스에서 constructor를 생략하지 않는 경우 서브클래스의 constructor에서는 반드시 super를 호출해야 한다.class Base {} class Derived extends Base { constructor() { // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor console.log('constructor call'); } } const derived = new Derived();
2. 서브클래스의 constructor에서 super를 호출하기 전에 this를 참조할 수 없다.
class Base {} class Derived extends Base { constructor() { // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor this.a = 1; super(); } } const derived = new Derived(1);
3. super는 반드시 서브클래스의 constructor에서만 호출한다. 서브클래스가 아닌 클래스의 constructor나 함수에서 super를 호출하면 에러가 발생한다.
class Base { constructor() { super(); // SyntaxError: 'super' keyword unexpected here } } function Foo() { super(); // SyntaxError: 'super' keyword unexpected here }
5.2. super 참조
- 메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.
1. 서브클래스의 프로토타입 메서드 내에서 super.sayHi는 수퍼클래스의 프로토타입 메서드 sayHi를 가리킨다.// 수퍼클래스 class Base { constructor(name) { this.name = name; } sayHi() { return `Hi! ${this.name}`; } } // 서브클래스 class Derived extends Base { sayHi() { // super.sayHi는 수퍼클래스의 프로토타입 메서드를 가리킨다. return `${super.sayHi()}. how are you doing?`; } } const derived = new Derived('Lee'); console.log(derived.sayHi()); // Hi! Lee. how are you doing?
2. 서브클래스의 정적 메서드 내에서 super.sayHi는 수퍼클래스의 정적 메서드 sayHi를 가리킨다.
// 수퍼클래스 class Base { static sayHi() { return 'Hi!'; } } // 서브클래스 class Derived extends Base { static sayHi() { // super.sayHi는 수퍼클래스의 정적 메서드를 가리킨다. return `${super.sayHi()} how are you doing?`; } } console.log(Derived.sayHi()); // Hi! how are you doing?
6. 상속 클래스의 인스턴스 생성 과정
- 상속 관계에 있는 두 클래스가 어떻게 협력하며 인스턴스를 생성하는지 살펴보자. 이를 통해 super를 더욱 명확하게 이해할 수 있을 것이다.
- 직사각형을 추상화한 Rectangle 클래스와 상속을 통해 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); // ColorRectangle {width: 2, height: 4, color: "red"}
// 상속을 통해 getArea 메서드를 호출
console.log(colorRectangle.getArea()); // 8
// 오버라이딩된 toString 메서드를 호출
console.log(colorRectangle.toString()); // width = 2, height = 4, color = red
- ColorRectangle 클래스에 의해 생선된 인스턴스의 프로토타입 체인은 다음과 같다.
- 서브클래스 ColorRectangle이 new 연산자와 함께 호출되면 다음 과정을 통해 인스턴스를 생성한다.
6.1. 서브클래스의 super 호출
- 자바스크립트 엔진은 클래스를 평가할 때 수퍼클래스와 서브클래스를 구분하기 위해 "base" 또는 "derived"를 값으로 갖는 내부 슬롯 [[ConstructorKind]]를 갖는다.
- 다른 클래스를 상속받지 않는 클래스(그리고 생성자 함수)는 내부 슬롯 [[ConstructorKind]]가 "base"로 설정된다.
- 상속받는 클래스는 "derived"로 설정된다.
- 이를 통해 수퍼클래스와 서브 클래스는 new 연산자와 함께 호출되었을 때 동작이 구분된다.
- 다른 클래스를 상속받지 않는 클래스(그리고 생성자 함수)는 new 연산자와 함께 호출되었을 때 암묵적으로 빈 객체, 즉 인스턴스를 생성하고 이를 this에 바인딩한다.
- 하지만 서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임한다.
- 이것이 바로 서브클래스의 constructor에서 반드시 super를 호출해야 하는 이유다.
- 하지만 서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임한다.
6.2. 수퍼클래스의 인스턴스 생성과 this 바인딩
- 수퍼클래스의 constructor 내부의 코드가 실행되기 이전에 암묵적으로 빈 객체를 생성한다.
- 이 빈 객체가 바로 클래스가 생성한 인스턴스다. 즉 인스턴스는 this에 바인딩된다.
- 따라서 수퍼클래스 constructor 내부의 this는 생성된 인스턴스를 가리킨다.
// 수퍼클래스
class Rectangle {
constructor(width, height) {
// 암묵적으로 빈 객체, 즉 인스턴스가 생성되고 this에 바인딩된다.
console.log(this); // ColorRectangle {}
// new 연산자와 함께 호출된 함수, 즉 new.target은 ColorRectangle이다.
console.log(new.target); // ColorRectangle
...
- 이때 인스턴스는 수퍼클래스가 생성한 것이다.
- 하지만 new 연산자와 함께 호출된 클래스가 서브클래스라는 것이 중요하다.
- 즉, new 연산자와 함께 호출된 함수를 가리키는 new.target은 서브클래스를 가리킨다.
- 따라서 인스턴스는 new.target이 가리키는 서브클래스가 생성한 것으로 처리한다.
- 따라서 생성된 인스턴스의 프로토타입은 수퍼클래스의 prototype 프로퍼티가 가리키는 객체(Rectangle.prototype)이 아니라 new.target, 즉 서브클래스의 prototype 프로퍼티가 가리키는 객체(ColorRectangle.prototype)다.
// 수퍼클래스
class Rectangle {
constructor(width, height) {
// 암묵적으로 빈 객체, 즉 인스턴스가 생성되고 this에 바인딩된다.
console.log(this); // ColorRectangle {}
// new 연산자와 함께 호출된 함수, 즉 new.target은 ColorRectangle이다.
console.log(new.target); // ColorRectangle
// 생성된 인스턴스의 프로토타입으로 ColorRectangle.prototype이 설정된다.
console.log(Object.getPrototypeOf(this) === ColorRectangle.prototype); // true
console.log(this instanceof ColorRectangle); // true
console.log(this instanceof Rectangle); // true
...
6.3. 수퍼클래스의 인스턴스 초기화
- 수퍼클래스의 constructor가 실행되어 this에 바인딩되어 있는 인스턴스를 초기화한다.
- 즉, this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스 프로퍼티를 초기화한다.
// 수퍼클래스
class Rectangle {
constructor(width, height) {
// 암묵적으로 빈 객체, 즉 인스턴스가 생성되고 this에 바인딩된다.
console.log(this); // ColorRectangle {}
// new 연산자와 함께 호출된 함수, 즉 new.target은 ColorRectangle이다.
console.log(new.target); // ColorRectangle
// 생성된 인스턴스의 프로토타입으로 ColorRectangle.prototype이 설정된다.
console.log(Object.getPrototypeOf(this) === ColorRectangle.prototype); // true
console.log(this instanceof ColorRectangle); // true
console.log(this instanceof Rectangle); // true
// 인스턴스 초기화
this.width = width;
this.height = height;
console.log(this); // ColorRectangle {width: 2, height: 4}
}
...
6.4. 서브클래스 constructor로의 복귀와 this바인딩
- super의 호출이 종료되고 제어 흐름이 서브클래스 constructor로 돌아온다.
- 이때 super가 반환한 인스턴스가 this에 바인딩된다.
- 서브클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용한다.
// 서브클래스
class ColorRectangle extends Rectangle {
constructor(width, height, color) {
super(width, height);
// super가 반환한 인스턴스가 this에 바인딩된다.
console.log(this); // ColorRectangle {width: 2, height: 4}
...
- 이처럼 super가 호출되지 않으면 인스턴스가 생성되지 않으며, this바인딩도 할 수 없다.
- 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없는 이유가 바로 이 때문이다.
6.5. 서브클래스의 인스턴스 초기화
- super 호출 이후, 서브 클래스의 constructor에 기술되어 있는 인스턴스 초기화가 실행된다.
- 즉, this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스 프로퍼티를 초기화한다.
6.6. 인스턴스 반환
- 클래스의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
// 서브클래스
class ColorRectangle extends Rectangle {
constructor(width, height, color) {
super(width, height);
// super가 반환한 인스턴스가 this에 바인딩된다.
console.log(this); // ColorRectangle {width: 2, height: 4}
// 인스턴스 초기화
this.color = color;
// 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
console.log(this); // ColorRectangle {width: 2, height: 4, color: "red"}
}
...
7. 표준 빌트인 생성자 함수 확장
- "동적 상속"에서 살펴보았듯이 extends 키워드 다음에는 클래스뿐만 아니라 [[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있다.
- String, Number, Array 같은 표준 빌트인 객체도 [[Construct]] 내부 메서드를 갖는 생성자 함수이므로 extends 키워드를 사용하여 확장할 수 있다.
// Array 생성자 함수를 상속받아 확장한 MyArray
class MyArray extends Array {
// 중복된 배열 요소를 제거하고 반환한다: [1, 1, 2, 3] => [1, 2, 3]
uniq() {
return this.filter((v, i, self) => self.indexOf(v) === i);
}
// 모든 배열 요소의 평균을 구한다: [1, 2, 3] => 2
average() {
return this.reduce((pre, cur) => pre + cur, 0) / this.length;
}
}
const myArray = new MyArray(1, 1, 2, 3);
console.log(myArray); // MyArray(4) [1, 1, 2, 3]
// MyArray.prototype.uniq 호출
console.log(myArray.uniq()); // MyArray(3) [1, 2, 3]
// MyArray.prototype.average 호출
console.log(myArray.average()); // 1.75
'프론트엔드 > JavaScript' 카테고리의 다른 글
[33장 7번째 데이터 타입 Symbol] 모던 자바스크립트 Deep Dive (1) | 2025.03.11 |
---|---|
[26장 ES6 함수의 추가 기능] 모던 자바스크립트 Deep Dive (1) | 2025.03.10 |
[25장 클래스-1] 모던 자바스크립트 Deep Dive (0) | 2025.03.07 |
[24장 클로저] 모던 자바스크립트 Deep Dive (1) | 2025.03.07 |
[23장 실행 컨텍스트-2] 모던 자바스크립트 Deep Dvie (1) | 2025.03.05 |