본문 바로가기

웹개발/javascript

클래스와 상속 그리고 인스턴스오브 (Class & Inheritance &instanceof)

 

 

클래스 Class 

 

클래스는 조금 더 연관 있는 데이터를 한 곳에 묶어놓는 컨테이너 같은 역할을 한다. 

클래스에는 이름과 나이 같은 속성(fields)과 행동(메서드)이 들어있다. 이렇게 필드와 메서드가 종합적으로 묶여있는 것을 클래스라고 하는 것이다.간혹 메서드는 들어있지 않는 경우도 있는데 그런 클래스를 데이터 클래스라고 한다. 

 

이렇게 관련 있는 변수나 함수들을 묶어놓은 것을 클래스라고 하고 클래스 안에서 내부적으로만 보이는 변수와 밖에서 보일 수 있는 변수들을 나누어서 외부적으로 보이지 않게 하는 것을 캡슐화(encapsulation)라고도 한다.

클래스를 이용해서 상속과 다양성이 일어날 수 있는데 이런 모든 것들이 가능한 것이 객체지향 언어라고 하고 자바스크립트는 객체지향 언어이다.

 

클래스는 틀, 청사진, 템플릿이라고 불리는데 클래스 자체에는 데이터가 들어있지 않고 틀만 정의해 놓은 것이다. 이런 클래스에는 요런 요런 데이터가 들어올 수 있어 라고만 정의해놓고 한 번만 선언한다. 

이런 클래스를 이용해서 실제로 데이터를 넣어서 만드는 것이 오브젝트이다. 

클래스를 이용해서 새로운 인스턴스를 생성하면 오브젝트가 되는 것이다. 오브젝트는 이런 클래스를 이용해서 굉장히 많이 만들 수 있고 클래스는 정의만 한 것이라서 실제로 메모리에 올라가진 않지만 실제로 데이터를 넣어서 오브젝트 인스턴스를 생성하면 이제 오브젝트는 메모리에 올라가게 된다. 

 

클래스는 ES6에서 추가된 문법으로 기존의 문법에 비슷하게 더해 만들어진 문법이다. 

 

 

 

 

1. 클래스 선언 (Class declarations)

class Person {
    // constuctor
    constructor(name, age) {
        // fields
        this.name = name;
        this.age = age;
    }

    // methods
    speak() {
        console.log(`${this.name}: hello`);
    }
}

const bob = new Person('bob', 20);
console.log(bob.name);				//bob
console.log(bob.age);				//20
bob.speak();					//bob: hello

 

클래스는 위와 같은 구조로 선언이 된다. 생성자 함수에 데이터를 지정해 넣을 수 있고 메서드를 넣을 수도 있다. 

메서드가 들어가 있지 않을 경우도 가능한데 그럴 경우 이 클래스를 '데이터 클래스'라고 부른다.

 

 

 

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

class User {
    constructor(firstName, lastName, age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    get age() {
        return this._age;
    }

    set age(value) {
        // if (value < 0) {
        //     throw Error('age can not be negative');
        // }

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

const user1 = new User('steve', 'job', -1);
console.log(user1.age);    //0

 

 사용자가 입력한 값을 내부에서 제어해서 변경하고 싶을 때 사용하는 것이 바로 게터와 세터이다. 

만약 개수나 나이를 정의하는 정보 값을 설정할 때, 사용자가 간혹 실수나 고의로 -1과 같은 개수와 나잇값에 알맞지 않은 값을 입력할 수 있다. 그것을 그대로 받아들인다면 문제가 있으므로 사전에 처리를 해두는 것이다. 

위의 주석 처리한 부분처럼 조건문으로 에러를 발생시키거나 아니면 값을 다르게 처리하여 반환할 수도 있다. 

 

 

 

 

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;
    }
}

 

게터와 세터를 지정할 때 주의해야 할 점이 있다.

생성자에서 this.age = age;라고 설정을 해두었는데 순서대로 보자면 this.age에 파라미터 age 값을 할당하고 게터와 세터 향하는 것이 아니라 this.age는 곧바로 세터로 향하게 되고 할당되는 age 또한 곧바로 세터로 향하게 된다.  결국 세터에서는 this.age = age를 실행하게 되고 이것의 age는 또다시 세터를 실행시키는 무한루프가 생긴다.  그러므로 게터와 세터의 안쪽 변수는 이름을 살짝 다르게 변형(보통 저렇게 앞에 언더바( _ )를 붙인다)시켜야지만 무한루프에 빠지지 않고 잘 작동할 수 있다.  

 

 

 

 

3. 필드 (Field) 

class Experiment {
    publicField = 2;
    privateField = 0;
}
const experiment = new Experiment();
console.log(experiment.publicField);
console.log(experiment.privateField);

 

클래스 안에 퍼블릭 필드와 프라이빗 필드로 값을 저장할 수 있다.  콘솔로 값을 찍어보면 퍼플릭 필드에 저장한 값은 클래스 내부나 외부 모두 확인할 수 있지만 프라이빗 필드에 저장한 값은 스코프를 나오면 undefined로 값을 불러올 수 없다. 

( ※ 최신 문법이라 아직 사용하기에는 이르기 때문에 사용하는 브라우저가 지원을 하는지 확인해보는 것이 바람직하다. )

 

 

 

 

4. 스테틱 프로퍼티와 메서드 (Static properties and methods)

class Article {
    static publisher = 'Hello, there';
    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);			//Hello, there
Article.printPublisher();			//Hello, there

 

클래스 안에 스태틱으로 프로퍼티나 메서드를 설정할 수 있다. 스태틱은 오프젝트나 데이터에 상관없이 클래스에 가지고 있는 고유한 값과 동일하게 반복되는 메서드 들으러 클래스로 새로운 오브젝트를 생성할 때 함께 옮겨가지 않는다. 그러므로 클래스 안에서만 존재하고 클래스로 접근해서 호출해야만 한다. 

( ※ 최신 문법이라 아직 사용하기에는 이르기 때문에 사용하는 브라우저가 지원을 하는지 확인해보는 것이 바람직하다. )

 

 

 

 

5. 상속 (Inheritance)

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;
    }
}

class Rectangle extends Shape {}
class Triangle extends Shape {
    draw() {
        super.draw();
        console.log(this.color);
    }
    getArea() {
        return (this.width * this.height) / 2;
    }
    toString() {
        return `Triangle: ${this.color}`
    }
}
const rectangle = new Rectangle(20, 20, 'blue');
rectangle.draw();
console.log(rectangle.getArea());

const triangle = new Triangle(20, 20, 'red');
triangle.draw();
console.log(triangle.getArea());

여러 클래스를 만들 때 클래스들 중에서도 공통된 프로퍼티나 메서드들이 있을 수 있다. 이것들을 묶어서 하나의 클래스로 만들어주고 다른 클래스를 만들 때 공통의 클래스를 상속할 수 있다. 그렇게 되면 코드를 작성하기에도 간결해져 효율적인 코드가 되고 공통적으로 무언가 잘못된 부분이 있다면 공통의 클래스에서만 수정할 수 있기 때문에 유지보수면에서도 효과적이다. 

 

위의 코드를 보면 먼저 Shape이라는 공통된 클래스를 만들고 그다음 다른 클래스인 Rectangle을 만들 때는 동일한 프로퍼티와 메서드를 작성하는 것 대신 extends 라는 키워드를 이용해서 Shape을 연장할 수 있다. 그렇게 되면 Rectangle 클래스를 통해 새로운 오브젝트를 생성할 때 Shape의 프로퍼티와 메서드를 사용하는 것이 가능해진다. 

 

또 여기서 '다양성'이라는 것이 등장하는데 위와 같이 Triangle이라는 클래스에서 Shape을 연장할 때 필요한 부분만 재 정의해서 쓸 수 있다. 이것을 '오버라이팅(overwriting)'이라고 한다. 또 오버라이팅을 할 때 기존의 함수를 포함하고 싶다면 super를 써서 함께 사용하는 것도 가능하다. 

 

 

 

 

6. 인스턴스 오브 (instanceof)

console.log(rectangle instanceof Rectangle);		//true
console.log(triangle instanceof Rectangle);			//false
console.log(rectangle instanceof Triangle);			//false
console.log(rectangle instanceof Shape);			//true
console.log(rectangle instanceof Object);			//true

 instanceof의 오른쪽에 있는 오브젝트는 클래스로 만들어진 오트 젝트이다. 여기서 instanceof는 왼쪽에 있는 오브젝트가 오른쪽에 있는 클래스의 인스턴스 인지 아닌지, 즉 오브젝트가 오른쪽의 클래스로 만들어진 인스턴스인지 아닌지를 true 혹은 false로 반환시켜준다.  

 여기서 rectangle 은 Shape를 상속했기 때문에 Shape의 인스턴스가 맞다. 

 또 자바스크립트에서 만든 모든 오브젝트, 클래스들은 자바스크립트에 있는 오브젝트를 상속한 것이다. 

 

 

 

 

 


 

유튜버 드림 코딩 엘리님의 영상을 기반으로 공부한 것을 정리한 내용입니다.