본문 바로가기
Javascript

프로그래밍 할 때 주의할 점 객체 상속 개념

by Antonio Bae 2023. 8. 2.

프로그래밍 할때 항상 생각해야 할 점입니다.

1.함수를 통해 프로그램의 흐름을 제어하지 마라!!

-명시적으로 프로그램의 흐름을 제어할 것!

 

2. 어떤 프로그램을 짤 때는 객체화에 대해 항상 생각 해봐야 한다

ex) 어제 만들었던 책 프로그램을 만들 때에도 함수가 아닌! 클래스가 존재 했어야 한다

ex)계산기 만들때도 각 기능들은 객체 안에 넣고 객체가 알아서 계산하게 만들어 봐야함

 

3.입력 받자마자 산출하지 말고 입력은 입력대로, 출력은 출력대로

 

4.주석은 기본으로 시작과 끝을 컴팩트하게 명시해줄것

 

자바스크립트에서 클래스 사용하는 방법을 오늘은 디테일하게 설명드리도록 하겠습니다.

https://poiemaweb.com/es6-class

Class | PoiemaWeb

자바스크립트는 프로토타입 기반(prototype-based) 객체지향형 언어다. 비록 다른 객체지향 언어들과의 차이점에 대한 논쟁들이 있긴 하지만, 자바스크립트는 강력한 객체지향 프로그래밍 능력들을 지니고 있다. 프로토타입 기반 프로그래밍은 클래스가 필요없는(class-free) 객체지향 프로그래밍 스타일로 프로토타입 체인과 클로저 등으로 객체 지향 언어의 상속, 캡슐화(정보 은닉) 등의 개념을 구현할 수 있다. 하지만 클래스 기반 언어에 익숙한 프로그래머들은 혼란을 일으킬 수 있으며 JavaScript를 어렵게 느끼게하는 하나의 ...

poiemaweb.com

class Foo {
  // constructor는 인스턴스의 생성과 동시에 클래스 필드의 생성과 초기화를 실행한다.
  constructor(num) {
    this.num = num;
  }
}

const foo = new Foo(1);

console.log(foo);
 

사실 구현된 모습은 이런 코드입니다.

foo = {num:1, printNum: function(){alert(this.num)}}

여기서 this._name에서 언더바마크는 외부에서 건들지 못하게 형식적으로 막겠다는 뜻

 

 

var Person = (function () {
  // Constructor
  function Person(name) {
    this._name = name;
  }
 

 

클래스는 속성과 메서드로만 이루어져 있습니다.

반드시 constructor 안에 변수 설정이 가능하며 다음과 같은 코드는 문법 에러입니다.

class Foo {
  name = ''; // SyntaxError

  constructor() {}
}
 

static이란?

정적변수입니다.

클래스로 객체를 생성하지 않아도 호출이 가능한 메소드를 뜻합니다.

 

예를들면 파이죠.

파이는 일정한 공식이 있죠 3.141592.......

이러한 것을 math를 통해서 보통 불러오고 new라는 객체를 만들어 쓰지는 않죠.

즉, 정적 프로퍼티는 데이터를 클래스 수준에 저장하고 싶을 때 사용합니다.

쉽게 말하자면 프로그램 전체를 관통해서 공통적으로 사용하는 함수들

 

class Foo {
  x = 1;            // Field declaration
  #p = 0;           // Private field
  static y = 20;    // Static public field
  static #sp = 30;  // Static private field
  // 2019/5 : Chrome 미구현
  // static #sm() {    // Static private method
  //   console.log('static private method');
  // }

  bar() {
    this.#p = 10; // private 필드 참조
    // this.p = 10; // 새로운 public p 필드를 동적 추가한다.
    return this.#p;
  }
}

const foo = new Foo();
console.log(foo); // Foo {#p: 10, x: 1}

console.log(foo.x); // 1
// console.log(foo.#p); // SyntaxError: Undefined private field #p: must be declared in an enclosing class
console.log(Foo.y); // 20
// console.log(Foo.#sp); // SyntaxError: Undefined private field #sp: must be declared in an enclosing class
console.log(foo.bar()); // 10
 

하단에 보시는 내용처럼 이렇게 new Foo 인스턴스로 호출을 할 수 없습니다.

 

클래스의 정적 메소드는 인스턴스로 호출할 수 없다.

이것은 정적 메소드는 this를 사용할 수 없다는 것을 의미한다.

일반 메소드 내부에서 this는 클래스의 인스턴스를 가리키며,

메소드 내부에서 this를 사용한다는 것은 클래스의 인스턴스의 생성을 전제로 하는 것이다.

class Foo {
  constructor(prop) {
    this.prop = prop;
  }

  static staticMethod() {
    /*
    정적 메소드는 this를 사용할 수 없다.
    정적 메소드 내부에서 this는 클래스의 인스턴스가 아닌 클래스 자신을 가리킨다.
    */
    return 'staticMethod';
  }

  prototypeMethod() {
    return this.prop;
  }
}

// 정적 메소드는 클래스 이름으로 호출한다.
console.log(Foo.staticMethod());

const foo = new Foo(123);
// 정적 메소드는 인스턴스로 호출할 수 없다.
console.log(foo.staticMethod()); // Uncaught TypeError: foo.staticMethod is not a function
 

클래스는 속성안에 객체나 함수같은 메소드가 들어가면 안됩니다.

예를 들면 다음과 같이 Foo 클래스 안에 meee라는 속성과 {a:1111,b:2222} 객체 이렇게 들어가는 경우입니다.

이렇습니다.

 

사진 설명을 입력하세요.

얼굴로 표현하자면 이런 코드가 되겠습니다.

<body>
    <script>
        class Face {
            constructor(eye,nose,mouse){
                this.eye = eye;
                this.nose = nose;
                this.mouse = mouse;
                this.drink = {a:water,b:juice}
            }
            eat(){~~~~
            }
            setter(옷을 갈아 입을때/ 달리는 속도 변경할때)
            getter(이름설정) 
            
        }
        주민번호() //static 정적변수
    </script>
</body>
</html>
 

 

 

getter와 setter는 함수!

getter

속성값을 갖고 올때

getter는 클래스 필드에 접근할 때마다 클래스 필드의 값을 조작하는 행위가 필요할 때 사용

class Foo {
  constructor(arr = []) {
    this._arr = arr;
  }

  // getter: get 키워드 뒤에 오는 메소드 이름 firstElem은 클래스 필드 이름처럼 사용된다.
  get firstElem() {
    // getter는 반드시 무언가를 반환해야 한다.
    return this._arr.length ? this._arr[0] : null;
  }
}

const foo = new Foo([1, 2]);
// 필드 firstElem에 접근하면 getter가 호출된다.
console.log(foo.firstElem); // 1
 

setter?

속성값을 변경할때 혹은 값을 주는 행위

setter는 클래스 필드에 값을 할당할 때마다 클래스 필드의 값을 조작하는 행위가 필요할 때 사용하며,

값을 줄때는 반드시 검증 하는 로직을 작성해야 합니다.

setter를 쓸때는 무조건 한정치가 있어야 합니다.

사진 설명을 입력하세요.

입출력을 나누는 이유

나누는 이유는 입 출력을 동시에 하는 것이 아니라 입력받은 값을 바로 출력하는 것이 아니라 저장을 하고 출력하는 과정으로 나누는 것이 유지보수에 편하기 때문입니다.

뭐 하나 잘못될 경우 전체를 봐야 하지만, 나눠서 작업했다면 고치기가 훨씬 수월하겠죠?

사진 설명을 입력하세요.

 

getter / setter 나누는 이유는 속성값을 구분하기 위함입니다.

 

상속

클래스 상속(Class Inheritance)은 코드 재사용 관점에서 매우 유용합니다.

 

다음 코드는 Circle이라는 부모 클래스와 Cylinder라는 자식 클래스 를 나타낸거고

자식에 있는 super라는 속성을 부모가 갖고 있는 radius(반지름을 쓰겠다는 코드입니다.

즉, 부모와 자식은 전혀 관계가 없으며, 부모가 가지고 있는 몇몇 속성 값만 쓰겠다는 개념이 부모 자식 개념입니다.

이를 오버라이딩이라 부릅니다.

 

// 부모 클래스
class Circle {
  constructor(radius) {
    this.radius = radius; // 반지름
  }

  // 원의 지름
  getDiameter() {
    return 2 * this.radius;
  }

  // 원의 둘레
  getPerimeter() {
    return 2 * Math.PI * this.radius;
  }

  // 원의 넓이
  getArea() {
    return Math.PI * Math.pow(this.radius, 2);
  }
}

// 자식 클래스
class Cylinder extends Circle {
  constructor(radius, height) {
    super(radius);
    this.height = height;
  }

  // 원통의 넓이: 부모 클래스의 getArea 메소드를 오버라이딩하였다.
  getArea() {
    // (원통의 높이 * 원의 둘레) + (2 * 원의 넓이)
    return (this.height * super.getPerimeter()) + (2 * super.getArea());
  }

  // 원통의 부피
  getVolume() {
    return super.getArea() * this.height;
  }
}

// 반지름이 2, 높이가 10인 원통
const cylinder = new Cylinder(2, 10);

// 원의 지름
console.log(cylinder.getDiameter());  // 4
// 원의 둘레
console.log(cylinder.getPerimeter()); // 12.566370614359172
// 원통의 넓이
console.log(cylinder.getArea());      // 150.79644737231007
// 원통의 부피
console.log(cylinder.getVolume());    // 125.66370614359172

// cylinder는 Cylinder 클래스의 인스턴스이다.
console.log(cylinder instanceof Cylinder); // true
// cylinder는 Circle 클래스의 인스턴스이다.
console.log(cylinder instanceof Circle);   // true
 

getArea 메소드를 보시면 부모와 자식이 사용하는 방식이 다릅니다.

이렇게 재정의 해서 쓰는 것을 오버라이딩이라고 부릅니다.

오버라이딩(Overriding) : 상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의하여 사용하는 방식

나머지 height는 자식에서 새로 만든 속성값인거죠.

getArea() {
    // (원통의 높이 * 원의 둘레) + (2 * 원의 넓이)
    return (this.height * super.getPerimeter()) + (2 * super.getArea());
  }
 

super 키워드

super 키워드는 부모 클래스를 참조(Reference)할 때 또는 부모 클래스의 constructor를 호출할 때 사용

 

// 부모 클래스
class Circle {
...
}

class Cylinder extends Circle {
  constructor(radius, height) {
    // ① super 메소드는 부모 클래스의 constructor를 호출하면서 인수를 전달한다.
    super(radius);
    this.height = height;
  }

  // 원통의 넓이: 부모 클래스의 getArea 메소드를 오버라이딩하였다.
  getArea() {
    // (원통의 높이 * 원의 둘레) + (2 * 원의 넓이)
    // ② super 키워드는 부모 클래스(Base Class)에 대한 참조
    return (this.height * super.getPerimeter()) + (2 * super.getArea());
  }

  // 원통의 부피
  getVolume() {
    // ② super 키워드는 부모 클래스(Base Class)에 대한 참조
    return super.getArea() * this.height;
  }
}

// 반지름이 2, 높이가 10인 원통
const cylinder = new Cylinder(2, 10);
 

① super 메소드는 자식 class의 constructor 내부에서 부모 클래스의 constructor(super-constructor)를 호출. 즉, 부모 클래스의 인스턴스를 생성한다. 자식 클래스의 constructor에서 super()를 호출하지 않으면 this에 대한 참조 에러(ReferenceError)가 발생

class Parent {}

class Child extends Parent {
  // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
  constructor() {}
}

const child = new Child();
 

이것은 super 메소드를 호출하기 이전에는 this를 참조할 수 없음을 의미한다.

② super 키워드는 부모 클래스(Base Class)에 대한 참조이다. 부모 클래스의 필드 또는 메소드를 참조하기 위해 사용

 

자바스크립트는 단일 상속만 허용합니다.

즉, 여러 부모를 두지 않고 한 부모만 둡니다.

 

다음에는 모듈에 대해 알아보겠습니다.

모듈

https://poiemaweb.com/es6-module

Module | PoiemaWeb

모듈이란 애플리케이션을 구성하는 개별적 요소로서 재사용 가능한 코드 조각을 말한다. 모듈은 세부 사항을 캡슐화하고 공개가 필요한 API만을 외부에 노출한다. ES6 모듈은 단 두 개의 키워드 export와 import를 제공한다.

poiemaweb.com