Class vs Object

Class

  • 클래스는 붕어빵을 만들 수 있는 틀을 말함. 템플릿만 정의해두고 어떤 데이터 형식이 들어올 수 있는지만 정의
  • 실제로 메모리에 올라가지않음

Object

  • 클래스를 이용하여 생성한 붕어빵을 말함. 실제로 데이터를 집어 넣음
  • 실제로 메모리에 올라감

Javascript 에서 클래스를 정의방법

  • introuduced in ES6 ( es6이전에는 클래스 없이 오브젝트를 만들 수 있었음)
  • syntactical sugar over prototype-based inheritance (완전히 새롭게 추가된 것이 아니라, 기존에 존재하던 프로토타입에 기반하여 문법만 추가된 것 ==> syntactical sugar : 문법적으로 달달한..)
// 1. Class declarations
class Person {
  //constructor
  constructor(name,age){
    //fields
    this.name = name;
    this.age = age;
  }
  //methods
  speak() {
    console.log(`${this.name}: hello!`);
  }
}

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

Getter와 Setter를 쓰는 이유?

자판기 커피 머신을 예로 들면, class CoffeeMachine{} 내에 numberOfCoffee (int) property, putCoin(), makeCoffee() 등의 메서드가 있을 수 있다.

그럼 여기에서 numberOfCoffee 프로퍼티는 ‘-1’로 설정 될 수 있을까? 당연히 안된다. 그래서 Getter와 Setter를 쓰는 것이다.
사용자가 실수로 ‘-1’을 설정한다고 해도, 코드단에서 0으로 변경해주어야 한다. 그래서 Getter 와 Setter를 사용하는 것이다.

// 2. Getter and Setters
class User {
  constructor(firstName, lastName, age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age; //이때 getter & setter 호출. 'this.age'부분에서 getter , '= age' 에서 setter 호출
  }

  get age() {
    return this._age; //age 변수명을 달리한다. 왜냐? 변수값이 다르지않으면 계속해서 호출이 일어남
  }
  set age(value) {
    //if(value < 0) {
    //  throw Error('age can not be negative!');
    //}
    this._age = value < 0 ? 0 : value; //0보다 작으면 0으로 셋
  }
}

const user1 = new User('Steve', 'Job', -1); // 유저가 실수로 나이를 -1로 세팅한 경우 ==> 이는 말이 안되므로 조금 더 방어적인 자세를 취해야함
console.log(user1.age); // 0


Encapsulation (캡슐화)

그럼 위의 커피머신 클래스에서 다른 사람이 numberOfCoffee 프로퍼티를 설정할 수 있게 하는게 좋을까 안좋을까? 자판기 커피 개수를 다른 사람이 수정하는 것은 당연히 안좋다. 그래서 numberOfCoffee 프로퍼티를 private으로 만드는 것이다. 이게 바로 Encapsulation(캡슐화)라고 볼 수 있다.

// 3. Fields (public, private)
// 최근에 추가됨
class Experiment {
  publicField = 2; //생성자를 쓰지 않고 그냥 정의하면 public -> 외부에서 접근 가능
  #privateField = 0; // #을 사용하여 private. 클래스 내부에서만 값이 보여지고 변경이 가능하고, 외부에서는 값을 확인하거나 변경할 수 없다
}
const experiment = new Experiment();
console.log(experiment.publicField); // 2
console.log(experiment.privateField); // undefined
// 4. Static properties and methods
// 최근에 추가됨
class Article() {
  static publisher = 'publisher';
  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 (static은 오브젝트마다 할당되어 지는 것이 아닌, 클래스 자체에 붙어있음)
console.log(Article.publisher); // publisher
Article.printPublisher(); //publisher

클래스 내의 프로퍼티들과 메소드들은 오브젝트 생성시 그대로 복제되고 내부 데이터만 변경되는데, 오브젝트 데이터와 상관없이 동일하게 반복적으로 사용되어지는 메서드가 있을 수 있다. 그럴때 ‘static’을 사용

즉, 클래스로 들어오는 데이터에 상관없이 클래스에서 사용할 수 있는 것이라면 static을 사용하여 정의하는 것이 메모리의 사용을 줄여준다.

상속(Inheritance) & 다형성(Polymorphism)

도형을 그리는 어플리케이션을 개발한다고 가정해보자.
도형 클래스는 너비와 높이, 색상등의 프로퍼티와 색칠 할 수있는 메서드 등을 가질 수 있다. 여기서 너비와 높이는 항상 반복된다.

그래서 너비와 높이는 Shape 클래스에 정의해두고 다른 클래스를 만들때 Shape 클래스를 상속하여 field나 method를 그대로 확장해서 사용할 수 있다. (상속)

삼각형의 넓이의 경우에, 최종적으로 나누기 2를 해주어야 한다. Shape에서 정의된 메서드 getArea()는 width * height으로 정의되어 있으므로 상속이후 해당 메서드를 오버라이딩하여 다른 로직으로 바꿀 수 있고, 또 super을 이용하여 부모의 메서드를 호출하는 것도 가능하다. (다형성)

// 5. Inheritance
// a way for one class to extend another class
class Shape {
  constructor(width, height, color) {
    this.width = width;
    this.height = height;
    this.color = color;
  } // field

  draw() {
    console.log(`drawing ${this.color} color ! `);
  } // method
  getArea() {
    return this.width * this.height;
  } // method
}

class Rectangle extends Shape{} // 이렇게만 정의해도 Shape 클래스에서 정의한 field와 method들이 Rectangle 클래스에 포함됨 (상속)
class Triangle extends Shape{
  draw () {
    super.draw(); // 부모의 draw 메서드 호출
    console.log('😀');
  } // 다형성
  getArea() {
    return (this.width * this.height) / 2; //override 다형성
  }
}

const rectangle = new Rectangle(20, 20, 'blue');
rectangle.draw(); // drawing blue color
console.log(rectangle.getArea()); // 400
const triangle = new Triangle(20,20,'red');
triangle.draw(); // drawing red color,  😀 두개 다 호출
console.log(triangle.getArea()); // 200

instanceOf

// 6. Class checking : 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 -> Shape를 상속했으므로
console.log(triangle instanceOf Object); // True -> 모든 오브젝트는 자바스크립트의 'Object'를 상속한다.

모든 오브젝트들은 자바 스크립트의 Object를 상속하기 때문에 Object내에 정의되어 있는 toString() , valueOf() 등의 메서드를 오버라이딩 하여 재정의할 수 있다.

class Triangle extends Shape{
  draw () {
    super.draw(); 
    console.log('😀');
  } 
  getArea() {
    return (this.width * this.height) / 2 ; //override 다형성
  }

  toString() {
    return `Triangle: color: ${this.color}`; //Object 내의 메서드 override도 가능하다
  }
}