Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

heyday2024 님의 블로그

[JS 문법 5주차(2)] Class -생성, getters & setters 본문

프론트엔드 부트캠프

[JS 문법 5주차(2)] Class -생성, getters & setters

heyday2024 2024. 10. 26. 19:51

1. Class

Javascript에서 왜 class라는 문법(ES6)이 생겼을까?

다른 여러 프로그래밍 언어(c#, c++, java...)는 대개 클래스 기반 프로그래밍 개발기법을 사용함

(상속, 생성자 등...)

 

이런 언어들을 사용했던 개발자들이 자바스크립트로 넘어와서 자꾸 클래스 처럼 개발하려고 하다보니까 JS에서도 그 니즈를 반영한 것.

 

클래스 (Class)

  • 클래스는 설계도에 해당하며, 여러 객체를 만들 수 있는 틀.
  • 이 설계도에는 객체가 가져야 할 특성(속성)과 행동(메서드)이 정의되어 있음.
  • 예를 들어 책상을 만드는 설계도를 보면 어떤 종류의 책상을 만들수 있는지 그리고 그 특징이 무엇인지 알 수 있음. 이런 책상을 만드는 설계도가 클래스임.

인스턴스 (Instance)

  • 인스턴스는 클래스를 기반으로 만들어진 실제 객체를 의미.
  • 같은 클래스로 만들어진 인스턴스라도 서로 다른 속성 값을 가질 수 있음. 예를 들어, 같은 설계도를 이용해 책상을 만들었지만, 각각 다른 색상을 가진 책상 인스턴스들을 만들 수 있음.
  • 즉, 클래스는 하나지만, 여러 개의 인스턴스가 생성될 수 있으며, 각 인스턴스는 클래스에 정의된 특성과 행동을 고유하게 가질 수 있음.

===> Class는 객체를 생성하기 위한 일종의 템플릿이고, Class를 생성하기 위해 'class'라는 키워드를 사용함.

class Person {
  // constructor는 이름을 변경할 수 없어요.
  constructor(name, age) {
    // 이름(name)과 나이(age)가 없으면 사람이 아니죠?
    // new라는 키워드를 이용해서 인스턴스를 만들 때, 기본적으로
    // 넣어야 하는 값들을 의미해요! :)
    // 여기서 말하는 this는 만들어질 인스턴스를 의미한다고 생각해주세요!
    this.name = name;
    this.age = age;
  }

  // 다양한 메소드를 아래와 같이 정의할 수 있어요.
  // 여기서 this.name으로 내부 값을 접근해야 함을 잊지 마세요! :)
  sayHello() {
    console.log(
      `Hello, my name is ${this.name} and I'm ${this.age} years old.`
    );
  }
}

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

// 만든 객체를 토대로 메서드 호출해보기
person1.sayHello(); // 출력: "Hello, my name is Alice and I am 30 years old."
person2.sayHello(); // 출력: "Hello, my name is Bob and I am 25 years old."

- 위 예시: Person이라는 이름의 Class를 만들었고, 그 클래스는 name, age 속성을 가지며 sayHello라는 메소드를 정의함. new라는 키워드로 Person 클래스의 인스턴스를 생성할 수 있음. 그리고 만들어진 인스턴스들은 sayHello라는 메소드를 호출할 수 있음.

 


2. Constructor?

Class 의 생성자 함수!!!

생성자 함수는 객체를 생성할 때 호출되며, 객체를 초기화하는 역할을 함.

  • 'constructor'라는 키워드를 사용함!!

 

class 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(`the modelYear of ${this.modelName} is ${this.modelYear}`);
  }
}

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();

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

- 생성자 함수(constructor)는 modelName, modeYear, type, price를 받아 새로운 인스턴스들을 생성함. (this 필수!!)

 


2. Getter & Setter?

Class에서는 getter와 setter를 사용하여 Class의 속성에 접근 가능!!!

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

모든 객체지향 프로그래밍 언어 ---> getters, setters 이용해서 안전하게 인스턴스의 속성에 접근하고 제어 가능.

- setter를 추가함으로서 외부에서 받아오는 값을 검증해서 내부에서 사용할지를 결정할 수 있음. 즉, 인스턴스를 만들기위해 외부로부터 받아오는 값이 valid한지 검증할 수 있게됨!!!

 

 

class Rectangle{
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

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

  //width를 위한 setter
  set width(value) {
    // 검증 1 : value가 음수이면 오류!
    if (value <= 0) {
      //
      console.log("[오류] 가로길이는 0보다 커야 합니다!");
      return;
    }
    // 검증 2: value가 숫자가 아니면 오류!
    else if (typeof value !== "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;
    }
    // 검증 2: value가 숫자가 아니면 오류!
    else if (typeof value !== "number") {
      console.log("[오류] 세로길이로 입력된 값이 숫자타입이 아닙니다!");
      return;
    }
    this.height = value;
  }


}

//instance 생성
const rec1 = new Rectangle(10, 20);
// const rec2 = new Rectangle(10, 30);
// const rec3 = new Rectangle(15, 20);
// ----> 이런 식으로 직접적인 값 세팅은 보안적으로 위험할 수 있고 검증도 어려움

- set과 get키워드를 사용해서 setters, getters를 다 설정해서 constructor에서 instance만들떄 set, get을 거쳐 외부로 들어온 값을 검증 후 그 값들로 초기화 해주는 코드를 만들었다.

 

하지만, 오류가 뜬다!! 

Maximum call stack size exceeded라는 오류가 뜸.

- 위 코드의 문제점은 무엇일까? 

  1. 맨처음에 new Rectangle에서 10, 20 으로 값을 잘 받아온다.
  2. 그 후, 생성자 함수(constructor)로 각각의 값이 들어온다.
  3. this.height = height; 그리고 this.width = width; 에 따라,  this.height, this.width에 각각의 값을 set하기 위해 set키워드로 만들어놨던 setter함수들로 이동한다.
  4. setter에서 들어온 값의 유효성을 다 검증한 뒤, 검증이 성공적으로 완료되면 this.height = value; 그리고 this.width = value; 에 따라, 각각의 값을 set하려고 한다.
  5. 하지만!!!!!! 이 코드는 결국 constructor에 쓰였던 그 코드부분과 같은 코드로, 각각의 값을 set하기 위해 또 setter들을 거치게 된다.
  6. 계속 이 과정(set 함수 -> 검증 -> set 함수 -> 검증 .......)이 반복되는 무한루프 현상이 생기고, call stack 입장에서는 이 무한 루프때문에 실행 컨텍스트가 계속 생기게 됨
  7. 그래서 Maximum call stack size exceeded라는 오류가 뜬 것임.

 

그래서 생긴 getters와 setters 쓸 때의 약속:

this에 접근하는 프로퍼티 이름 앞에는 항상 underscore(_) 붙여주기!!

 

  • underscore는 프로그래밍 세계에서 private이라는 의미를 많이 가짐
  • 이 인스턴스 내에서만 쓰이기 위한 변수로서 분리하겠다는 뜻.
class Rectangle {
  constructor(width, height) {
    // underscore : private(은밀하고, 감춰야 할 때)
    this._width = width;
    this._height = height;
  }

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

  //width를 위한 setter
  set width(value) {
    // 검증 1 : value가 음수이면 오류!
    if (value <= 0) {
      //
      console.log("[오류] 가로길이는 0보다 커야 합니다!");
      return;
    }
    // 검증 2: value가 숫자가 아니면 오류!
    else if (typeof value !== "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;
    }
    // 검증 2: value가 숫자가 아니면 오류!
    else if (typeof value !== "number") {
      console.log("[오류] 세로길이로 입력된 값이 숫자타입이 아닙니다!");
      return;
    }
    this._height = value;
  }

  getArea() {
    console.log("넓이는 ", this._height * this._width);
  }
}

//instance 생성
// const rec2 = new Rectangle(10, 30);
// const rec3 = new Rectangle(15, 20);
// ----> 이런 식으로 직접적인 값 세팅은 보안적으로 위험할 수 있고 검증도 어려움

const rec1 = new Rectangle(10, 20);
console.log("높이: ", rec1.height, "  너비:", rec1.width); //높이:  20   너비: 10

rec1.getArea(); //넓이는  200

- 수정된 코드(underscore!!!!!!!!!!!!)

 

<getters, setters 이용한 예시 코드>

// [요구사항]
// 1. Car라는 새로운 클래스를 만들되, 처음 객체를 만들 때는
//    다음 네 개의 값이 필수로 입력돼야 합니다!
//    (1) modelName
//    (2) modelYear
//    (3) type : 가솔린(g), 전기차(e), 디젤(d)
//    (4) price
// 2. makeNoise() 메서드를 만들어 클락션을 출력해주세요.
// 2-1. 해당 자동차가 몇년도 모델인지 출력하는 메서드 작성!
// 3. 이후 자동차를 3개 정도 만들어주세요(객체 생성)

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

class Car {
  constructor(modelName, modelYear, type, price) {
    this._modelName = modelName;
    this._modelYear = modelYear;
    this._type = type;
    this._price = price;
  }

  // getters, setters
  get modelName() {
    return this._modelName;
  }
  get modelYear() {
    return this._modelYear;
  }
  get type() {
    return this._type;
  }
  get price() {
    return this._price;
  }

  set modelName(value) {
    if (value.length === 0) {
      console.log("오류 발생 - 모델명이 입력되지 않았습니다");
      return;
    } else if (typeof value === "string") {
      console.log("오류 발생 - 입력된 모델명이 문자형이 아닙니다!");
      return;
    }
    this._modelName = value;
  }
  set modelYear(value) {
    // 유효성 검사
    if (value.length !== 4) {
      console.log("[오류] 입력된 년도가 4자리가 아닙니다.확인해주세요!");
      return;
    } else if (typeof value !== "string") {
      console.log("[오류] 입력된 모델명이 문자형이 아닙니다!");
      return;
    }
    this._modelYear = value;
  }
  set type(value) {
    if (value.length <= 0) {
      console.log("[오류] 타입이 입력되지 않았습니다. 확인해주세요!");
      return;
    } else if (value !== "g" && value !== "d" && value !== "e") {
      // g(가솔린), d(디젤), e(전기차)가 아닌 경우 오류
      console.log("[오류] 입력된 타입이 잘못되었습니다. 확인해주세요!");
      return;
    }

    this._type = value;
  }
  set price(value) {
    if (typeof value !== "number") {
      console.log("[오류] 가격으로 입력된 값이 숫자가 아닙니다. 확인해주세요!");
      return;
    } else if (value < "1000000") {
      console.log("[오류] 가격은 100만원보다 작을 수 없습니다. 확인해주세요!");
      return;
    }

    this._price = value;
  }

  // methods
  makeNoise() {
    console.log(`${this.modelName}: 빵!!!!`);
  }
  printModelYear() {
    console.log(`the modelYear of ${this.modelName} is ${this.modelYear}`);
  }
}

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();

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

// getter 예시1
console.log(car1.modelName);
// setter 예시1
car1.modelName = 1;
console.log(car1.modelName);

 

 

 

유효성 검사는 사용자들이 입력한 값에 대한 오류를 잡아내는 데 필수적임으로, 

최대한 오류를 일으킬 수 있는 데이터들을 많이 걸러주는 것이 들어오는 데이터 관리를 더욱 편리하고 유용하게 하기 위해 매우 중요하다!!!!!!