#1. 클래스

1. 클래스의 정의

1) 클래스(class)란?

  • ES6 버전부터 새롭게 추가된 객체 문법이다.
  • Java, C++, python 등 다른 객체지향 언어에서도 필수적인 개념이다.
  • 자바스크립트에서 클래스는 프로토타입을 이용해서 만들어졌지만, ES5의 클래스 의미와는 다른 문법이다.
  • 특정 객체를 생성하기 위해 변수와 메서드를 정의하는 일종의 틀이다. (객체의 설계도 역할을 하는 프로그램 소스)
    • 객체를 정의하기 위한 멤버변수와 메서드(함수)로 구성된다.
  • 하나의 클래스를 통해 동일한 구조를 갖는 객체를 여러개 생성할 수 있다.

 

2) 객체 (Object)

  • 사전적 의미: 어떠한 물건이나 대상
  • 프로그래밍에서의 의미: 프로그램에서 표현하고자 하는 기능을 묶기 위한 단위
    • 객체를 이루는 것은 데이터와 기능이다.
    • 데이터는 변수로 표현된다.
    • 객체안에 표함된 변수를 멤버변수 혹은 프로퍼티라고 한다.
    • 기능은 메서드(함수)로 표현된다.

 

2. 클래스의 기본 형식

  • 클래스의 이름은 명사들의 조합으로 이루어지며, 첫 글자는 대문자로 지정하는 것이 관례이다.
class 클래스이름 {
  // 생성자 -> 멤버변수 선언 및 기타 초기화
  // getter, setter
  // 메서드
};
  • 클래스를 통해 객체를 생성할 때는 new 예약어를 사용한다.
var,let,const 객체이름 = new 클래스이름();

// 객체는 자신에게 부여된 가능을 점(.)을 통해 접근할 수 있다.
객체이름.멤버변수 = 값;
객체이름.메서드();
  • 일반적으로 자바스크립트에서의 객체 선언은 const 키워드를 사용한다.
  • 위와 같이 정의하면 변수는 클래스 안에 정의된 모든 기능을 부여받은 특수한 형태의 변수가 되는데 이를 객체 라고 한다.

 

3. 클래스의 작성 패턴

객체라는 개념은 배열이 같은 종류의 변수들만 그룹화 하는 한계를 벗어나 서로 다른 종류의 변수를 그룹화 하는 것에서 출발해, 변수들간의 관계를 구현하기 위해 메서드를 함께 포함하는 형태로 발전했다.

1) 변수만 정의한 클래스

  • 생성자 함수를 정의하고 생성자 함수 안에서 this 키워드를 통해 객체 안에 탑제될 변수값들을 생성한다.
  • 생성자 함수는 consctructor  라는 키워드(예약어)를 사용한다.
    • 생성자의 이름은 고정이고, 앞에 예약어는 없다.
    • 필요시 파라미터 정의 가능하고, 리턴은 불가능하다.
class Example {
  // 생성자 함수 -> 생성자의 역할은 멤버변수를 정의
  constructor() {
    this.x = null;
  }
};

// 클래스의 객체 생성
const ex1 = new Example();
console.log(ex1);  // Example { x: null }
console.log(ex1.x);  // null

const ex2 = new Example();
console.log(ex2);  // Example { x: null }
console.log(ex2.x);  // null

// 객체의 특성 -> 같은 구조를 갖지만 저장되는 내용은 개별적이다.
ex1.x = "값 넣었다."
console.log(ex1);  // Example { x: '값 넣었다.' }
console.log(ex1.x);  // 값 넣었다

ex2.x = "다른 값 넣었다."
console.log(ex2);  // Example { x: '다른 값 넣었다.' }
console.log(ex2.x);  // 다른 값 넣었다.

 

※ JSON 객체와 차이점

  • class 나 prototype을 통해 생성된 new 객체는 구조는 동일하지만, 각각 독립적인 값을 갖는다.
  • JSON으로 생성된 객체는 싱글톤(그릇이 하나)이므로 단순 복사만으로는 동일한 형식을 갖는 두개의 데이터를 생성한다.
  • 서로 독립적인 데이터를 보유하려면 동일한 JSON 코드를 한번더 작성해야 한다.
const ex = {
  x: "Andy",
  y: "Josh"
};

// 얕은 복사
const ex2 = ex;

console.log(ex);  // { x: 'Andy', y: 'Josh' }
console.log(ex2);  // { x: 'Andy', y: 'Josh' }

// 객체끼리의 대입은 복사가 아닌 참조이므로 원본의 데이터를 변경하면 복사본도 함께 변경된다. (반대도 동일)
ex.x = "Tom";
ex.y = "Juny";

console.log(ex);  // { x: 'Tom', y: 'Juny' }
console.log(ex2);  // { x: 'Tom', y: 'Juny' }

 

2) 메서드만 정의한 클래스

  • 용도나 목적이 같은 메서드들을 별도의 클래스로 묶어둔다.
class Ex {

  // 메서드 정의
  plusMethod(x, y) {
    return x + y;
  }
  minusMethod(x, y) {
    return x - y;
  }
};

// 객체 생성
const e = new Ex();
console.log(e.plusMethod(4,2));  // 6
console.log(e.minusMethod(4,2));  // 2

 

3) 메서드와 멤버변수를 함께 갖는 클래스

  • 멤버변수의 스코프는 클래스 내의 모든 메서드에서 식별 가능하다.
  • 멤버변수는 모든 메서드가 공유하는 전역변수의 개념이 된다.
  • 같은 클래스에 속한 멤버변수나 함수끼리는 this 라는 예약어를 통해서만 접근 가능하다.
class Example {
  // 생성자
  constructor() {
    this.output = null;
  }
  // 메소드 생성	
  method1() {
    console.log(this.output);
  }
  method2() {
    this.output = 1;
  }
  method3() {
    this.output = 2;
  }
};

// 객체 생성
const e = new Example();

// method2를 호출 후 method1을 출력
e.method2();
e.method1();  // 1

// method3을 호출 후 method1을 출력
e.method3();
e.method1();  // 2

 

4) getter, setter

  • 객체를 통한 멤버변수로의 직접 접근이 소스코드 보안에 좋지 않기 때문에 멤버변수에 대한 직접 접근을 제한하려는 목적으로 멤버변수의 이름 앞에 언더바( _ )를 붙인다. (혹은 getter, setter 이름과 구분하기 위해)
  • 멤버변수에 대한 간접적인 할당, 반환을 수행하는 메서드를 별도로 만드는 형태
class User {
  constructor() {
    this._userName = null;
    this._userAge = null;
  }
  
  // 멤버변수 _userName 값을 반환하기 위한 getter 함수
  get userName() {
    return this._userName;
  }
  // 멤버변수 _userName에 값을 할당하기 위한 setter 함수
  set userName(value) {

    // 0, 빈문자열(""), NaN, false, null, undefinde 는 false를 반환
    // 즉, 매개변수로 받는 값 앞에 ! 가 붙으면  false 값을 true로 변환하여 if문 실행
    // 정상적인 값이 아니면 "이름을 입력하세요" 출력
    if(!value) {
      console.log("이름을 입력하세요.");
      return;
    }
    this._userName = value;
  }

  // 멤버변수 _userAge 값을 반환하기 위한 getter 함수
  get userAge() {
    return this._userAge;
  }
  // 멤버변수 _userAge에 값을 할당하기 위한 setter 함수
  set userAge(value) {
    if(!value) {
      console.log("나이를 입력하세요.");
      return;
    }
    this._userAge = value;
  }

  // 기능을 수행할 메서드
  output() {
    console.log(`당신의 이름은 ${this._userName}, 나이는 ${this._userAge}`);
  }
};

// 객체 생성
const you = new User();
// 생성된 객체로 클래스 메소드 호출
you.output();  // 당신의 이름은 null, 나이는 null

// false 값을 매개변수에 대입 -> if문 실행 (!value 가 false를 true로 변환)
you.userName = 0;  // 이름을 입력하세요.
you.userAge = 0;  // 나이를 입력하세요.

// true 값을 대입 -> if문 무시
you.userName = "Andy";
you.userAge = 3
you.output();  // 당신의 이름은 Andy, 나이는 3

 

4. 상속

  • 부모클래스의 기능을 자식클래스에 상속시킨 후 추가적인 기능을 명시하여 원래의 기능을 확장하는 형태.
  • 클래스를 정의할 때 클래스 이름 뒤에 extends 키워드를 쓰고 상속 받을 부모 클래스의 이름을 지정한다.
  • 자식클래스에서 변경시 부모클래스에는 영향이 없다.
class 자식클래스이름 extends 부모클래스이름 {};

 

1) 상속의 기능 확장

  • 부모클래스의 기능을 상속받고, 자식클래스에서 기능을 추가하여 사용할 수 있다.
// 부모클래스 생성
class SayHello {
  // 메서드 생성
  eng() {
    console.log("Hello");
  }
};

// 부모클래스를 상속받는 자식클래스 생성
class SayHelloKor extends SayHello {
  // 자식클래스 메서드 생성
  kor() {
    console.log("안녕");
  }
};

// 자식클래스 객체 생성
const say = new SayHelloKor();

// 자식클래스의 객체에서 부모클래스 메서드 호출
say.eng();  // Hello
// 자식클래스 메서드 호출
say.kor();  // 안녕

 

2) 공통적인 기능 역할을 하는 부모클래스

  • 여러개의 클래스가 포함하는 기능 중 일부가 공통적으로 사용 될 경우 한 클래스에 기능을 넣고, 그 클래스를 상속하여 공유할 수 있다.
  • 부모클래스의 생성자가 파라미터를 통해 초기화 한다면, 상속 받은 자식클래스도 해당된다. 
// 공통 기능을 하는 부모클래스 정의
class Protoss {
  // 모든 객체가 갖는 명사적 특성들을 멤버변수로 정의
  constructor(name, hp, dps) {
    this._name = name;
    this._hp = hp;
    this._dps =  dps;

    console.log("[%s] 체력: %d, 공격력: %d", name, hp, dps);
  };

  // 객체가 수행해야 하는 동작들을 메서드(함수) 형태로 정의
  move(position) {
    console.log(`${this._name} (이)가 ${position} 까지 이동합니다.`);
  };
  attack(target) {
    console.log(`${this._name} (이)가 ${target} (을)를 공격합니다. 데미지: ${this._dps}`);
  }
};

// 부모클래스를 상속 받는 자식클래스 생성
class Zelot extends Protoss {

  // 자식클래스 메서드 생성
  sword(target) {  // sword 메서드가 호출되면 아래 부모클래스의 attack 메서드도 호출
    this.attack(target);
    console.log(">>>>>> 검으로 찌르기");
  }
};

// 부모클래스를 상속 받는 자식클래스 생성
class Dragoon extends Protoss {

  // 자식클래스 메서드 생성
  fire(target) {
    this.attack(target);
    console.log(">>>>>> 원거리 공격");
  }
};

// 부모클래스가 파라미터를 통해 초기화 하기 때문에 자식클래스도 해당된다.
const z1 = new Zelot("질럿1", 300, 20); // 파라미터를 통해 멤버변수 호출
z1.move("본진");
z1.sword("본진");

const z2 = new Zelot("질럿2", 240, 32);
z2.move("멀티");
z2.sword("멀티");

const d1 = new Dragoon("드라군1", 100, 40);
d1.move("본진");
d1.fire("본진");

const d2 = new Dragoon("드라군2", 70, 45);
d2.move("멀티");
d2.fire("멀티");

 

3) 오버라이딩

  • 자식클래스가 상속 받은 부모클래스의 메서드를 재작성 하여 사용하는 형태.
    • 부모클래스를 상속 받은 자식클래스에서 메서드를 추가 하는 것은 기능 확장의 형태이고, 오버라이딩은 부모의 기능을 수정하여 재사용하는 개념이다.
  • 자식 객체를 통해 실행시 자식클래스의 메서드가 우선이 된다.
class Example {
  constructor(para1, para2) {
    this._para1 = para1;
    this._para2 = para2;
  }
  // 부모클래스에서 sample 메서드 생성
  sample(ex) {
    console.log(`${ex},${this._para1}와 ${this._para2}을 부모클래스의 메서드에서 출력`);
  }
};

class Over extends Example {
  // 부모클래스의 메서드를 자식클래스에서 재정의
  sample(ex) {
    console.log(`${ex},${this._para1}와 ${this._para2} 땡긴다.`);
  }
};

const o = new Over("소고기", "곱창");
// 부모클래스에 선언된 sample 메서드 호출시 자식클래스에 재정의된 메서드 호출
o.sample("참치");  // 참치,소고기와 곱창 땡긴다.

 

4) super 키워드

  • this 키워드가 현재 클래스나 상속 받은 부모 클래스의 멤버변수를 가르킨다면 , super 키워드는 부모의 메서드를 오버라이딩 하고 있는 자식 클래스 안에서 부모 메서드의 원래 기능을 호출한다.
  • 자식클래스에서 생성자를 갖는 경우 그 안에서 부모의 생성자를 호출해야 한다. 
class Example {
  constructor(para1, para2) {
    this._para1 = para1;
    this._para2 = para2;
  }

  sample(ex) {
    console.log(`${ex},${this._para1}와 ${this._para2}을 부모클래스의 메서드에서 출력`);
  }
};

class Over extends Example {
  sample(ex) {
    console.log(`${ex},${this._para1}와 ${this._para2} 땡긴다.`);
  }
};

// 부모클래스를 다른 자식클래스에 상속
class Over2 extends Example {
  // super 키워드를 사용해 부모클래스의 원래 기능 호출
  sample(ex) {
    super.sample(ex);
  }
};

const o = new Over("소고기", "곱창");
o.sample("참치");  // 참치,소고기와 곱창 땡긴다.

const o2 = new Over2("소고기", "곱창");
o2.sample("참치");  // 참치,소고기와 곱창을 부모클래스의 메서드에서 출력

 

5. 정적 멤버변수, 정적 메서드

  • 클래스에 속한 변수나 함수에 static 키워드를 사용하면 객체 생성에 상관 없이 클래스 이름을 통해 접근할 수 있는 정적인 기능을 할 수 있다.
  • 각 객체간의 공유 자원이 될 수 있다.
class Customer {
  constructor(name) {
    this._name = name;
  }

  // 모든 객체가 공유하는 정적 멤버변수 정의
  static count = 0;

  // 정적 멤버변수에 대한 관리를 위해서 함수 정의
  in() {
    // 스태틱은 클래스이름. 으로 접근
    Customer.count++;
  }
  out() {
    Customer.count--;
  }
  showState() {
    console.log(`손님의 이름: ${this._name}, 전체 손님 수: ${Customer.count}`);
  }
};

const c1 = new Customer("Andy");
c1.in();
c1.showState();  // 손님의 이름: Andy, 전체 손님 수: 1

const c2 = new Customer("Lucy");
c2.in();
c2.showState();  // 손님의 이름: Lucy, 전체 손님 수: 2

const c3 = new Customer("Josh");
c3.in();
c3.showState();  // 손님의 이름: Josh, 전체 손님 수: 3

c1.out();
c2.out();

c1.showState();  // 손님의 이름: Andy, 전체 손님 수: 1
c2.showState();  // 손님의 이름: Lucy, 전체 손님 수: 1
c3.showState();  // 손님의 이름: Josh, 전체 손님 수: 1

+ Recent posts