클래스(Class)

2021. 3. 23. 14:02Front-end/Javascript

728x90
반응형

클래스(Class)

🍼 클래스(Class) vs 객체(Object)

Class

  • 붕어빵 틀에 비유
  • 템플릿, 청사진이라고도 한다
  • 클래스 안에는 데이터가 들어있지 않다

 

Object

  • 붕어빵 틀로 만든 붕어빵
  • 클래스에 데이터를 넣어서 만든 것이 객체
  • 클래스를 이용해서 많은 객체를 만들 수 있음
  • 붕어빵 클래스에 팥 데이터를 넣으면 팥 붕어빵, 크림 데이터를 넣으면 크림 붕어빵이 된다.

 

클래스는 ES6에 추가된 기능임

  • 클래스가 없었을 때에는 function을 이용해서 바로 object를 만들었다(이게 prototype)
  • 기존에 존재하던 프로토타입을 기반으로 하며 간편하게 쓸 수 있도록 문법적으로만 추가되었다고 해서 문법적 설탕(Syntatic sugar)이라고 한다. (여전히 클래스도 프로토타입 기반이다)

 

클래스 몸체에서 정의할 수 있는 메소드

  • constructor(생성자)
  • 프로토타입 메소드(일반 메소드)
  • 정적 메소드(static)

1. 클래스의 선언

class Person{

    constructor(name, age){

        //fields

        this.name = name;

        this.age = age;

    }

        //methods

    speak(){

        console.log(`${this.name} :  hello!`);

    }

}

 

🍩Object의 생성

위에서 잘 정의한 Person이라는 클래스를 가지고 객체를 만들어보자.

const ellie = new Person('ellie', 20);

console.log(ellie.name); // ellie

console.log(ellie.age); // 20

ellie.speak(); // ellie : hello!

 

 

2. getter & setter

🍮 Getter 함수와 Setter 함수를 사용하면 특정 값을 조회하려고 하거나 바꾸려고 할때, 우리가 원하는 코드를 실행할 수 있다.

 

🍮 getter, setter를 이해하기 쉬운 이야기

  • 자판기 = class
  • 자판기에는 커피가 있지
  • 커피 개수 = 프로퍼티
  • 동전을 넣고, 커피를 만들고 = 메소드
  • 커피 개수가 마이너스인게 맞을까 안맞을까? 당연히 안맞지 그러니까 getter, setter를 쓰는거지
  • 사용자가 마음대로 커피 개수를 설정하는게 좋을까 안좋을까 안좋으니까 private로 만드는거지

 

class User{

    constructor(firstName, lastName, age){

        this.firstName = firstName;

        this.lastName = lastName;

        this.age = age;

    }

}

const user1 = new User('Steve', 'Job', -1);

console.log(user1.age);

여기서 보면 age를 -1로 지정하고 user1을 만들었다.

그런데 사람의 나이가 -1이라는 것은 우리가 원하는 것이 아니다.

이렇게 클래스를 사용하는 사용자가 잘못 사용해도 좀 더 방어적으로 만들기 위해 필요한 것이 getter이다.

 

class User{

    constructor(firstName, lastName, age){

        this.firstName = firstName;

        this.lastName = lastName;

        this.age = age;

    }

    get age(){

        return this._age;

    }

    set age(value){

        this._age = value;

    }

}

const user1 = new User('Steve', 'Job', -1);

console.log(user1.age);

getter와 setter 함수는 get과 set 키워드를 써서 작성해주면 된다.

 

🌟그런데 이 때 그냥 age가 아니라 _age라고 써준 이유는,

constructor에 있는 age랑 똑같이 이름을 작성해주면,

return this.age <- 여기서는 계속 get age를 호출하여 결과적으로 무한재귀이다.

this.age = value <- 여기서는 계속 set age를 호출하여 결과적으로 무한재귀이다.

그래서 보통은 _age라고 언더바를 붙여서 많이 쓴다.

 

🌟우리가 _age라고 해놓았지만 user1.age 라고 호출하거나 user1.age에 값을 할당할 수 있는 이유는 내부적으로 get,set이라는 키워드를 써주었기 때문이다.

 

나이가 음수라면 어떻게 할지 코드 작성하기

 set age(value){

        if(value < 0){

            throw Error(`age can not be nagative`);

        }

        this._age = value;

    }

나이가 음수라면 에러를 던지는 식으로 극단적으로 작성할 수도 있고,

아니면 좀 더 유순하게 작성하려면

 set age(value){

        this._age = value < 0 ? 0 : value;

 }

나이가 0보다 작은지 확인하고, 0보다 작으면 0을 그렇지않다면 value 값을 넣어주도록 작성할 수도 있다.

 

 

3. public & private

 

//3. Fields(public, private)

class Experiment{

    publicField = 2;

    #privateField = 0;

}

const experiment = new Experiment();

console.log(experiment.publicField); // 2

console.log(experiment.privateField); // undefined

너무 최근에 나와서 많이 사용하고 있지는 않지만 이런게 나왔다 정도만 일단은 알고 있으면 될 것 같다.(최신 브라우저에서 사용되지 않고 있음

constructor(생성자)를 쓰지 않고 field를 정의할 수 있는데, 그냥 정의하면 외부에서 접근이 가능한 public으로 정의가 되고 #를 붙이면 클래스 내부에서만 사용가능하고 외부에서는 값을 읽을 수도 변경할 수도 없는 private으로 정의할 수 있다.

 

4. static

🍫 정적메소드 : 인스턴스를 생성하지 않아도 호출할 수 있는 메소드

🍫 정적 메소드는 프로토타입메소드처럼 인스턴스로 호출하지 않고 클래스로 호출한다.

 

//4. Static properties and methods

class Article{

    static publisher = 'Dream Coding';

    constructor(articleNumber){

        this.articleNumber = articleNumber;

    }

    static printPublisher(){

        console.log(Article.publisher);

    }

}

이것도 아직 쓰기엔 무리가 있지만 이런게 있다는 것 정도만 알면 좋을 것 같다.

클래스 안에 있는 fields와 methods들은 새로운 오브젝트를 만들 때마다 그대로 복제되어서 값만 우리가 지정한 값으로 변경된 값으로 만들어지는데, 간혹 이런 오브젝트의 데이터에 상관 없이 클래스가 가지고 있는 고유한 값과, 데이터에 상관 없이 동일하게 반복적으로 사용되어지는 메소드들이 있을 수 있다. 그런 것들을 static을 붙여서 만들게 되면 오브젝트와 상관없이 클래스 자체에 연결되어 있다.

 

이게 무슨 말인지 살펴보겠다.

//4. Static properties and methods

class Article{

    static publisher = 'Dream Coding';

    constructor(articleNumber){

        this.articleNumber = articleNumber;

    }

    static printPublisher(){

        console.log(Article.publisher);

    }

}

const article1 = new Article(1);

const article2 = new Article(2);

console.log(article1.publisher);  // undefined

console.log(Article.publisher); // Dream Coding

article1.publisher라고 콘솔에 출력하면 undefined인데,

Article.publisher라고 콘솔에 출력하면 Dream Coding이 출력된다.

이렇게 나오는 이유는 publisher라는 변수는 어떤 객체에 붙어있는 것이 아니라 Article이라는 클래스 자체에 붙어있는 값이기 때문이다. static으로 지정된 함수를 호출할 때에도 마찬가지이다.

이처럼 static으로 지정된 것을 사용하려면 클래스 자체에 있는 것이므로 클래스로 사용을 해주면 된다.

 

들어오는 값에 상관 없이 공통적으로 클래스에서 쓸 수 있는 거라면 static을 이용해서 작성하는 것이 메모리를 줄여줄 수 있다.

 

5. 상속 & 다형성

우리가 브라우저에 다양한 도형을 그릴 수 있는 웹 어플리케이션을 만든다고 가정해보면

직사각형,삼각형,동그라미를 그릴 수 있다고 하자.

이런 애들을 클래스로 정의할 때 어떻게 정의할 수 있을까?

 

너비와, 높이, 색상,  이런 것들이 있을 수 있고 공통적으로 drawing, 색칠 이런식으로 다양한 메소드 들이 있을 텐데 이렇게 계속 반복되어지는 것들이 있다.

 

너비와 높이  이런 것들을 계속 따로 만들어서 반복하는 것 보다,

얘네들의 공통점(도형) 즉 shape를 한 번에 만들어서 계속 재사용하면 편할 것이다.

 

재사용이 가능하므로 유지보수도 쉽다.

만약 문제가 있다면 공통적인 shape에 와서 수정하면 되기 때문이다.

 

🍒상속

class Shape{

    constructor(width, height, color){

        this.width = width;

        this.height = height;

        this.color = color;

    }

    draw(){

        console.log(`drawing ${this.color} color of`);

    }

    getArea(){

        return this.width * this.height;

    }

}

이런 Shape라는 클래스가 있다고 하자. 우리가 Rectangle이라는 클래스를 만들려고 하는데 extends 키워드를 통해 작성을 해주겠다.

 

class Rectangle extends Shape{

}

const rectangle = new Rectangle(20, 20, 'blue');

rectangle.draw(); // drawing blue color of

이렇게 작성만 해줘도 원래는 Shape에서 존재하던 draw메소드를 Rectangle에서도 사용할 수 있다. 왜냐하면 Rectangle이 Shape를 extends(연장)한 클래스이기 때문이다.

 

triangle도 만들어보겠다. 이것도 마찬가지이다.

class Rectangle extends Shape{

}

class Triangle extends Shape{

}

const rectangle = new Rectangle(20, 20, 'blue');

rectangle.draw(); // drawing blue color of

const triangle = new Triangle(20, 20, 'red');

triangle.draw(); // drawing red color of 

이렇게 상속을 이용하면 공통되는 것들을 일일이 작성하지 않아도 동일한 것을 계속 재사용할 수 있기 때문에 만약 수정해야할 일이 있어도 한 군데에만 가서 수정을 해주면 되기 때문에 아주 좋다.

 

🍒다형성

class Rectangle extends Shape{

}

const rectangle = new Rectangle(20, 20, 'blue');

console.log(rectangle.getArea()); // 400

일단 rectangle(🟨) 의 getArea를 호출하게 되면 20 * 20 = 400 을 출력한다.

 

하지만 우리는 triangle(🔺)의 넓이는 width * height / 2 까지 해주고 싶기 때문에, getArea를 다시 재정의할 필요가 있다.

 

여기서 다형성이 빛을 발한다. 우리가 필요한 함수만 재정의해서 쓸 수 있는 것이다. 이것을 Overrriding (재정의 - 상속 관계에 있는 클래스 간에 같은 이름의 메서드를 정의하는 것) 이라고 한다.

 

class Rectangle extends Shape{

}

class Triangle extends Shape{

    getArea(){

        return (this.width * this.height) / 2;

    }

}

const rectangle = new Rectangle(20, 20, 'blue');

const triangle = new Triangle(20, 20, 'red');



console.log(rectangle.getArea()); // 400

console.log(triangle.getArea()); // 200

이렇게 필요한 함수만 재정의 하면 된다.

 

🍏 super 이용하기 - 만약 상위클래스(부모클래스)의 부분도 쓰면서 새롭게 정의하고 싶다면?

class Triangle extends Shape{

    draw(){

        super.draw();

        console.log(`🔺`);

    }

    getArea(){

        return (this.width * this.height) / 2;

    }

}

const triangle = new Triangle(20, 20, 'red');

triangle.draw(); // drawing red color of

		// 🔺

이렇게 super를 이용해서 호출하게 되면 부모의 draw 메소드를 호출할 수 있다.

 

6. instanceOf

console.log(rectangle instanceof Rectangle); // true

console.log(triangle instanceof Rectangle); // false

console.log(triangle instanceof Triangle); // true

console.log(triangle instanceof Shape); // true

console.log(triangle instanceof Object); // true

왼쪽에 있는 object가 오른쪽에 있는 class의 인스턴스인지 아닌지를 확인하고 true와 false를 리턴한다.

 

🍑 Triangle이 Shape를 상속했기 때문에 Triangle클래스로 만들어진 triangle객체도 당연히 Shape의 인스턴스라고 할 수 있다.

🍑 그리고 모든 객체는 자바스크립트의 기본 Object를 상속한 것이다. 그래서 기본적으로 제공하는 toString() 같은 메소드를 사용할 수가 있다. 그런데 toString을 사용하게 되면 [Object Object] 이렇게 쓸모없는 데이터가 출력이 되니까 toString을 재정의(overriding해서 사용하면 된다.)

class Triangle extends Shape{

    draw(){

        super.draw();

        console.log(`🔺`);

    }

    getArea(){

        return (this.width * this.height) / 2;

    }

    toString(){

        return `Triangle: color: ${this.color}`;

    }

}

이렇게 재정의하면 조금 더 의미있는 데이터를 출력할 수 있다.

 

 

728x90
반응형