Class: Class, getter/setter, 정적 메소드

summereuna🐥·2024년 6월 4일

JS 문법 정리

목록 보기
17/20

Class와 관련된 JS의 역사


JavaScript객체 지향 언어이다.
그럼에도 불구하고 객체 지향 언어의 대표적인 문법인 Class 문법ES6(2015)가 되서야 도입되었다.

  • 자바스크립트 기반이 아닌 다른 언어(Java, C# 등)에서는 대게 클래스 기반의 프로그래밍 개발 기법(생성자, 상속 등)을 사용한다.
  • 개발자들이 자바스크립트에서도 클래스 ‘처럼’ 개발하려 노력하다보니 JS에서도 그 니즈를 반영하여 Class 문법이 도입되었다. 😃

1. 클래스와 인스턴스의 일반적 개념


모든 객체 지향 언어가 가지고 있는 클래스와 인스턴스의 일반적 개념을 알아보자.

예시

공장에서 ‘상품’다량으로 생산해내기 위해 ‘설계도’를 만든다.
그리고 그 설계도를 토대로 각각 특징이 있는 ‘상품’을 찍어낸다.

클래스(Class)


클래스(Class)는 학교에서 다양한 종류의 책상을 만드는 설계도와 비슷

  • 이 설계도를 보면 어떤 종류의 책상을 만들 수 있는지,
  • 책상이 가지고 있는 특징(이 특징은 변수-또는 속성과 메서드)은 무엇인지 알 수 있다.

인스턴스(Instance)


인스턴스(Instance)는 이 설계도를 보고 만들어진 실제 상품과 비슷

  • 상품의 모양, 크기, 색상, 재료 등은 모두 설계도에 따라 만들어지며,
  • 이러한 상품들은 모두 다른 인스턴스가 된다.

이처럼 클래스(Class)객체를 만들기 위한 설계도라고 생각할 수 있고,
설계도를 바탕으로 만들어진 실제 객체들인스턴스(Instance)라고 할 수 있다.

2. Class와 Constructor


2-1. Class란


Class는 객체를 생성하기 위한 일종의 템플릿

  • class 키워드를 사용해 Class 생성
//class 키워드로 Person 설계도 만들기
class Person {
	// constructor는 이름을 변경할 수 없다.
    
  constructor(name, age) {
    //생성자 함수에는 객체에 꼭 필요한 속성 넣어주기 <- 이름(name)과 나이(age)
    // new라는 키워드를 이용해서 인스턴스를 만들 때, 기본적으로 넣어야 하는 값들
    
	// 여기서 말하는 this는 만들어질 인스턴스를 의미
    //설계도 따라 만들 실체의 이름 = 외부에서 받아온 이름
    this.name = name;
    this.age = age;
  }

	// 다양한 메소드를 아래와 같이 정의할 수 있다.
	// 내부 값에 접근하려면 this.속성 으로 한다.
  sayHello() {
    console.log(`안녕하세요, 제 이름은 ${this.name} 입니다.`);
  }
  
  printAge() {
    console.log(`나이는 ${this.age}살 이예요.`);
  }
}

//설계도를 통해 인스턴스(실제 사람 객체) 만들기
// 설계도에 근거해서 이름은 "Alice", 나이는 30인 사람 만들어 줘
// 설계도에 근거해서 이름은 "Bob", 나이는 25인 사람 만들어 줘
const person1 = new Person("Alice", 30);
const person2 = new Person("Bob", 25);


// 만든 객체를 토대로 메서드 호출하기
person1.sayHello(); // 출력: "안녕하세요, 제 이름은 Alice 입니다."
person2.sayHello(); // 출력: "안녕하세요, 제 이름은 Alice 입니다."

person1.printAge(); // 출력: "나이는 30살 이예요."


// 만들어야 하는 대상이 많을 경우, 데이터가 배열 안에 있다면
// Array.forEach()로 새로운 인스턴스 생성할 수도 있다.
  • 위 코드에서 Person Class는 name과 age 속성을 가지고 있으며, sayHello, printAge 메소드를 정의
  • new 키워드를 사용하여 Person Class의 인스턴스를 생성하고, sayHello, printAge 메소드를 호출

2-2. Constructor란


Constructor는 Class의 생성자 함수

  • 생성자 함수는 객체를 생성할 때 호출
  • 객체를 초기화하는 역할
  • constructor 키워드를 사용하여 정의
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayHello() {
    console.log(`안녕하세요, 제 이름은 ${this.name} 입니다.`);
  }
  
  printAge() {
    console.log(`나이는 ${this.age}살 이예요.`);
  }
}

const person1 = new Person("Alice", 30);
const person2 = new Person("Bob", 25);


person1.sayHello();
person2.sayHello();
person1.printAge();
  • 위 코드에서 constructor 메소드는 name과 age를 인자로 받아
    this.namethis.age 속성을 초기화

2-3. 클래스 연습하기


요구사항

  1. Car라는 새로운 클래스를 만들되, 처음 객체를 만들 때는 다음 네 개의 값이 필수로 입력되어야 한다.
    • modelName
    • modelYear
    • type
    • price
  2. 메서드
    • makeNoise() 메서드를 만들어 클락션을 출력해라.
  3. 이후 자동차를 3개 정도 만들어라.
class Car {
  //생성자 함수로 Car 클래스에 속성 넣기
  constructor(modelName, modelYear, type, price) {
    this.modelName = modelName;
    this.modelYear = modelYear;
    this.type = type;
    this.price = price;
  }

  // 메서드 만들기
  makeNoise() {
    console.log(
      `${this.modelName}: 빵!`
    );
  }
}

// 자동차 객체 만들기
const car1 = new Car("Sorento", "2023", "e", 5000);
const car2 = new Car("SM5", "1999", "g", 3000);
const car3 = new Car("QM6", "2010", "g", 4500);

// 만든 객체를 토대로 메서드 호출하기
car1.makeNoise();
car2.makeNoise();
car3.makeNoise();
  • new 키워드로 새로운 객체 인스턴스를 만들 때, 필요한 필수 값은 생성자 함수 쪽에서 읽어 왔단 사실 잊지말자!

3. getter와 setter


(예시) 사각형 클래스 만들고, 넓이를 구해보자

// 사각형 클래스 생성
class Rectangle {
  //프로퍼티 생성
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }

  printArea() {
    console.log(`사각형의 넓이는 ${this.height * this.width} 입니다.`);
  }
}

//인스턴스 생성
const rect1 = new Rectangle(10, 20); //넓이 200
const rect2 = new Rectangle(3, 10); //넓이 30

rect1.printArea();
rect2.printArea();
  • 하지만 이것 만으로는 개념적으로 부족!
  • height, width는 외부에서 사용, 세팅 할 수 있어야 하는데,
    height, width가 내부적인 변수이기도 하기 때문에,
    직접적으로 세팅하는 것은 위험하고 검증도 어렵다.

3-1. getter와 setter


대부분의 객체 지향 프로그래밍 언어에서 getter와 setter에 대한 기본적인 개념이 있다.

  • Class를 통해 객체(인스턴스)를 만들면, 그 안에는 constructor에서 정의한 필수 입력 값들인 프로퍼티가 있다.
  • 이 프로퍼티 값에 접근하고 설정하기 위한 메서드가 바로 gettersetter이다.

gettersetter를 사용하여 Class의 속성에 접근할 수 있다.

  • getter속성 값을 반환하는 메소드
  • setter속성 값을 설정하는 메소드

getter와 setter를 통해, 생성한 인스턴스를 정해진 규격 안에서 자유자제로 변경할 수 있다.

class Rectangle {
  constructor(height, width) {
    // ✅ underscore : private(은밀하고, 감춰야 할 때)
    this._height = height;
    this._width = width;
  }

  // width를 위한 getter
  get width() {
    return this._width;
  }

  // width를 위한 setter
  set width(value) {
    // 검증 1 : value가 음수이면 오류!
    if (value <= 0) {
      //
      console.log("[오류] 가로길이는 0보다 커야 합니다!");
      return;
    } else if (typeof value !== "number") { //또는 Number
      console.log("[오류] 숫자를 넣어주세요.");
      return;
    }
    this._width = value;
  }

  // height를 위한 getter
  get height() {
    return this._height;
  }

  // height를 위한 setter
  set height(value) {
    // 검증 1 : value가 음수이면 오류!
    if (value <= 0) {
      //
      console.log("[오류] 세로길이는 0보다 커야 합니다!");
      return;
    } else if (typeof value !== "number") { //또는 Number
      console.log("[오류] 숫자를 넣어주세요.");
      return;
    }
    this._height = value;
  }

  // getArea : 가로 * 세로 => 넓이
  getArea() {
    const area = this._width * this._height;
    console.log(`넓이는 => ${area}입니다.`);
  }
}

// instance 생성
const rect1 = new Rectangle(10, 7);

// 메서드 호출해 넓이 구하기
rect1.getArea();

💡 this._변수명

내부 인스턴스에서 사용하는 this.변수명에는 항상 변수명 앞에 underscore(_)를 붙여야 한다.

  • underscore(_)의 의미는 프로그래밍에서 private하다는 의미를 가진다.
    예를 들어 this._height는 이 인스턴스 내에서만 쓰이기 위한 변수다라는 뜻이다.
  • _를 붙이지 않으면, this.변수명에 외부에서 받아온 값을 세팅하려고 setter 함수 무한으로 호출해 버려서 무한 루프가 발생한다.
  • 그러면 콜스택 입장에서, 콜스택에 쌓이는 실행 컨텍스트가 무한으로 생성되어 버리기 때문에 RangeError: Maximum call stack size exceeded 오류가 뜬다.

3-2. getter, setter 연습


// 클래스 연습해보기!

// [추가 요구사항]
// 1. modelName, modelYear, price, type을 가져오는 메서드
// 2. modelName, modelYear, price, type을 세팅하는 메서드
// 3. 만든 인스턴스를 통해서 마지막에 set 해서 get 하는 로직까지


class Car {
  //생성자 함수로 Car 클래스에 속성 넣기
  constructor(modelName, modelYear, type, price) {
    this._modelName = modelName;
    this._modelYear = modelYear;
    this._type = type;
    this._price = price;
  }

  // getter 메서드: this._변수명 값 반환
  get modelName() {
    return this._modelName;
  }
  get modelYear() {
    return this._modelYear;
  }
  get type() {
    return this._type;
  }
  get price() {
    return this._price;
  }

  // setter 메서드: this._변수명 값에 외부 값 세팅함
  set modelName(value) {
    //유효성 검사
    if (value.length <= 0) {
      console.log("[오류] 입력되지 않았습니다.");
      return;
    }

    if (typeof value !== "string") {
      console.log("[오류] 문자를 넣어주세요.");
      return;
    }

    //검증 완료된 경우에만 setting
    this._modelName = value;
  }

  set modelYear(value) {
    if (typeof value.length === 4) {
      console.log("[오류] 입력된 년도가 4자리가 아닙니다.");
      return;
    }

    if (typeof value !== "string") {
      console.log("[오류] 문자를 넣어주세요.");
      return;
    }

    this._modelYear = value;
  }

  set type(value) {
    if (value.length <= 0) {
      console.log("[오류] 입력되지 않았습니다.");
      return;
    }

    if (value !== "g" && value !== "e" && value !== "d") {
      console.log(
        "[오류] 가솔린(g), 전기차(e), 디젤(d) 중 하나를 입력해주세요."
      );
      return;
    }

    this._type = value;
  }

  set price(value) {
    if (typeof value !== "number") {
      console.log("[오류] 숫자를 넣어주세요.");
      return;
    }

    if (value < 1000000) {
      console.log("[오류] 가격은 100만원 보다 작을 수 없습니다.");
      return;
    }

    this._price = value;
  }

  // 메서드 만들기
  printCar() {
    console.log(
      `${this._modelName}, ${this._modelYear}, ${this._type}, ${this._price}`
    );
  }
}

// 자동차 객체 만들기
const car1 = new Car("Sorento", "2023", "e", 5000);
const car2 = new Car("SM5", "1999", "g", 3000);
const car3 = new Car("QM6", "2010", "g", 4500);

// 만든 객체를 토대로 메서드 호출하기
car1.printCar();
car2.printCar();
car3.printCar();

// ✅ getter 예시
console.log(car1.modelName); //Sorento

// ✅ setter 예시
car1.modelName = "kia";
console.log(car1.modelName); //kia

car1.modelName = 100;
console.log(car1.modelName); //[오류] 문자를 넣어주세요.   //kia

4. 상속 (Inheritance)


상속은 클래스의 기능을 유산으로 내려주는 기능으로, 부모 자식 간의 관계를 생각 할 수 있다.

  • 부모 클래스, 자식 클래스...
    • 동물 클래스 > 개 클래스, 고양이 클래스 등.. 이런식

Class는 상속을 통해 다른 Class의 기능을 물려받을 수 있다.

  • 상속을 받는 Class(자식)를 subclass 또는 derived class라고 한다.
  • 상속을 하는 Class(부모)를 superclass 또는 base class라고 한다.

클래스 끼리의 상속관계를 잘 이해하기 위해서 예시를 살펴보자.

4-1. 상속 예시


class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name}가 말한다.`);
  }
}

const me = new Animal("euna");
me.speak(); //euna가 말한다.

//✅ 하위 클래스 <- 부모 클래스 상속 받아서 만들기
class Dog extends Animal {
  //✅ 부모로 부터 상속 받은 것을 재정의(오버라이딩=덮어 쓰기) 할 수 있다.
  speak() {
    console.log(`${this.name}가 짖는다.`);
  }
}

const yeti = new Dog("Yeti");
yeti.speak(); //Yeti가 짖는다.
  • 위 코드에서 Dog ClassAnimal Class상속받았다.
  • Dog Class에서 speak 메소드💡 오버라이딩하여 Animal Class의 speak 메소드를 덮어쓸 수 있다.

4-2 상속(Inheritance) 연습하기


Car 클래스로부터 상속받아 전기자동차 클래스를 만들어 보자.
전기차이기 때문에 constructor 상에 type은 필요 없다.

class Car {
  //생성자 함수로 Car 클래스에 속성 넣기
  constructor(modelName, modelYear, type, price) {
    this.modelName = modelName;
    this.modelYear = modelYear;
    this.type = type;
    this.price = price;
  }

  // 메서드 만들기
  makeNoise() {
    console.log(`${this.modelName}: 빵!`);
  }

  printModelYear() {
    console.log(`${this.modelName}: ${this.modelYear}년도 모델`);
  }
}

// 자동차 객체 만들기
const car1 = new Car("Sorento", "2023", "e", 5000000);
const car2 = new Car("SM5", "1999", "g", 3000000);
const car3 = new Car("QM6", "2010", "g", 4500000);

// 만든 객체를 토대로 메서드 호출하기
car1.makeNoise();
car2.makeNoise();
car3.makeNoise();

car1.printModelYear();
car2.printModelYear();
car3.printModelYear();

console.log(car1.modelName); // Sorento

// ✅ Car를 상속받는 전기 자동차 클래스 만들기
class ElectronicCar extends Car {
  constructor(modelName, modelYear, price) {
    // ✅ Car에 있는 constructor를 통해 자동차를 만들기 때문에 굳이 안써도 되지만
    // 재정의 필요하면 constructor 써주면 된다.

    //✅ 자식클래스의 constructor가 부모클래스의 constructor와 다를 경우
    // super 키워드를 사용하여
    // 부모의 constructor에게 이렇게 받을 거란거 알려줘야 한다.
    super(modelName, modelYear, "e", price);
    // => 타입은 항상 "e"로 정해져 있다고 알리기
  }
}

const eleCar1 = new ElectronicCar("테슬라", "2023", 9000); // 여기서 자식 컨스트럭터에 맞게 3개만 넣으면 된다.
console.log(eleCar1.modelName); // 테슬라

eleCar1.makeNoise(); //테슬라: 빵!
eleCar1.printModelYear(); //테슬라: 2023년도 모델

super
super 키워드는 객체 리터럴 또는 클래스의 속성에 접근하거나 슈퍼클래스의 생성자를 호출하는 데 사용된다.

전기 자동차의 특성을 고려해서 충전시간도 추가해보자

// Car를 상속받는 전기 자동차 클래스 만들기
class ElectronicCar extends Car {
  constructor(modelName, modelYear, price, chargeTime) {
    // Car에 있는 constructor를 통해 자동차를 만들기 때문에 굳이 안써도 되지만
    // 재정의 필요하면 constructor 써주면 된다.

    //✅ 그리고 super 키워드를 사용하여
    // 부모의 constructor에 이렇게 받을 거란거 알려줘야 한다.
    super(modelName, modelYear, "e", price);
    // => 타입은 항상 "e"로 정해져 있다고 알리기

    // ✅ 부모한테 없는 건 아래 처럼 새로 추가
    this.chargeTime = chargeTime;
  }

  // ✅ 충전시간 구하기 
  getChargeTime() {
    return this.chargeTime;
  }

  // ✅ 충전시간 세팅하기
  setChargeTime(time) {
    this.chargeTime = time;
  }
}

const eleCar1 = new ElectronicCar("테슬라", "2023", 9000, 60); // 여기서 자식 컨스트럭터에 맞게 3개만 넣으면 된다.
console.log(eleCar1.modelName); // 테슬라

eleCar1.makeNoise(); //테슬라: 빵!
eleCar1.printModelYear(); //테슬라: 2023년도 모델

console.log("-------------------");

// ✅ 충전 시간 출력
console.log(eleCar1.getChargeTime()); //60

// ✅ 충전 시간 변경
eleCar1.setChargeTime(20); // eleCar1.chargeTime = 20;
console.log(eleCar1.getChargeTime()); //20

근데 여기선 왜 게터 세터 안 쓴거지...?


5. 정적 메소드 (Static Method)


기본적으로 클래스는 객체를 다량으로 안전하고 정확히 만들기 위해 사용한다.
굳이 인스턴스화 하지 않아도 되는, 즉 객체를 만들지 않아도 되는, 변하지 않는 메소드도 있다.
이런 메소드는 정적 메소드로써 정의하여 클래스 자체를 호출하는 방식으로 사용한다.

  • static 키워드를 사용하여 Class 레벨의 메소드를 정의할 수 있다.

  • 클래스명.정적메소드명();

    • Class 레벨의 메소드는 인스턴스에서 호출할 수 없고,
    • Class 이름으로 직접 호출할 수 있다.

static이라는 말에서 알 수 있듯이, 인스턴스를 만들지 않고 사용할 수 있기 때문에 유틸리티 함수, 정적 속성인 경우 인스턴스 간에 복제할 필요가 없는 데이터(똑같은 것을 공유해서 쓸 때)를 만들 때 사용되곤 한다.

즉, 인스턴스를 만들 필요가 없을 때 사용한다고 이해하면 된다.

//클래스 자체는 일반 클래스 모양인데
//✅ 클래스 안에 든 메소드가 정적인 특징을 가진다.
class Calculator {
  //정적 메소드임을 명시하기 위해 static 키워드 사용
  static add(a, b) {
    return a + b;
  }

  static subtract(a, b) {
    return a - b;
  }
}

//원래 클래스의 메소드에 접근하려면 클래스의 인스턴스를 만들고
//클래스인스턴스명.메소드 이런식으로 접근 하지만
//✅ 정적 메소드의 경우 그냥 Class 레벨에서 호출하면 된다.
//클래스명.정적메소드명();
console.log(Calculator.add(1, 2)); // 3
console.log(Calculator.subtract(3, 2)); // 1
  • 위 코드에서 Calculator Class는 addsubtract 메소드를 정의한다.
  • 두 메소드 모두 Class 레벨에서 호출할 수 있으며, 인스턴스에서 호출할 수 없다.
profile
Always have hope🍀 & constant passion🔥

0개의 댓글