[JavaScript 18] 클래스의 고급 기능

김헤일리·2022년 12월 5일
1

JavaScript

목록 보기
19/20
  • 클래스라는 문법은 객체를 더 안전하고 효율적으로 생성하기 위해 만들어진 문법이다.
  • class를 통해 원하는 구조의 객체 틀을 짜놓고, 비슷한 모양의 객체를 공장처럼 찍어낼 수 있다.
    • 쉽게 생각해서 클래스 = 붕어빵 기계, 그리고 객체 = 붕어빵 으로 보면 된다.

1. 상속

  • "상속 (inheritance)" 은 클래스의 선언 코드를 중복해서 작성하지 않도록 함으로써 코드의 생산 효율을 올리는 문법을 의미한다.
    • 기본 형태: class 클래스 이름 extends 부모클래스 이름 { }

    • 상속은 이름처럼 어떤 클래스가 가지고 있는 유산 (속성과 메소드) 을 다른 클래스에게 물려주는 형태로 사용한다.

      • 이때 유산을 주는 클래스를 "부모 클래스 (parent class)" , 유산을 받는 클래스를 "자식 클래스 (child class)" 라고 한다.

      • 예시:

<script>
    class Rectangle {
      // 1. 사각형을 정의하는 클래스 생성
      constructor(width, height) {
        this.width = width;
        this.height = height;
      }

      getPerimeter() {
        return 2 * (this.width + this.height);
      }
      // 2. 사각형의 둘레를 구할 수 있는 함수를 선언한다.
      // 2-1. 해당 함수는 Rectangle의 함수형 속성인 메소드이다.

      getArea() {
        return this.width * this.height;
      }
      // 3. 사각형의 넓이를 구할 수 있는 함수 또한 메소드로 추가해둔다.
    }

    class Square extends Rectangle {
    // 4. Square라는 새로운 클래스를 생성할 때, Rectangle로부터 메소드를 상속 받는다.
      constructor(length) {
        super(length, length);
        // 5. 부모의 생성자 함수 (Rectangle 클래스의 constructor)를 호출한다.
        // 5-1. super()를 이용해서 부모의 생성자 함수를 호출하면, square의 매개변수인 "length"가 Rectangle의 width, height와 동일한 속성이 된다.
      }
    }

    const square = new Square(20, 20);
    console.log(`정사각형의 둘레: ${square.getPerimeter()}`);
    console.log(`정사각형의 넓이: ${square.getArea()}`);
    // 6. Square 클래스의 경우, getPerimeter(), getArea() 메소드를 따로 선언하지 않았어도 상속 받았기 때문에 사용할 수 있다.
  </script>


2. private 속성과 메소드

  • 클래스 사용자가 클래스의 속성이나 메소드를 의도하지 않은 방향으로 사용하는 것을 막아 클래스의 안정성을 확보하기 위해 나온 문법이다.
    • 기본 형태:
      class 클래스 이름{
        #속성 이름
        #메소드 이름 () {}
      }
    • 속성과 메소드 이름 앞에 "#"을 붙인다.
    • private 속성은 사용하기 전에 미리 외부에 어떤 속성을 private 속성으로 사용하겠다고 선언해줘야 한다.
    • 예시:
<script>
    class Square {
      #length;
      // 1. 미리 특정 속성 (여기선 length) 을 private 속성으로 사용하겠다고 선언한다.

      constructor(length) {
        if (length <= 0) {
          throw "길이는 0보다 커야 합니다.";
          // 2. 다른 사용자가 의도와 다르게 코드를 사용하지 못 하도록 예외 처리를 하고,
        }
        this.#length = length;
        // 2-1. constructor의 (length)가 private 속성으로 지정한 #length와 같다는 것을 명시한다.
      }
      getPerimeter() {
        return 4 * this.#length;
      }

      getArea() {
        return this.#length * this.#length;
      }
    }

    const square = new Square(10);
    console.log(`정사각형의 둘레: ${square.getPerimeter()}`);
    console.log(`정사각형의 넓이: ${square.getArea()}`);
</script>
  • 만약 여기서 사용자가 square의 length 속성을 변경해도, 클래스 내부에서 사용하고 있는 속성은 #length 속성이기 때문에 결과에 영향을 주지 않는다.

  • 클래스 외부에서 #length 속성을 사용해 값을 변경하려고 해도, 외부에서 변경할 수 없다는 점 때문에 에러 메세지가 출력됩니다.

    • Uncaught SyntaxError: Private field '#length' must be declared in an enclosing class


3. 게터(getter)와 세터(setter)

  • private 속성 사용 시, 외부에선 해당 속성에 아예 접근할 수 없는 문제가 발생한다.
  • 위의 예시를 기준으로, square 객체의 length 속성이 몇인지 확인할 수도 없고, length 속성을 변경하고 싶어도 변경할 수 없다.
  • 그래서 상황에 따라서 속성을 읽고 쓸 수 있는 메소드인 게터와 세터를 만들어서 제공한다.
    • 접근자 프로퍼티의 본질은 함수인데, 이 함수는 값을 획득(get)하고 설정(set)하는 역할을 담당한다.
    • 외부 코드에서는 함수가 아닌 일반적인 프로퍼티처럼 보인다.
    • 예시:
<script>
    class Square {
      #length;
      // 1. 미리 특정 속성 (여기선 length) 을 private 속성으로 사용하겠다고 선언한다.

      constructor(length) {
        this.setLength(length);
      }

      setLength(value) { // 속성에 걊을 지정하는 메소드를 세터라고 한다.
        if (value <= 0) {
          throw "길이는 0보다 커야 합니다.";
          // 2. 다른 사용자가 의도와 다르게 코드를 사용하지 못 하도록 예외 처리를 하고,
        }
        this.#length = value;
        // 2-1. constructor의 (length)가 private 속성으로 지정한 #length와 같다는 것을 명시한다.
      }

      getLength(value) { // 속성값을 확인할 때 사용하는 메소드를 게터라고 한다.
        return this.#length;
      }
	

      getPerimeter() {
        return 4 * this.#length;
      }
      getArea() {
        return this.#length * this.#length;
      }
    }

    const squareA = new Square(10);
    console.log(`한 변의 길이는 ${squareA.getLength()}입니다.`);
	console.log(`둘레: ${squareA.perimeter}`);
	console.log(`넓이: ${squareA.area}`)
	// 속성을 사용하는 형태로 사용하면 자동적으로 게터와 세터가 호출된다.

    square.setLength(-10);
</script>
  • 이렇게 getter와 setter 메서드를 구현하면 객체엔 fullName이라는 '가상’의 프로퍼티가 생긴다.
  • 가상의 프로퍼티는 읽고 쓸 순 있지만 실제로는 존재하지 않는다.


4. static (정적) 속성과 메소드

  • 프레임워크 개발자들은 더 효율적으로 프레임워크를 개발할 수 있게 다양한 패턴을 고안한다.

    • 이러한 패턴을 "디자인 패턴" 이라고 부른다.
  • 여러 디자인 패턴을 활용하기 위해서 문법들이 추가되는데, 비교적 최근에 추가된 문법으로는 "static 속성""static 메소드" 가 있다.

    • 기본 형식:
      class 클래스 이름 {
        static 속성 =static 메소드 () {
        }
      }
  • static 속성과 메소드는 인스턴스를 만들지 않고 사용할 수 있는 속성과 메소드다.

  • prototype 이 아닌 클래스 함수 자체에 메서드를 설정하는 것을 정적 메소드라고 부른다.

  • 일반적인 변수와 함수처럼 사용이 가능하기 때문에 클래스 이름 뒤에 점을 찍고 사용하고, 클래스 안에서 static 키워드를 붙여 만들 수 있다.

    • 예시:
<script>
    class Square {
      #length;
      static #counter = 0; // private 속성과 static 속성은 한번에 적용될 수 있다.
      static get counter() {
        return Square.#counter;
      }

      constructor(length) {
        this.length = length;
        Square.#counter += 1;
      }

      static perimeterOf(length) {
        return length * 4;
      }

      static areaOf(length) {
        return length * length;
      }

      get length() {
        return this.#length;
      }
      get perimeterOf() {
        return this.#length * 4;
      }
      get area() {
        return this.#length * this.#length;
      }

      set length(length) {
        if (length <= 0) {
          throw "길이는 0보다 커야 합니다.";
        }
        this.#length = length;
      }
    }

    const squareA = new Square(10);
    const squareB = new Square(10);
    const squareC = new Square(10);
    console.log(`지금까지 생성된 Square 인스턴스는 ${Square.counter}개입니다.`);
    console.log(
      `한 변의 길이가 20인 정사각형의 둘레는 ${Square.perimeterOf(20)}입니다.`
    );
    console.log(
      `한 변의 길이가 30인 정사각형의 넓이는 ${Square.areaOf(30)}입니다.`
    );
</script>
  • 변수와 함수를 클래스 내부에 작성하면:
    • 어떤 속성과 함수가 클래스 내부에 귀속되어 있다는 것을 명시적으로 나타낼 수 있다.
    • private 특성과 게터, 세터를 부여해서 조금 더 안전한 변수와 함수로 사용할 수 있다.


5. 오버라이드

  • 부모가 갖고 있는 함수를 자식에서 다시 선언해서 덮어쓰는 것을 "오버라이드"라고 한다.
    • 클래스를 상속받은 자식의 내부에서 부모에 있던 메소드와 같은 이름의 메소드를 만든다면 이것을 "오버라이드 했다"라고 표현한다.
    • 예시:
<script>
    class LifeCyle {
      // 1. 새로운 부모 클래스를 생성
      call() {
        // 2. 부모 클래스 LifeCyle 내부에 있는 함수로서, 호출 시 a,b,c 함수를 각각 추가로 출력한다.
        this.a();
        this.b();
        this.c();
      }

      a() {
        console.log("a() 메소드를 호출합니다.");
      }
      b() {
        console.log("b() 메소드를 호출합니다.");
      }
      c() {
        console.log("c() 메소드를 호출합니다.");
      }
    }

    new LifeCyle().call();
    // 3. 부모 클래스를 호출할 경우, 부모 클래스 내부에 작성되어 있는 내용이 동일하게 출력된다.
    /* 
    a() 메소드를 호출합니다. a.html:18
    b() 메소드를 호출합니다. a.html:21 
    c() 메소드를 호출합니다. a.html:24
    */

    class Child extends LifeCyle {
      // 4. 자식 클래스를 선언하고, extends 키워드를 사용해서 LifeCycle 클래스 내부에 있는 함수를 사용한다.
      a() {
        console.log("자식의 a() 메소드입니다.");
        // 5. 자식 클래스 내부에서 부모 클래스 내부에 있던 함수를 수정한다.
      }
    }

    new Child().call();
    // 6. 자식 클래스 내부에 있는 함수를 호출한다.
    /*
    자식의 a() 메소드입니다. a:html:32
    b() 메소드를 호출합니다. a:html:21
    c() 메소드를 호출합니다. a:html:24
    */
</script>

  • 만약 부모에 있던 메소드의 내용도 사용하고 싶다면 super.메소드() 형태의 코드를 사용한다.
    • 예시:
class Child extends LifeCyle {
  a() {
    super.a();
    // 1. 부모의 a() 메소드를 실행한다.
    console.log("자식의 a() 메소드입니다.");
      }
    }

new Child().call();
// 2. 자식 클래스 내부에 있는 함수를 호출한다.
/*
a() 메소드를 호출합니다. a.html:20
자식의 a() 메소드입니다. a:html:32
b() 메소드를 호출합니다. a:html:23
c() 메소드를 호출합니다. a:html:26
*/

  • 자바스크립트는 Object라는 최상위 클래스를 가지기 때문에 어떤 클래스를 만들어도 자동으로 Object 클래스를 상속받는다.
  • toString() 이라는 이름으로 메소드를 만들면 Object 클래스에 있던 toString() 메소드를 오버라이드한다.
    • 예시:
<script>
    class Pet {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }

      toString() {
        return `이름: ${this.name} \n나이: ${this.age}`;
      }
      // 1. 내부적으로 문자열로 반환되게 만든다
    }

    const pet = new Pet("명랑", 7);
    alert(pet);
    // 2. "명랑", 7 이라는 정보가 객체가 아니라 문자열로 반환된다.

    console.log(pet + "");
    // 3. 콘솔에도 문자열 결합 연산자를 호출할 때도 toString()를 이용해서 바꾼 형태로 출력된다

 </script>
  • 자바스크립트의 alert() 함수는 매개변수로 받은 자료를 문자열로 바꾼 후 출력했다.
  • toString() 메소드를 오버라이드 했기 때문에 문자열로 출력된다.


출처:

profile
공부하느라 녹는 중... 밖에 안 나가서 버섯 피는 중... 🍄

0개의 댓글