호쌤
호쌤 Just For Fun

[JS] Class

크리에이티브 커먼즈 라이선스 ITPAPER(호쌤,쭈쌤)에 의해 작성된 ≪[JS] Class≫은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.
이 라이선스의 범위 이외의 이용허락을 얻기 위해서는 leekh4232@gmail.com으로 문의하십시오.

[JS] Class

ES6 버전부터 신규로 추가된 객체 설계 문법으로 Prototype이 발전된 형태로 이해하면 됩니다.

Java, C/C++, C# 등의 다른 프로그래밍 언어에서 말하는 Class와 동일한 개념이기 때문에 대부분의 프로그래밍 언어에서 객체지향 개념을 공부하신 경우는 매우 쉽게 접근할 수 있는 개념입니다.

Class,객체가 등장하는 프로그래밍 패러다임

#01. 객체 (Object)

  • 사전적 의미 : 어떠한 물건이나 대상
  • 프로그래밍에서의 의미 : 프로그램에서 표현하고자 하는 기능을 묶기 위한 단위

객체를 구성하는 단위

  • 객체를 이루는 것은 데이터와 기능이다.
  • 데이터는 변수로 표현된다.
    • 객체 안에 포함된 변수를 멤버변수 혹은 프로퍼티라 한다.
  • 기능은 메서드(=함수)로 표현된다.

#02. 클래스 (Class)

객체의 설계도 역할을 하는 프로그램 소스

공장에서 하나의 설계도를 사용하여 여러 개의 제품을 생산할 수 있는 것처럼 하나의 클래스를 통해 동일한 구조를 갖는 객체를 여러 개 생성할 수 있다.

img1 img2

1) 클래스의 가장 기본적인 코드 형식

클래스 이름은 명사들의 조합으로 이루어지며 첫 글자는 대문자로 지정하는 것이 관례이다.

1
2
3
4
5
class 클래스이름 {
    // 생성자 --> 멤버변수 선언 및 기타 초기화
    // getter, setter
    // 메서드
}

2) 클래스를 통한 객체 생성하기

new 예약어를 사용한다.

1
var|let|const 변수이름 = new 클래스이름();

일반적으로 JS에서의 객체 선언은 const 키워드를 사용함.

위와 같이 정의하면 변수는 클래스 안에 정의된 모든 기능을 부여받은 특수한 형태의 변수가 되는데 이를 객체라고 하고, 객체는 자신에게 기능을 점(.)을 통해 접근할 수 있다.

1
2
객체.멤버변수 = ;
객체.메서드();

3) 클래스의 작성 패턴

  1. 변수만 정의
  2. 메서드만 정의
  3. 변수와 메서드를 함께 정의

객체라는 개념은 배열이 같은 종류의 변수들만 그룹화 하는 한계를 벗어나 서로 다른 종류의 변수를 그룹화 하는데서 출발한다. (이 상태를 C언어의 구조체라고 한다.)

그렇게 그룹화 해 놓은 변수들간의 관계를 구현하기 위해 메서드를 함께 포함하는 형태로 발전된 것이다.

4) 변수만 정의한 클래스

생성자 함수를 정의하고 생성자 함수 안에서 this키워드를 통해 객체 안에 탑제될 변수값들을 생성한다.

생성자 함수는 constructor로 이름이 미리 약속되어져 있다.

클래스는 중복 선언될 수 없기 때문에 Jupyter에서 실습할 경우 아래의 코드 블록이 2번 실행되면 에러가 발생하게 된다. (커널 재시작 필요)

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
class MemberClass {
    /** 생성자 함수 */
    constructor() {
        this.userName = "이름";
        this.email = "이메일";
    }
}

var m1 = new MemberClass();
console.log(m1);
console.log(m1.userName);
console.log(m1.email);

var m2 = new MemberClass();
console.log(m2);
console.log(m2.userName);
console.log(m2.email);

m1.userName = "민혁";
m1.email = "mh@gmail.com";

m2.userName = "철수";
m2.email = "cs@gmail.com";

console.log(m1);
console.log(m2);
▶ 출력결과
1
2
3
4
5
6
7
8
MemberClass { userName: '이름', email: '이메일' }
이름
이메일
MemberClass { userName: '이름', email: '이메일' }
이름
이메일
MemberClass { userName: '민혁', email: 'mh@gmail.com' }
MemberClass { userName: '철수', email: 'cs@gmail.com' }

JSON 형식의 객체와 차이

class나 prototype을 통해 new로 생성된 객체는 구조는 동일하지만 각각 독립적인 값을 갖는다.

앞의 예제에서 m1과 m2가 내장하는 멤버변수의 값을 각각 민혁과 철수로 변경했을 때 두 개의 객체는 각각 독립적인 값이 저장된 상태로 존재한다.

JSON으로 생성되는 객체는 싱글톤으로서 존재하므로 단순 복사만으로는 동일한 형식을 갖는 두 개의 데이터를 생성할 수 없다.

서로 독립적인 데이터를 보유하려면 동일한 JSON 코드를 한 번 더 작성해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var m3 = {
    userName: "이름",
    email: "이메일"
};

var m4 = m3; // 객체의 복사가 아닌 참조가 발생한다.

console.log(m3);
console.log(m4);

// 값의 변경
m3.userName = "민수";
m3.email = "ms@gmail.com";

// 객체간의 복사는 서로 영향을 준다.
console.log(m3);
console.log(m4);
▶ 출력결과
1
2
3
4
{ userName: '이름', email: '이메일' }
{ userName: '이름', email: '이메일' }
{ userName: '민수', email: 'ms@gmail.com' }
{ userName: '민수', email: 'ms@gmail.com' }

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
class Calc {
    plus(x, y) {
        return x + y;
    }

    minus(x, y) {
        return x - y;
    }
}

var c = new Calc();
console.log(c.plus(1,2));
console.log(c.minus(10, 5));
▶ 출력결과
1
2
3
5

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

멤버변수의 스코프는 클래스 내의 모든 메서드에서 식별 가능하다. 결국 멤버변수는 모든 메서드가 공유하는 전역 변수의 개념이 된다.

같은 클래스에 속한 멤버변수나 함수끼리는 예약어 this를 통해서만 접근 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class HelloWorld {
    /** 생성자 함수 */
    constructor() {
        // 멤버변수 정의 : 선언만 하고 추후 값을 할당할 경우 null을 할당해 둔다.
        this.message = null;
    }

    sayHello() { console.log(this.message); }
    setEng()   { this.message = "Hello Javascript"; }
    setKor()   { this.message = "안녕하세요. 자바스크립트"; }
}

const hello = new HelloWorld();

// 메서드의 호출
hello.setEng();
hello.sayHello();
hello.setKor();
hello.sayHello();
▶ 출력결과
1
2
Hello Javascript
안녕하세요. 자바스크립트

7) getter, setter

객체를 통한 멤버변수로의 직접 접근이 소스코드 보안에 좋지 않기 때문에 멤버변수에 대한 직접 접근을 제한하려는 목적으로(혹은 getter, setter 이름과 구분을 위한 목적으로) 멤버변수의 이름을 언더바(_)로 시작하게 지정한다.

그 후에 멤버변수에 대한 간접적인 할당, 반환을 수행하는 메서드를 별도로 만드는 형태를 getter, setter라고 한다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class UserClass {
    constructor() {
        this._userName = null;
        this._email = null;
    }

    /** 멤버변수 _userName에 값을 할당하기 위한 setter 함수 */
    set userName(value) {
        if (!value) {
            console.log("userName을 입력하세요.");
            return;
        }
        this._userName = value;
    }

    /** 멤버변수 _userName 값을 반환하기 위한 getter 함수 */
    get userName() {
        return this._userName;
    }

    /** 멤버변수 _email에 값을 할당하기 위한 setter 함수 */
    set email(value) {
        if (!value) {
            console.log("email을 입력하세요.");
            return;
        }
        this._email = value;
    }

    /** 멤버변수 _email 값을 반환하기 위한 getter 함수 */
    get email() {
        return this._email;
    }

    /** 일반적인 기능을 수행하기 위한 메서드 */
    login() {
        if (!this.userName || !this.email) {
            console.log("userName이나 email을 확인하세요.");
            return;
        }

        // 같은 클래스의 getter를 통해 멤버변수 값을 반환받아 출력에 활용한다.
        console.log("로그인되었습니다. >> userName=%s, email=%s", this.userName, this.email);
    }
}

const user = new UserClass();
user.login();

user.userName = "";
user.email = "";

user.userName = "helloworld";
user.email = "hello@world.com";
user.login();
▶ 출력결과
1
2
3
4
userName이나 email을 확인하세요.
userName을 입력하세요.
email을 입력하세요.
로그인되었습니다. >> userName=helloworld, email=hello@world.com

03. 클래스 상속

어떤 클래스의 기능을 다른 클래스에 상속시킨 후 추가적인 기능을 명시하여 원래의 기능을 확장하는 방법.

class를 정의할 때 클래스 이름 뒤에 extends 키워드를 명시하고 상속받고자 하는 부모 클래스의 이름을 지정한다.

1) 기능의 확장으로서의 상속

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/** 기본 기능을 갖는 클래스 */
class SayHello {
    eng() {
        console.log("Hello Javascript")
    }
}

/** 기본 기능을 확장하는 클래스 */
// 부모의 모든 기능을 상속 받으며, 추가적으로 자신이 구현하는 기능도 사용할 수 있다.
class SayHelloWorld extends SayHello {
    kor() {
        console.log("안녕하세요 자바스크립트");
    }
}

const say = new SayHelloWorld();
say.eng(); // 부모에게 상속받는 기능 사용하기
say.kor(); // 자신이 확장한 기능 사용하기
▶ 출력결과
1
2
Hello Javascript
안녕하세요 자바스크립트

2) 여러 클래스간의 공통 기능을 모아 놓는 의미로서의 상속

여러 개의 클래스가 포함하는 기능 중 일부가 동일한 경우 각 클래스로부터 공통되는 부분을 독립적인 클래스로 추출하고 그 클래스를 상속하여 공유하는 처리 기법

공통기능을 정의하는 부모 클래스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Protoss {
    /** 모든 객체가 갖는 명사적 특성들을 멤버변수로 정의 */
    constructor(name, hp, dps) {
        this._name = name;    // 이름
        this._hp = hp;        // 체력(health point)
        this._dps = dps;      // 초당공격력(damage per Second)
        console.log("[%s] 체력: %d, 공격력: %d", name, hp, dps);
    }

    /** 객체가 수행해야 하는 동작들을 함수 형태로 정의 */
    move(position) {
        console.log("%s(이)가 %s까지 이동합니다.", this._name, position);
    }

    attack(target) {
        console.log("%s(이)가 %s(을)를 공격합니다. 데미지: %d", this._name, target, this._dps);
    }
}

부모를 상속받는 자식 클래스(들) 정의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Zealot extends Protoss {
    sword(target) {
        // 부모에게 상속받은 기능은 자식에게는 자신의 것으로 인식된다.
        this.attack(target);
        console.log(" >>>>>>> 검으로 찌르기");
    }
}

class Dragoon extends Protoss {
    fire(target) {
        // 부모에게 상속받은 기능은 자식에게는 자신의 것으로 인식된다.
        this.attack(target);
        console.log(" >>>>>>> 원거리 공격");
    }
}

자식 클래스에 대한 객체 생성

부모가 생성자 파라미터를 통해 초기화를 수행하고 있다면 그 생성자는 자식 클래스에게도 상속된다.

그러므로 자식 클래스를 통한 객체 생성시에도 부모가 요구하는 생성자 파라미터를 전달해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var z1 = new Zealot("질럿1", 300, 20);
z1.move("본진");
z1.sword("본진");

var z2 = new Zealot("질럿2", 300, 25);
z2.move("멀티");
z2.sword("멀티");

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

var d2 = new Dragoon("드라군2", 200, 35);
d2.move("멀티");
d2.fire("멀티");
▶ 출력결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[질럿1] 체력: 300, 공격력: 20
질럿1(이)가 본진까지 이동합니다.
질럿1(이)가 본진(을)를 공격합니다. 데미지: 20
 >>>>>>> 검으로 찌르기
[질럿2] 체력: 300, 공격력: 25
질럿2(이)가 멀티까지 이동합니다.
질럿2(이)가 멀티(을)를 공격합니다. 데미지: 25
 >>>>>>> 검으로 찌르기
[드라군1] 체력: 250, 공격력: 40
드라군1(이)가 본진까지 이동합니다.
드라군1(이)가 본진(을)를 공격합니다. 데미지: 40
 >>>>>>> 원거리 공격
[드라군2] 체력: 200, 공격력: 35
드라군2(이)가 멀티까지 이동합니다.
드라군2(이)가 멀티(을)를 공격합니다. 데미지: 35
 >>>>>>> 원거리 공격

3) 메서드 오버라이드(Override)

클래스 간에 부모-자식 관계가 형성되었을 때 자식 클래스에서 부모 클래스가 갖는 메서드와 동일한 이름의 메서드를 정의하는 기법.

자식이 정의한 메서드에 의해 부모 메서드는 가려지게 된다.

상속 후 자식이 메서드를 추가하는 것이 기능의 확장이라면 메서드 오버라이드는 부모의 기능을 수정하는 개념이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Terran {
    /** 모든 객체가 갖는 명사적 특성들을 멤버변수로 정의 */
    constructor(name, hp, dps) {
        this._name = name;    // 이름
        this._hp = hp;        // 체력(health point)
        this._dps = dps;      // 초당공격력(damage per Second)
        console.log("[%s] 체력: %d, 공격력: %d", name, hp, dps);
    }

    /** 객체가 수행해야 하는 동작들을 함수 형태로 정의 */
    attack(target) {
        console.log("%s(이)가 %s(을)를 공격합니다. 데미지: %d", this._name, target, this._dps);
    }
}

class Marine extends Terran {
    attack(target) {
        console.log("%s(이)가 %s에게 사격을 개시합니다. 데미지: %d", this._name, target, this._dps);
    }
}

const m = new Marine("해병1", 120, 30);
// 자식 클래스에 의해 재정의된 기능 호출 --> 부모의 메서드는 가려진다.
m.attack("질럿");
▶ 출력결과
1
2
[해병1] 체력: 120, 공격력: 30
해병1(이)가 질럿에게 사격을 개시합니다. 데미지: 30

4) super 키워드

Override 이전의 원본 기능 호출하기

this 키워드가 현재 클래스나 부모로부터 상속 받은 자원을 가리키는 예약어인 반면, super 키워드는 부모의 메서드를 Override 하고 있는 자식 클래스 안에서 부모의 원래 기능을 호출하고자 하는 경우에 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
class Tank extends Terran {
    attack(target) {
        // 부모의 원본 메서드 호출
        super.attack(target);
        // 부모의 기능에 새로운 기능을 덧붙임
        console.log(" >>>> 탱크 포격 !!!!");
    }
}

const t = new Tank("탱크1", 120, 30);
// 자식 클래스에 의해 재정의된 기능 호출
t.attack("드라군");
▶ 출력결과
1
2
3
[탱크1] 체력: 120, 공격력: 30
탱크1(이)가 드라군(을)를 공격합니다. 데미지: 30
 >>>> 탱크 포격 !!!!

부모 클래스의 생성자

super 키워드를 메서드처럼 사용할 경우 부모 클래스의 생성자를 의미한다.

자신의 생성자를 통해 전달받은 파라미터와 추가적으로 가공된 파라미터를 부모의 생성자로 전달하여 객체를 생성하는 방법에 변화를 주고자 할 경우 사용한다.

1
2
3
4
5
6
7
8
9
class Firebat extends Terran {
    // 자신의 생성자를 통해서 부모의 생성자를 간접적으로 호출할 수 있다.
    constructor(name) {
        super(name, 500, 50);
    }
}

const f = new Firebat("화염방사병");
f.attack("");
▶ 출력결과
1
2
[화염방사병] 체력: 500, 공격력: 50
화염방사병(이)가 적(을)를 공격합니다. 데미지: 50

#04. 정적 멤버변수, 정적 메서드

클래스에 속한 변수나 함수에 static 키워드를 접목하면 객체 생성에 상관 없이 클래스 이름을 통해 항상 접근할 수 있는 정적 기능을 정의할 수 있다.

이렇게 정의된 정적 기능은 각 객체간의 공유 자원이 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Customer {
    constructor(name) {
        this._name = name;
    }

    static count = 0;

    in() {
        Customer.count++;
    }

    out() {
        Customer.count--;
    }

    showState() {
        // _name은 각 객체들이 고유하게 관리하는 값, count는 모든 객체가 공유하는 값
        console.log("손님이름: %s, 전체 손님 수: %d", this._name, Customer.count);
    }
}
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
var c1 = new Customer("손님1");
c1.in();
c1.showState();
console.log("------------");

var c2 = new Customer("손님3");
c2.in();

c1.showState();
c2.showState();

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

var c3 = new Customer("손님3");
c3.in();

c1.showState();
c2.showState();
c3.showState();

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

c3.out();
c2.out();
c1.showState();
c2.showState();
c3.showState();
▶ 출력결과
1
2
3
4
5
6
7
8
9
10
11
12
손님이름: 손님1, 전체 손님 수: 1
------------
손님이름: 손님1, 전체 손님 수: 2
손님이름: 손님3, 전체 손님 수: 2
------------
손님이름: 손님1, 전체 손님 수: 3
손님이름: 손님3, 전체 손님 수: 3
손님이름: 손님3, 전체 손님 수: 3
------------
손님이름: 손님1, 전체 손님 수: 1
손님이름: 손님3, 전체 손님 수: 1
손님이름: 손님3, 전체 손님 수: 1

크리에이티브 커먼즈 라이선스 ITPAPER(호쌤,쭈쌤)에 의해 작성된 ≪[JS] Class≫은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.
이 라이선스의 범위 이외의 이용허락을 얻기 위해서는 leekh4232@gmail.com으로 문의하십시오.

comments powered by Disqus