Skip to content

Commit 354756a

Browse files
committed
✏️ 6회차 문서 수정 : 클래스
1 parent 75eaa47 commit 354756a

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed

Javascript/class.md

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,5 +193,247 @@ console.log(me instanceof Person); // true
193193
- 자식 클래스에서 constructor를 선언하지 않으면 부모의 constructor를 바라봅니다.
194194
- 그러나 자식 클래스에서 constructor를 선언했음에도 super() 키워드를 사용하지 않는다면 참조 에러가 발생합니다.
195195

196+
super 키워드는 양이 많아 단원을 분리합니다.
197+
198+
<br>
199+
200+
## super 키워드
201+
> super 키워드는 함수처럼 호출하거나 this와 같이 식별자처럼 참조할 수 있는 특수한 키워드입니다.
202+
203+
super 키워드는 아래와 같이 동작합니다.
204+
1. super를 **호출**하면 슈퍼 클래스의 constructor()를 호출합니다.
205+
2. super를 **참조**하면 슈퍼 클래스의 메서드를 호출할 수 있습니다.
206+
207+
자세히 알아볼까요?
208+
209+
<br>
210+
211+
### super 호출
212+
> new 연산자와 함께 서브 클래스를 호출하면서 전달한 인수는 super 호출을 통해 슈퍼 클래스의 constructor()에 전달됩니다.
213+
214+
슈퍼 클래스에서 추가한 프로퍼티와 서브 클래스에서 추가한 프로퍼티를 갖는 인스턴스를 생성한다면 서브 클래스의 constructor를 생략할 수 없습니다.
215+
216+
또한 new 연산자와 함께 서브 클래스를 호출하면서 전달한 인수를 슈퍼 클래스의 constructor에 super 키워드를 통하여 전달할 수 있습니다.
217+
```js
218+
// 슈퍼 클래스
219+
class Base {
220+
constructor(a, b) { //
221+
this.a = a;
222+
this.b = b;
223+
}
224+
}
225+
226+
// 서브 클래스
227+
class Derived extends Base {
228+
// 암묵적으로 constructor가 정의되지만 직접 입력할 수 있습니다.
229+
// constructor(...args) { super(...args); }
230+
constructor(a, b, c) {
231+
super(a, b);
232+
this.c = c;
233+
}
234+
}
235+
236+
const derived = new Derived(1, 2, 3);
237+
console.log(derived); // Derived {a: 1, b: 2, c: 3}
238+
```
239+
240+
이 때 주의사항은 아래와 같습니다.
241+
1. 서브 클래스에서 constructor를 생략하지 않는 경우, 서브 클래스의 constructor에선 반드시 super를 호출해야 합니다.
242+
```js
243+
class Base {}
244+
245+
class Derived extends Base {
246+
constructor() {
247+
// ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
248+
console.log('constructor call');
249+
}
250+
}
251+
252+
const derived = new Derived();
253+
```
254+
255+
<br>
256+
257+
2. 서브 클래스의 constructor에서 super를 호출하기 전에 this를 참조할 수 없습니다.
258+
```js
259+
class Base {}
260+
261+
class Derived extends Base {
262+
constructor() {
263+
// ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
264+
this.a = 1;
265+
super();
266+
}
267+
}
268+
269+
const derived = new Derived(1);
270+
```
271+
272+
<br>
273+
274+
3. super는 반드시 서브 클래스의 constructor에서만 호출해야 합니다. 서브 클래스가 아닌 클래스의 constructor나 함수에서 super를 호출하면 에러가 발생해요.
275+
```js
276+
// 슈퍼 클래스
277+
class Base {
278+
constructor(name) {
279+
this.name = name;
280+
}
281+
282+
sayHi() {
283+
return `Hi! ${this.name}`;
284+
}
285+
}
286+
287+
// 서브 클래스
288+
class Derived extends Base {
289+
sayHi() {
290+
// super.sayHi는 슈퍼 클래스의 프로토타입 메서드를 가리킵니다.
291+
return `${super.sayHi()}. how are you doing?`;
292+
}
293+
}
294+
295+
const derived = new Derived('Lee');
296+
console.log(derived.sayHi()); // Hi! Lee. how are you doing?
297+
```
298+
299+
<br>
300+
301+
### super 참조
302+
> super는 자심을 참조하고 있는 메서드가 바인딩된 객체의 프로토타입을 가리킵니다.
303+
304+
super를 참조로 사용하는 형태들을 볼까요?
305+
306+
- 서브 클래스의 프로토타입 메서드 내에서 `super.메서드`는 슈퍼 클래스의 프로토타입 메서드를 가리킵니다.
307+
```js
308+
// 슈퍼 클래스
309+
class Base {
310+
constructor(name) {
311+
this.name = name;
312+
}
313+
314+
sayHi() {
315+
return `Hi! ${this.name}`;
316+
}
317+
}
318+
319+
// 서브 클래스
320+
class Derived extends Base {
321+
sayHi() {
322+
// super.sayHi는 슈퍼 클래스의 프로토타입 메서드를 가리킵니다.
323+
return `${super.sayHi()}. how are you doing?`;
324+
}
325+
}
326+
327+
const derived = new Derived('Lee');
328+
console.log(derived.sayHi()); // Hi! Lee. how are you doing?
329+
```
330+
- 단, super가 슈퍼 클래스의 메서드가 바인딩 된 객체인 슈퍼 클래스의 prototype 프로퍼티에 바인딩된 프로토타입을 참조할 수 있어야 합니다. 아래 처럼요.
331+
```js
332+
// 슈퍼 클래스
333+
class Base {
334+
constructor(name) {
335+
this.name = name;
336+
}
337+
338+
sayHi() {
339+
return `Hi! ${this.name}`;
340+
}
341+
}
342+
343+
class Derived extends Base {
344+
sayHi() {
345+
// __super는 Base.prototype을 가리킵니다.
346+
const __super = Object.getPrototypeOf(Derived.prototype);
347+
return `${__super.sayHi.call(this)} how are you doing?`;
348+
}
349+
}
350+
```
351+
- 이렇게 동작하기 위해 메서드는 내부 슬롯 `[[HomeObject]]`를 가지며 자신을 바인딩하고 있는 객체를 가리킵니다.
352+
- 단, ES6의 메서드 축약 표현으로 정의된 함수만 `[[HomeObject]]`를 갖습니다.
353+
```js
354+
const obj = {
355+
// [[HomeObject]]를 갖습니다.
356+
foo() {},
357+
// [[HomeObject]]를 갖지 않습니다.
358+
bar: function () {}
359+
};
360+
```
361+
362+
<br>
363+
364+
> 결국 super 참조를 의사 코드로 표현하면 아래와 같습니다.
365+
```js
366+
super = Object.getPrototypeOf([[HomeObject]])
367+
```
368+
1. `[[HomeObject]]`는 메서드 자신을 바인딩하고 있는 객체를 가리킵니다.
369+
2. `[[HomeObject]]`를 통해 메서드 자신을 바인딩하고 있는 객체의 프로토타입을 찾을 수 있습니다.
370+
3. 예로 들자면, Derived 클래스의 sayHi 메서드는 Derived.prototype에 바인딩되어 있습니다.
371+
- 따라서 Derived 클래스의 sayHi 메서드의 `[[HomeObject]]`는 Derived.prototype이고, 이를 통해 Derived 클래스의 sayHi 메서드 내부의 super 참조가 Base.prototype으로 결정됩니다.
372+
- 최종적으로 super.sayHi는 Base.prototype.sayHi를 가리키게 돼죠.
373+
374+
<br>
375+
376+
- 서브 클래의 정적 메서드 내에서 `super.메서드`는 슈퍼 클래스의 정적 메서드를 가리킵니다.
377+
```js
378+
// 슈퍼 클래스
379+
class Base {
380+
static sayHi() {
381+
return 'Hi!';
382+
}
383+
}
384+
385+
// 서브 클래스
386+
class Derived extends Base {
387+
static sayHi() {
388+
// super.sayHi는 슈퍼 클래스의 정적 메서드를 가리킵니다.
389+
return `${super.sayHi()} how are you doing?`;
390+
}
391+
}
392+
393+
console.log(Derived.sayHi()); // Hi! how are you doing?
394+
```
395+
396+
<br>
397+
398+
### 상속 클래스의 인스턴스 생성 과정
399+
> 클래스가 단독으로 인스턴스를 생성하는 과정보다 상속을 통해 인스턴스를 생성하는 과정이 더 복잡합니다.
400+
401+
서브 클래스가 new 연산자와 함께 호출되면 아래의 과정을 통해 인스턴스를 생성합니다.
402+
403+
- **서브 클래스의 super 호출**
404+
- 자바스크립트 엔진은 클래스 평가 시 슈퍼 클래스와 서브 클래스를 구분하기 위해 `base` 또는 `derived`를 값으로 갖는 내부 슬롯 `[[ConstructorKind]]`를 갖습니다.
405+
- 다른 클래스를 상속받지 않는 클래스는 내부 슬롯 `[[ConstructorKind]]`의 값이 `base`인 반면, 다른 클래스를 상속받는 클래스는 `derived`로 설정되며 이를 통해 동작이 구분됩니다.
406+
- 상속받는 클래스가 new 연산자와 함께 호출되면 자신이 직접 인스턴스를 생성하지 않고 슈퍼 클래스에 인스턴스 생성을 위임합니다. 이게 바로 서브 클래스의 constructor에서 반드시 super를 호출해야 하는 이유죠.
407+
- super가 호출되면 슈퍼 클래스의 constructor가 호출됩니다. 서브 클래스의 constructor 내부에 super 호출이 없다면 에러가 발생하는 이유는 실제 인스턴스를 생성하는 주체는 슈퍼 클래스이므로, 슈퍼 클래스의 constructor가 호출되지 않으면 인스턴스를 생성할 수 없기 때문이죠.
408+
409+
<br>
410+
411+
- **슈퍼 클래스의 인스턴스 생성과 this 바인딩**
412+
- 슈퍼 클래스의 constructor 내부의 코드가 실행되기 이전에 암묵적으로 빈 객체를 생성하는데, 이 빈 객체가 클래스의 인스턴스입니다. 그리고 암묵적으로 생성된 빈 객체는 this에 바인딩 되죠. 결국 슈퍼 클래스의 constructor 내부의 this는 생성된 this를 가리키게 됩니다.
413+
- 이 때 인스턴스는 슈퍼 클래스가 생성한 것이지만, new 연산자와 함게 호출된 클래스는 서브 클래스입니다. 즉, new 연산자와 함께 호출된 함수를 가리키는 `new.target`은 서브 클래스를 가리키며, 인스턴스는 `new.target`이 가리키는 서브 클래스가 생성한 것으로 처리됩니다.
414+
- 결국 생성된 인스턴스의 프로토타입은 슈퍼 클래스의 prototype 프로퍼티가 가리키는 객체가 아니라 `new.target`이 가리키는 서브 클래스의 prototype 프로퍼티의 객체가 되는 것이죠.
415+
416+
<br>
417+
418+
- **슈퍼 클래스의 인스턴스 초기화**
419+
- 슈퍼 클래스의 constructor가 실행되고 this에 바인딩된 인스턴스를 초기화합니다.
420+
421+
<br>
422+
423+
- **서브 클래스 constructor로의 복귀와 this 바인딩**
424+
- super 호출이 종료되고 제어 흐름이 서브 클래스로 돌아오면 super가 반환한 인스턴스가 this에 바인딩됩니다.
425+
- 서브 클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용합니다.
426+
- 이처럼 super가 호출되지 않으면 인스턴스가 생성되기는 커녕 this 바인딩도 이루어지지 않습니다. 서브 클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없는 이유죠.
427+
428+
<br>
429+
430+
- **서브클래스의 인스턴스 초기화**
431+
- 서브 클래스의 constructor가 실행되고 this에 바인딩된 인스턴스에 프로퍼티를 추가합니다.
432+
433+
<br>
434+
435+
- **인스턴스 반환**
436+
- 클래스의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환됩니다.
437+
196438
<hr>
197439
<br>

0 commit comments

Comments
 (0)