본문 바로가기

Languages/JavaScript

[JavaScript] 클래스 (Class)

728x90

 

 

   목차 

1. "클래스"란?
2. 클래스의 기본 문법
3. 클래스의 prototype
4. 생성자 함수 vs 클래스 
5. 클래스 표현식

 

 


클래스.. 너란 녀석.... 미루고...미루고... 미뤄왔다...
클래스를 알려면 생성자 함수, 프로토타입, this를 다 알아야했다.
그리고 난... 난..... class를 쓰고 싶지 않았다...
그저 생성자 함수만 고집해왔다...
타입스크립트를 공부하는데도 클래스에 대한 언급이 있어서
잊을만 하면 자꾸 나오니 안되겠다 싶어서 그냥 제대로 짚고 알고 넘어가야겠다 생각했다.
이 글을 쓰기 위해서 생성자함수, 프로토타입, this에 대한 게시글 작성도 완료했고, 야무지게 공부해왔다.
평소답지 않게 서론이 길었다...ㅎ
드디어! 클래스에 대해서 알아보자!

 

 


 

 

 1. "클래스"란? 

 

클래스는 객체 지향 프로그래밍에서 특정 객체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀로, 객체를 정의하기 위한 상태(멤버 변수)와 메서드(함수)로 구성된다.

 

 


 



 2. 클래스의 기본 문법 

 

class User {
  // 여러 메서드를 정의할 수 있음
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    alert(this.name);
  }

}

// 사용법:
let user = new User("John");
user.sayHi();

 

new User("John")를 호출하면 다음과 같은 일이 일어난다.

 

1. 새로운 객체가 생성된다.

2. 넘겨받은 인수와 함께 constructor가 자동으로 실행됩니다. 이때 인수 "John"이 this.name에 할당된다.*참고) 객체의 기본 상태를 설정해주는 생성자 메서드 constructor()는 new에 의해 자동으로 호출되므로, 특한 절차 없이 객체를 초기화 할 수 있다.

 

이런 과정을 거친 후에 user.sayHi() 같은 객체 메서드를 호출할 수 있다.

 

 


 

 

 3. 클래스의 prototype 

 

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// User가 함수라는 증거
alert(typeof User); // function

 

위 코드에서 내부 작동 원리는 다음과 같다.

1. User라는 이름을 가진 함수를 만든다.

2. 함수 본문은 생성자 메서드 constructor에서 가져온다. (생성자 메서드가 없으면 본문이 비워진 채로 함수가 만들어진다.)

3. sayHi같은 클래스 내에서 정의한 메서드를 User.prototype에 저장한다. 

 

 

new User를 호출해 객체를 만들고 객체의 메서드를 호출하면, 메서드를 prototype 프로퍼티를 통해 가져오는 것이다. 

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// 클래스는 함수이다.
alert(typeof User); // function

// 정확히는 생성자 메서드와 동일하다.
alert(User === User.prototype.constructor); // true

// 클래스 내부에서 정의한 메서드는 User.prototype에 저장된다.
alert(User.prototype.sayHi); // sayHi() { alert(this.name); }

// 현재 프로토타입에는 메서드가 두 개이다.
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi

 

 


 

 

 4. 생성자 함수 vs 클래스 

 

순수 함수(생성자 함수)로 클래스 역할을 하는 함수를 선언하는 방법과 class 키워드를 사용하는 방법의 결과는 거의 같다.

아래의 예시를 통해 비교해보자.

 

 

 1) 생성자 함수로 만들기 

 

// class User와 동일한 기능을 하는 순수 함수를 만들어 보자

// 생성자 함수를 만든다.
function User(name) {
  this.name = name;
}
// 모든 함수의 프로토타입은 'constructor' 프로퍼티를 기본으로 갖고 있기 때문에
// constructor 프로퍼티를 명시적으로 만들 필요가 없다.

// prototype에 메서드를 추가한다.
User.prototype.sayHi = function() {
  alert(this.name);
};

// 사용법
let user = new User("John");
user.sayHi();

 

참고) 위 코드 대신 아래와 같이 써도 된다. 

function User(name) {
  this.name = name;
  
  this.sayHi = function() {
    alert(this.name);
  };
}

let user = new User("John");
user.sayHi();

 

 

 

 2) 클래스로 만들기 

 

class User {
  constructor(name) {
    this.name = name;
  }
  
  sayHi() {
    alert(this.name);
  }
}

let user = new User("John");
user.sayHi();

 

이렇게 보면 진짜 거의 차이가 없어보이는데, 굳이 class를 왜 만들었고, 왜 사용하는지 궁금할 것이다. 

그런데 이 둘 사이에는 아래와 같은 3가지 차이점이 있다. 

 

 

1) class로 만든 함수엔 특수 내부 프로퍼티인 [[IsClassConstructor]]: true가 이름표처럼 붙는다.

 

[[IsClassConstructor]]: true라는 내부 프로퍼티는 JavaScript 엔진이 클래스로 정의된 함수를 구별하기 위해 사용하는 특수한 표시이다.
이 표시는 일반적으로 사용자 코드에서 직접 접근하거나 조작할 수 없다.
이 내부 프로퍼티는 JavaScript 엔진 내부에서 클래스로 정의된 함수를 구분하고 관리하는 데 사용된다.

 

예를 들어, 주어진 클래스가 생성자 함수로 사용되어야 하는지 여부를 판단하는 데 이 내부 프로퍼티가 사용될 수 있다.
이것은 자바스크립트 엔진이 클래스를 올바르게 사용하는 방법을 보장하는 데 도움이 된다.
예를 들어, 클래스의 인스턴스를 생성할 때 new 키워드를 사용하지 않으면 TypeError가 발생한다.
이것은 클래스의 생성자가 항상 [[IsClassConstructor]]: true를 가지고 있으며, 이를 통해 엔진이 클래스를 적절하게 처리할 수 있음을 보장하기 때문이다.

 

따라서 [[IsClassConstructor]]: true가 붙은 클래스는 일반 함수와는 다르게 동작하며, 일반 함수처럼 호출되지 않고 new 키워드와 함께 사용되어야 함을 엔진에게 알려준다.

 

 

2) 클래스에 정의된 메서드는 열거할 수 없다(non-enumerable). 클래스의 prototype 프로퍼티에 추가된 메서드의 enumerable 플래그는 false이다.

 

이와 달리 생성자 함수를 사용한 경우, 해당 메서드는 프로토타입의 enumerable 속성이 true로 설정되어 열거 가능하므로 클래스에서 정의된 메서드와 생성자 함수에서 정의된 메서드 간에 이러한 차이가 발생한다.

for..in으로 객체를 순회할 때, 메서드는 순회 대상에서 제외하고자 하는 경우가 많으므로 이 특징은 꽤 유용하다.

참고로, 클래스 자체는 열거할 수 없지만, 클래스의 인스턴스는 열거 가능한 프로퍼티나 메서드를 가질 수 있다.

 

class MyClass {
  method1() {
    // ...
  }
  
  method2() {
    // ...
  }
}

// MyClass 클래스의 프로토타입에 추가된 메서드(method1, method2)는 열거 가능하다.
console.log(Object.getOwnPropertyNames(MyClass.prototype));
// 출력: ["constructor", "method1", "method2"]

// MyClass 클래스 자체를 열거할 수는 없다.
for (let key in MyClass) {
  console.log(key);
  // 이 루프는 실행되지 않는다.
}

// MyClass 클래스의 인스턴스를 생성한다.
let instance = new MyClass();

// 이 인스턴스의 메서드는 열거 가능하다.
for (let key in instance) {
  console.log(key);
  // 출력: method1, method2
}

 

 

3) 클래스는 항상 엄격 모드로 실행된다(use strict). 클래스 생성자 안 코드 전체엔 자동으로 엄격 모드가 적용된다.

 

class MyClass {
  constructor() {
    // 클래스 생성자 안에 있는 코드는 자동으로 엄격 모드로 실행된다.
    
    // 예시 코드
    this.method1 = function() {
      console.log("Method 1");
    };
  }
  
  // 다른 메서드들...
}

// MyClass 생성자를 호출하여 인스턴스 생성
let instance = new MyClass();

// 인스턴스를 사용하여 메서드 호출
instance.method1();

 

위의 코드에서 constructor 메서드 내부의 모든 코드는 자동으로 엄격 모드로 실행된다.

method1은 생성자 내부에서 정의되었지만, 생성된 인스턴스의 메서드로 사용할 수 있다.

 

 


 

 

 5. 클래스 표현식 

 

함수처럼 클래스도 다른 표현식 내부에서 정의, 전달, 반환, 할당할 수 있다.

클래식 표현식은 아래와 같이 만들 수 있다.

 

let User = class {
  sayHi() {
    alert("안녕하세요.");
  }
};

 

기명 함수 표현식(Named Function Expression)과 유사하게 클래스 표현식에도 이름을 붙일 수 있다.

클래스 표현식에 이름을 붙이면, 이 이름은 오직 클래스 내부에서만 사용할 수 있다.

 

// 기명 클래스 표현식(Named Class Expression)
// (명세서엔 없는 용어이지만, 기명 함수 표현식과 유사하게 동작한다.)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass라는 이름은 오직 클래스 안에서만 사용할 수 있다.
  }
};

new User().sayHi(); // 원하는대로 MyClass의 정의를 보여준다.

alert(MyClass); // ReferenceError: MyClass is not defined, MyClass는 클래스 밖에서 사용할 수 없다.

 

 

 

728x90