Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

25-1장 클래스 - 최주용 #1021

Merged
merged 2 commits into from
Dec 19, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
302 changes: 302 additions & 0 deletions docs/25_클래스/최주용.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
# 📌 25장 클래스

**TIL 기록일** : 2023.12.19 (25.1 ~ 25.5)

## 목차

[1. 클래스는 프로토타입의 문법적 설탕인가?](#-251-클래스는-프로토타입의-문법적-설탕인가)
[2. 클래스 정의](#-252-클래스-정의)
[3. 클래스 호이스팅](#-253-클래스-호이스팅)
[4. 인스턴스 생성](#-254-인스턴스-생성)
[5. 메서드](#-255-메서드)

## 👉 25.1 클래스는 프로토타입의 문법적 설탕인가?

자바스크립트는 프로토타입 기반 객체지향 언어이다.

프로토타입 기반 객체지향 언어는 클래스가 필요없는 객체지향 프로그래밍 언어다.

```javascript
// ES5 생성자 함수
var Person = (function () {
function Person(name) {
this.name = name;
}

// 프로토타입 메서드
Person.prototype.sayHi = function () {
console.log(`Hi! My name is ` + this.name);
};

// 생성자 함수 반환
return Person;
})();

var me = new Person("Lee");
me.sayHi(); // Hi! My name is Lee
```

ES6에 도입된 클래스는 기존 프로토타입 기반 객체지향 프로그래밍보다 자바나 C#과 같은 클래스 기반 객체지향 프로그래밍 언어와 매우 흡사한 새로운 객체 생성 매커니즘을 제시한다.

그렇다고 ES6의 클래스가 기존의 프로토타입 기반 객체지향 모델을 폐지하는 것은 아니다. 사실 클래스는 함수이며 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용할 수 있도록 하는 문법적 설탕<sup>syntactic sugar</sup> 이라고 볼 수도 있다.

클래스와 생성자 함수 모두 프로토타입 기반의 인스턴스를 생성하지만, 클래스는 더욱 엄격하며 생성자 함수에서는 제공하지 않는 기능도 제공한다.

1. 클래스를 `new` 연산자 없이 호출하면 에러가 발생한다.

2. 클래스는 상속을 지원하는 `extends`와 `super` 키워드를 제공한다.

3. 클래스는 호이스팅이 발생하지 않는 것처럼 동작한다. 하지만 함수 선언문으로 정의된 생성자 함수는 함수 호이스팅이, 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅이 발생한다.

4. 클래스 내의 모든 코드에는 암묵적으로 `strict mode`가 지정되어 실행되며 strict mode를 해제할 수 없다.

5. 클래스의 `constructor`, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false다. 다시 말해, 열거되지 않는다.

이와 같이 클래스는 생성자 함수 기반의 객체 생성 방식보다 견고하고 명료하다. 따라서 문법적 설탕이라고 보기보다는 **새로운 객체 생성 매커니즘**이라 보는 것이 좀 더 합당하다.

## 👉 25.2 클래스 정의

클래스는 `class` 키워드를 사용하여 정의한다. 클래스 이름은 생성자 함수와 마찬가지로 파스칼 케이스를 사용하는 것이 일반적이다.

클래스는 함수와 마찬가지로 표현식으로 정의할 수 있으며, 이는 클래스가 값으로 사용할 수 있는 일급 객체라는 것을 의미한다. 따라서 다음과 같은 특징을 갖는다.

- 무명의 리터럴로 생성할 수 있다. 즉 런타임에 생성이 가능하다.

- 변수나 자료구조에 저장 가능하다..

- 함수의 매개변수로 전달할 수 있다.

- 함수의 반환값으로 사용할 수 있다.

---

클래스 몸체에서 정의할 수 있는 메서드는 `constructor`(생성자), 프로토타입 메서드, 정적 메서드 세 가지가 있다.

```javascript
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}

// 프로토타입 메서드
sayHi() {
console.log(`Hi! My name is ${this.name}`);
}

// 정적 메서드
static sayHello() {
console.log("Hello!");
}
}

const me = new Person("Lee");

// 인스턴스의 프로퍼티 참조
console.log(me.name); // Lee
// 프로토타입 메서드 호출
me.sayHi(); // Hi! My name is Lee
// 정적 메서드 호출
Person.sayHello(); // Hello!
```

클래스와 생성자 함수의 정의 방식을 비교하면 다음과 같다.

![](https://velog.velcdn.com/images/wuzoo/post/684b6601-b86b-4a93-b7cc-3927e934a822/image.png)

## 👉 25.3 클래스 호이스팅

클래스는 함수로 평가된다.

```javascript
class Person {}

console.log(typeof Person); // function
```

클래스 선언문으로 정의한 클래스는 함수 선언문과 같이 소스코드 평가 과정, 즉 런타임 이전에 먼저 평가되어 함수 객체를 생성한다. 이때 클래스가 평가되어 생성된 함수 객체는 생성자 함수로서 호출할 수 있는 함수, 즉 constructor이다.

클래스 선언문은 마치 호이스팅이 발생하지 않는 것처럼 보이나 그렇지 않다.

```javascript
const Person = "";

{
// 호이스팅이 발생하지 않는다면 ''이 출력되어야 한다.
console.log(Person);
// Reference: Cannot access 'Person' before initialization

// 클래스 선언문
class Person {}
}
```

변수 호이스팅이 발생하지 않는다면 해당 스코프에 `Person` 식별자가 등록되지 않았으므로 전역 변수의 Person을 출력하겠지만, 클래스 선언문이 호이스팅이 발생하여 아직 초기화되지 않았다는 **"일시적 사각지대"**를 의미하는 문구가 출력된다.

var, let, const, function, class 키워드를 사용하여 선언된 모든 식별자는 호이스팅된다.

## 👉 25.4 인스턴스 생성

클래스는 생성자 함수이며 항상 `new` 연산자와 함께 호출되어 인스턴스를 생성한다.

```javascript
class Person {}

// 인스턴스 생성
const me = new Person();
console.log(me); // Person {}
```

클래스는 인스턴스를 생성하는 것이 유일한 존재 이유이므로, `new` 연산자와 함께 호출하지 않으면 에러가 발생한다.

```javascript
class Person {}

const me = Person();
// TypeError: Class constructor Person cannot be invoked without 'new'
```

## 👉 25.5 메서드

### 25.5.1 constructor

`constructor`는 인스턴스를 생성하고 초기화하기 위한 특수한 메서드다. constructor는 이름을 변경할 수 없다.

```javascript
class Person {
// 생성자
constructor(name) {
this.name = name;
}
}
```

클래스가 생성한 인스턴스의 내부를 들여다보자.

```javascript
// 인스턴스 생성
const me = new Person("Lee");
console.log(me);
```

![](https://velog.velcdn.com/images/wuzoo/post/25f04bf1-4a02-46e1-b5da-37fa9de3973f/image.png)

Person 클래스의 constructor 내부에서 this에 추가한 name 프로퍼티가 클래스가 생성한 인스턴스의 프로퍼티로 추가된 것을 확인할 수 있다. 즉, 생성자 함수와 마찬가지로 constructor 내부에서 this에 추가한 프로퍼티는 인스턴스 프로퍼티가 된다. constructor 내부의 this는 생성자 함수와 마찬가지로 클래스가 생성한 인스턴스를 가리킨다.

인스턴스를 생성할 때 클래스 외부에서 인스턴스 프로퍼티의 초기값을 전달하려면 다음과 같이 `constructor`에 매개변수를 선언하고 인스턴스를 생성할 때 초기값을 전달한다. 이때 초기값은 constructor의 매개변수에게 전달된다.

### 25.5.2 프로토타입 메서드

클래스 몸체에 정의한 메서드는 생성자 함수에 의한 객체 생성 방식과는 다르게 클래스의 `prototype` 프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 된다.

```javascript
class Person {
// 생성자
constructor(name) {
this.name = name;
}

// 프로토타입 메서드
sayHi() {
console.log(`Hi! My name is ${this.name}`);
}
}

const me = new Person("Lee");
me.sayHi(); // Hi! My name is Lee
```

### 25.5.3 정적 메서드

정적<sup>static</sup> 메서드는 인스턴스를 생성하지 않아도 호출할 수 있는 메서드를 말한다.

생성자 함수의 경우 정적 메서드를 생성하기 위해서는 다음과 같이 명시적으로 생성자 함수에 메서드를 추가해야 한다.

```javascript
// 생성자 함수
function Person(name) {
this.name = name;
}

// 정적 메서드
Person.sayHi = function () {
console.log("Hi!");
};

Person.sayHi(); // Hi!
```

클래스에서는 메서드에 `static` 키워드를 붙이면 정적 메서드가 된다.

```javascript
class Person {
// ...
static sayHi() {
console.log("Hi");
}
}
```

정적 메서드는 인스턴스로 호출할 수 없다. 인스턴스의 프로토타입 체인 상에는 클래스가 존재하지 않기 때문에 인스턴스로 클래스의 메서드를 상속받을 수 없다.

```javascript
// 인스턴스 생성
const me = new Person("Lee");
me.sayHi(); // TypeError: me.sayHi is not a function
```

### 25.5.4 정적 메서드와 프로토타입 메서드의 차이

두 메서드의 차이는 다음과 같다.

1. 정적 메서드와 프로토타입 메서드는 자신이 속해 있는 프로토타입 체인이 다르다.
2. 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다.
3. 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다.

```javascript
class Square {
// 정적 메서드
static area(width, height) {
return width * height;
}
}

console.log(Square.area(10, 10)); // 100
```

정적 메서드 area는 2개의 인수를 전달받아 면적을 계산한다. 이때 인스턴스 프로퍼티를 참조하지 않는다. 만약 인스턴스 프로퍼티를 참조해야 한다면 프로토타입 메서드를 사용해야 한다.

```javascript
class Square {
constructor(width, height) {
this.width = width;
this.height = height;
}

// 프로토타입 메서드
area() {
return this.width * this.height;
}
}

const square = new Square(10, 10);
console.log(square.area()); // 100
```

22절 "함수 호출 방식과 this 바인딩"에서 살펴보았듯이 메서드 내부의 `this`는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체, 즉 메서드 이름 앞의 마침표(.) 연산자 앞에 기술한 객체에 바인딩된다.

위 예제의 경우 `square` 객체로 프로토타입 메서드 `area`를 호출했기 때문에 area 내부의 this는 square 객체를 가리킨다.

정적 메서드는 클래스로 호출해야 하므로 정적 메서드 내부의 this는 인스턴스가 아닌 클래스를 가리킨다. 즉, 프로토타입 메서드와 정적 메서드 내부의 this 바인딩이 다르다.

메서드 내부에서 인스턴스 프로퍼티를 참조할 필요가 있다면 `this`를 사용해야 하므로 프로토타입 메서드를 사용해야 하지만, 인스턴스 프로퍼티를 참조해야 할 필요가 없다면 this를 사용하지 않게 된다.
따라서 this를 사용하지 않는다면 프로토타입 메서드는 인스턴스를 생성한 후 인스턴스로 호출해야 하므로 this를 사용하지 않는 정적 메서드로 정의하는 것이 좋다.

### 25.5.5 클래스에서 정의한 메서드의 특징

1. `function` 키워드를 생략한 메서드 축약 표현을 사용한다.
2. 객체 리터럴과는 다르게 클래스에 메서드를 정의할 때는 콤마(,)가 필요 없다.
3. 암묵적으로 `strict mode`로 실행된다.
4. `for ... in`문이나 `Object.keys` 메서드 등으로 열거할 수 없다. 즉, 프로퍼티의 열거가 불가능하며 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false다.
5. 내부 메서드는 [[Constructor]]를 갖지 않는 non-constructor다. 따라서 `new` 연산자와 함께 호출할 수 없다.
Loading