객체를 생성하기 위한 템플릿으로(no data in), "특별한 함수"이다.
data(field)와 method를 하나로 추상화하며, 단 한 번만 선언한다.
( method가 없고 오직 field로만 이루어진 클래스 → data class)
함수를 함수 표현식과 함수 선언으로 정의할 수 있듯이 class 문법도 class 표현식과 class 선언 두 가지 방법을 제공한다.
클래스를 이용해 *캡슐화, 상속, 다형성이 일어날수 있다.
ES6에 추가되어졌으며, 프로토타입을 이용해서 만들어진 syntactic sugar이다.
캡슐화: 내부에서 볼 수 있는 변수와 외부에서 볼 수 있는 변수를 나누는 것
: instance of a class
클래스를 이용해 새로운 인스턴스를 생성하면 오브젝트가 된다.
클래스는 정의만 한 것이라서 실제 메모리에 올라가지 않지만, 데이터를 넣으면 오브젝트는 메모리에 올라가게 된다.
함수 선언과 클래스 선언의 중요한 차이점은 함수 선언은 호이스팅이 일어나지만, 클래스 선언은 그렇지 않다.
클래스를 사용하기 위해서는 클래스를 먼저 선언해야 한다. 그렇지 않으면, 아래의 코드처럼 ReferenceError를 던진다.
const p = new Rectangle(); // ReferenceError
class Rectangle {}
class Person {
// 🔹 constructor : 생성자
// 생성자를 이용해 나중에 object만들 때 필요한 데이터들을 전달 받는다.
constructor(name, age) { // 함수의 매개변수와 인자의 개념과 비슷
// 🔹 fields
this.name = name; // 전달받은 데이터들(인자)을 Class fields에 할당
this.age = age;
}
// 🔹 methods
speak() {
console.log(`${this.name}: hello!`);
}
}
const merry = new Person("merry", 24);
console.log(merry.age); // 24
console.log(merry.name); // merry
merry.speak(); // merry: hello!
: class로 생성된 객체를 생성하고 초기화하기 위한 특수한 메서드이다.
클래스 안에 한 개만 존재할 수 있다. 만약 클래스에 여러 개의 constructor
메서드가 존재하면 SyntaxError가 발생할 것이다.
Accessor property(접근자 프로퍼티)는 'getter(획득자)'와 ‘setter(설정자)’ 메서드로 표현된다. 객체 리터럴 안에서 getter와 setter 메서드는 get
과 set
으로 나타낼 수 있다.
class User {
constructor(firstName, lastName, age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
get age() {
// getter: obj.age을 실행할 때 실행되는 코드
//
return this._age;
}
set age(value) {
// setter: obj.age = value를 실행할 때 실행되는 코드
//
this._age = value < 0 ? 0 : value;
}
}
const user1 = new User("Steve", "Job", -1);
console.log(user1.age); // 0
getter
: object.propertyName
을 사용해 property를 읽으려고 할 때 실행된다.
→ 즉, 위 예제의 console.log(user1.age);
처럼 필드에서 데이터를 꺼내려고 할 때 실행된다.
setter
: object.propertyName = value
으로 property에 값을 할당하려 할 때 실행된다.
→ 즉, 위 예제의 new User("Steve", "Job", -1);
처럼 필드에 데이터를 넣으려고 할 때 실행된다.
setter
를 무한 호출하지 않기 위해선 getter
와 setter
안에서 쓰여지는 변수의 이름을 조금 다른 것으로(e.g. age→_age) 만들어 줘야 한다.
결론적으로, User라는 클래스 안에는 총 3개의 field(firstName, lastName, _age)가 있다. field에는 _age가 있지만 .age로 호출(user1.age
)할 수 있는 것과 .age에 값을 할당(this.age=age;
)할 수 있는 것은 getter
와 setter
를 내부적으로 사용하기 때문이다.
❗ 참고
기술적으론 외부 코드에서
user1._age
을 사용해 이름에 바로 접근할 수 있다. 그러나 밑줄 (user1._age
) 로 시작하는 property는 객체 "내부"에서만 활용하고, 외부에서는 건드리지 않는 것이 관습이다. 따라서 위의 예제에서user1.age
을 통해 property에 접근하고 수정하는 것 처럼user._name
을 직접적으로 사용하지는 않는 것이 좋다.
일반적으로 프로그래밍을 할 때, 객체들의 데이터(필드)를 외부에서 직접적으로 접근하는 것을 막아놓는다. 필드들을 private 접근 제한자로 막아두고, 각 필드의 Getter, Setter로 접근하는 방식을 사용한다.
이렇게 프로그래밍 하는 이유는 객체의 "무결성"을 보장하기 위함이다.
예를 들어, Man이라는 클래스에 weight(몸무게)라는 필드가 존재할 때
weight는 0보다 작을 수 없으나, 외부에서 직접적으로 접근할 경우 weight에 -100이라는 값을 줌으로써 객체의 무결성이 깨지는 일이 발생한다.
이를 방지하기 위해, 필드를 private로 만들어 외부의 접근을 제한한 후
Setter를 사용해 전달받은 값을 내부에서 가공해 필드에 넣어주는 방식을 사용한다. 마찬가지로 필드 값을 가져올 때도, Getter를 사용해 본 필드의 값을 숨긴 채 내부에서 가공된 값을 꺼낼 수 있다.
❗ 출처
💥 브라우저 호환성 문제가 있으므로 사용에 유의하세요.
class Experiment {
// constructor를 쓰지 않고도 field를 생성할 수 있음
publicField = 2;
#privateField = 0;
}
const experiment = new Experiment();
console.log(experiment.publicField); // 2
console.log(experiment.privateField); // undefined
publicField = 2;
→ 그냥 작성하면, 외부에서 접근이 가능한 public 으로 생성된다.
#privateField = 0;
→ 🌟 '#'을 사용하면, 클래스 내부에서만 값에 접근 및 변경이 가능한 private으로 생성된다.
💥 브라우저 호환성 문제가 있으므로 사용에 유의하세요.
class안에 있는 field와 method들은 새로운 object를 만들 때마다 값만 전달받은 인자로 변경된 채 복제되어져 만들어진다.
하지만, object에 들어오는 데이터에 상관없이 공통적으로 class에서 쓸 수 있는 거라면 static
을 이용해서 클래스 자체에만 생성하는 것이 메모리의 사용을 조금 더 줄일 수 있다.
static
은 캐시, 고정된 설정값, 또는 인스턴스 간 복제할 필요가 없는 어떤 데이터 등에 유용하게 쓰일 수 있다.
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
Article.printPublisher(); // Dream Coding
상속을 사용하면 공통된 것들을 일일히 작성하지 않아도 extends를 이용해 재사용할 수 있다.
extends
: 클래스 선언이나 클래스 표현식에서 다른 클래스의 자식 클래스를 생성하기 위해 사용된다. (sub classing)class Shape {
constructor(width, height, color) {
this.width = width;
this.height = height;
this.color = color;
}
draw() {
console.log(`drawing ${this.color} color of`);
}
//여기서 무언가를 수정하면 Shape을 상속하는 클래스들도 변경되서 적용됨
getArea() {
return this.width * this.height;
}
}
class Rectangle extends Shape {}
// Shape에 있는 모든 것들이 Rectangle에 포함되게 됨
class Triangle extends Shape {
// 새로운 것을 작성하면 새롭게 생성되고(여기선 toString),
// 원래 있던 것에 다르게 작성하면 재정의 되고(여기선 getArea()),
// 원래 있던 것을 언급하지 않고 넘어가면 그대로 복제되어 생성됨
draw() {
super.draw();
// 부모 클래스의 draw를 호출하고 싶으면 super를 사용하면 됨
console.log("🔺");
// overriding하면 더이상 Shape에 정의된 draw함수가 호출되지 않고 재정의한 함수만 호출된다.
}
getArea() {
return (this.width * this.height) / 2;
}
toString() {
return `Triangle color: ${this.color}`;
}
}
const rectangle = new Rectangle(20, 20, "blue");
rectangle.draw(); // drawing blue color of
console.log(rectangle.getArea()); // 400
const triangle = new Triangle(20, 20, "red");
triangle.draw(); // drawing red color of // 🔺
console.log(triangle.getArea()); // 200
console.log(triangle.toString()); // Triangle color: red
object
instanceof
class: object가 class를 이용해서 만들어진 instance인지 판별해주는 operator이다.
→ Boolean(T/F)을 리턴함
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
// 우리가 만든 모든 오브젝트, 클래스들은 JavaScript Object를 상속한 것임
❗ 참고 자료
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Classes
https://developer.cdn.mozilla.net/ko/docs/Web/JavaScript/Reference/Classes/Class_fields
https://www.youtube.com/channel/UC_4u-bXaba7yrRz_6x6kb_w
https://ko.javascript.info/property-accessors#ref-614
https://thiago6.tistory.com/75