JavaScript

[JavaScript] 생성자 함수와 클래스

FRDYtheme 2023. 4. 10. 18:07

생성자 함수는 객체를 생성하기 위한 템플릿 역할을 하는 함수다. 붕어빵 틀이나 거푸집이라고 생각하면 될 듯.

function Person(name, age) { // 거푸집
  this.name = name;
  this.age = age;
}

const person1 = new Person('John', 30); // 팥 붕어빵
const person2 = new Person('Jane', 25); // 슈크림 붕어빵

 

이 생성자 함수를 통해 생성된 객체는 각자 다른 메모리에 저장되어 참조된다. 즉, 키나 값을 바꿔도 되는 고유한 객체가 됨

 

특징

  • 생성자 함수는 첫 글자를 대문자로 쓰는 게 관례라고 한다.
  • 생성자 함수를 통해 생성된 객체는 생성자 함수의 프로토타입을 상속받는데, 인스턴스 메서드를 프로토타입에 추가하면 하나의 메서드를 모두가 공유할 수 있다.
function Person(name, age) { // 생성자 함수
  this.name = name;
  this.age = age;
}

Person.prototype.sayName = function() { // 붕어빵들이 공유하는 인스턴스 메서드1
  console.log(`My name is ${this.name}.`);
};

Person.prototype.sayAge = function() { // 붕어빵들이 공유하는 인스턴스 메서드1
  console.log(`I am ${this.age} years old.`);
};

const person1 = new Person("John", 30); // 팥 붕
const person2 = new Person("Jane", 25); // 슈 붕

person1.sayName(); // "My name is John."
person1.sayAge(); // "I am 30 years old."

person2.sayName(); // "My name is Jane."
person2.sayAge(); // "I am 25 years old."

생성자 함수 내부에 메서드를 프로퍼티로 추가하지 않고 .prototype을 작성해서 프로토타입에 추가하는 이유는

전자로 메서드를 작성하면 객체가 생성될 때마다 메서드도 중복 생성되면서 메모리가 늘어나기 때문. 

 

ES+6 클래스 문법

클래스는 객체를 생성하기 위한 설계도라고 생각하면 된다.

class ClassName {
  constructor(parameter1, parameter2, ...) {
    // 인스턴스 생성 시 초기화할 코드 작성
  }

  method1(parameter1, parameter2, ...) {
    // 메서드 코드 작성
  }

  method2(parameter1, parameter2, ...) {
    // 메서드 코드 작성
  }

  // ...
}

생성자 함수와 마찬가지로, 클래스에는 constructor라는 특수한 메서드가 있는데, 이는 객체가 생성될 때 자동으로 호출.

내부에 객체를 초기화하는 코드를 작성할 수 있다.

 

위에 생성자 함수는 내부에 메서드를 작성하면 생성되는 객체가 메서드를 중복 생성하면서 메모리가 비효율적이지만

 

클래스 문법은 내부에 메서드를 작성하면 ClassName.prototype에 저장되어 생성된 객체가 이를 공유한다.

 

클래스를 통한 객체 생성

const obj = new ClassName(arg1, arg2, ...);

클래스.prototype에 저장된 메서드 호출

obj.method1(arg1, arg2, ...);

 

클래스는 상속을 지원하며, extends 키워드를 사용해 부모 클래스를 상속받을 수 있다.

상속 받은 자식 클래스에서 부모 클래스의 메서드 호출 가능.

class ChildClassName extends ParentClassName {
  constructor(parameter1, parameter2, ...) {
    super(parameter1, parameter2, ...);
    // 자식 클래스에서 초기화할 코드 작성
  }

  method1(parameter1, parameter2, ...) {
    super.method1(parameter1, parameter2, ...);
    // 자식 클래스에서 오버라이드한 메서드 코드 작성
  }
}

 

부모 클래스를 상속한 자식 클래스에서 부모 클래스의 생성자 함수를 호출하기 위해 super 키워드를 작성해야한다.

즉, 부모 클래스의 설정을 불러오는 키워드라고 보면 된다.

 

super로 부모 클래스를 상속받아 그대로 사용하거나 내용을 추가할 수 있다.

 

class Animal { // 부모 클래스 Animal
  constructor(name, age) { // 초기화 함수 constructor
    this.name = name;
    this.age = age;
  }

  speak() { // 부모 클래스 메서드
    console.log(`${this.name} makes a sound.`);
  }
}

class Cat extends Animal { // 자식 클래스 Cat
  constructor(name, age, breed) { // 부모 클래스의 name,age 구조 상속받고 breed 구조 추가
    super(name, age); // 부모 클래스 생성자 호출
    this.breed = breed;
  }

  meow() { // 자식 클래스의 메서드.
    console.log(`${this.name} meows.`);
  }
}

const fluffy = new Cat('Fluffy', 2, 'Persian');
fluffy.speak(); // "Fluffy makes a sound." // 자식 클래스는 부모 클래스의 메서드 사용 가능.
fluffy.meow(); // "Fluffy meows."

 

 


 

생성자 함수와 클래스 문법의 차이점

1. 문법 : 생성자 함수는 함수 선언문으로 작성(화살표 함수 불가능), 클래스는 class 키워드로 작성하며 첫 문자는 대문자 관례.

2. 상속 : 생성자 함수는 프로토타입 체인을 통해 상속을 구현하고, 클래스는 extends 키워드를 사용해 상속을 구현.3. 메서드 : 생성자 함수는 프로토타입 객체에 직접 할당하며, 클래스는 클래스 본문 내부에 메서드를 정의.4. 생성자 : 생성자 함수에서는 생성자 함수의 이름과 동일한 메서드를 정의하여 객체를 초기화, 클래스에서는 constructor 메서드를 사용해 객체를 초기화.5. 변수 호이스팅 : 클래스는 변수 호이스팅이 발생하지 않으며, 생성자 함수는 변수 호이스팅이 발생한다.

/*
	클래스가 호이스팅이 발생하지 않는 이유
    
    클래스는 호이스팅이 되지 않는 것이 아니라 클래스 선언문 자체가 호이스팅이 되기 때문.
    클래스는 let 또는 const와 마찬가지로 블록 스코프를 갖기 때문에
    해당 클래스가 선언된 블록 내에서만 유효하며 클래스 내부에서 선언된 변수나 메서드는
    해당 클래스가 인스턴스화되어야만 사용할 수 있기 때문에, 호이스팅이 발생하지 않는다.
*/

6. 상수 정의 : 클래스에서는 상수를 static 키워드와 함께 정의할 수 있으며, 생성자 함수에서는 상수를 정의할 수 없다.7. 추상화 : 클래스에서는 추상 클래스와 추상 메서드를 지원하며, 생성자 함수는 지원하지 않는다.

/*
	추상 클래스와 추상 메서드는 일반적인 클래스나 메서드와는 다르게
    구체적인 구현이 없이 선언만 되어 있는 것을 말한다.
    즉, 추상 클래스는 인스턴스를 생성할 수 없으며
    추상 메서드는 하위 클래스에서 구현되어야 한다.
    
	예를 들어, 동물 클래스를 추상 클래스로 선언한다면
    각 동물의 종류마다 구현이 달라지기 때문에 인스턴스를 생성할 수 없고
    하위 클래스에서 해당 종류에 맞는 메서드를 구현하도록 강제할 수 있다.
    
    추상 메서드를 사용하여 '소리를 낸다' 메서드를 강제하는 것도 가능.
    이때, 추상 메서드는 하위 클래스에서 해당 메서드를 반드시 구현해야 하므로
    동물 클래스와 같이 인스턴스를 생성할 수 없다.

	따라서 추상 클래스와 추상 메서드는 상속과 다형성을 구현하는 데 매우 유용.
*/

 

// 추상 클래스
class Animal {
  constructor(name) {
    this.name = name;
  }

  // 추상 메서드
  makeSound() {
    throw new Error('Subclass must implement abstract method');
  }
}

// 추상 클래스를 상속받은 하위 클래스
class Dog extends Animal {
  constructor(name) {
    super(name);
  }

  // 추상 메서드를 구현
  makeSound() {
    console.log('멍멍');
  }
}

// 추상 클래스를 상속받은 다른 하위 클래스
class Cat extends Animal {
  constructor(name) {
    super(name);
  }

  // 추상 메서드를 구현
  makeSound() {
    console.log('야옹');
  }
}

// 추상 클래스의 인스턴스를 만들 수 없음
const animal = new Animal('동물'); // Error: Can't instantiate abstract class

// 추상 클래스를 상속받은 하위 클래스의 인스턴스를 생성하여 사용
const dog = new Dog('개');
dog.makeSound(); // 멍멍

const cat = new Cat('고양이');
cat.makeSound(); // 야옹

 

참고로 자바스크립트에서는 클래스를 직접적으로 지원하지 않으므로, 클래스 내부에 추상 메서드가 하나 이상 있다면 해당 클래스는 추상 클래스로 간주할 수 있다. 추상 메서드는 구현부가 없고 메서드 이름과 매개변수만을 선언하며, 자식 클래스에서 반드시 구현해야 한다. 따라서 자식 클래스에서 구현하지 않으면 에러가 발생.