웹 개발을 시작한 지 오래되진 않았지만 javscript를 사용하면서 class 라는 단어를 className 외에는 타이핑해 본 적이 있었나 싶은데요. 리액트에서도 현재 class형 컴포넌트보다 함수형 컴포넌트의 사용이 대중적이고 본인도 그렇구요. 대체 class는 왜 언제 사용하는 지에 대해 알아 보겠습니다.
class
는 객체를 생성하기 위한 템플릿 이라고 합니다. 데이터와 이를 조작하는 코드를 하나로 추상화한다고 해요. 또 javascript 의 prototype을 이용해서 만들어 졌지만 ES5의 클래스와는 다른 의미와 문법을 가진다고 합니다. ES6 이전에는 function 을 이용해 class를 흉내내서 사용했다고 합니다.
class
는 "특별한 함수" 이고, 정의하는 방법도 함수처럼 두 가지 방법이 있다고 합니다.
class person {
constructor(height,weight) {
this.height = height;
this.weight = weight;
}
}
class 선언 방법이 있습니다. class 라는 키워드와 클래스의 이름을 지정해서 클래스를 정의할 수 있네요.
! 주의할 점
함수 선언의 경우 호이스팅과정에서 초기화가 일어나지만 class는 표현식 class선언 모두 초기화가 일어나지 않기 때문에 정의하기 전에 코드를 사용하면 에러가 발생합니다.
const foo = new animals(); // reference error !
class animals {
// ...
}
let Animals = class {
constructor(height,legs){
this.height = height;
this.legs = legs;
}
}
console.log(Animals.name) // animals
let Plant = class Plant2 {
constructor(height,leaves) {
this.height = height;
this.leaves = leaves;
}
}
console.log(Plant.name) // Plant2
클래스를 나타내는 두번째 방법입니다. 클래스 표현식은 이름을 가질 수도 있고, 갖지 않을 수도 있습니다. 이름을 가진 클래스인 Plant는 클래스의 body의 local scope에 한하여 유효합니다. 하지만 클래스의 name 속성을 통해 인스턴스가 아닌 클래스의 이름을 찾을 수 있다고 한다.
*class body : {} 내부를 의미함.
의문1 : 클래스는 plant2 인스턴스는 plant?
의문2 : 이름이 있음과 없음의 메모리 할당 차이?
class body 는 중괄호 {} 내부를 의미하고 constructor 나 멤버를 정의하는 곳입니다. 클래스 body 에서는 strict mode를 통해 엄격한 문법이 적용됩니다. 이유는 조용하게 발생하는 오류를 방지하기 위함입니다.
constructor 메서드는 class로 생성한 객체를 생성하고 초기화하기 위한 특수한 메서드입니다.constructor는 class 내부에 한 개만 존재할 수 있습니다. 그렇지 않으면 syntax error 가 발생합니다.
static 키워드는 class를 위한 정적 메서드를 생성합니다. class 의 인스턴스화 없이 실행할 수 있고, class 의 인스턴스에서는 호출할 수 없습니다. static 은 어플리케이션을 위한 유틸리티 함수를 생성하는 데 주로 사용된다고 합니다. 또한 캐시나 고정 환경설정 또는 인스턴스 간 복제의 필요가 없는 정적인 데이터에 유용하다고 합니다.
class Animals {
speak() {
return this;
}
static eat() {
return this;
}
}
let obj = new Animal();
obj.speak(); // the Animal object
let speak = obj.speak;
speak(); // undefined
Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined
class 내부에서는 앞서 나왔듯 strict mode 가 자체적으로 적용되기 때문에 this를 사용할 경우 undefined 를 출력합니다.
function Animal() { }
Animal.prototype.speak = function() {
return this;
}
Animal.eat = function() {
return this;
}
let obj = new Animal();
let speak = obj.speak;
speak(); // global object (in non–strict mode)
let eat = Animal.eat;
eat(); // global object (in non-strict mode)
기존의 함수형 방식으로 작성할 경우 strict mode 가 적용되지 않기 때문에 위처럼 최상위 객체를 바인딩합니다.
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
위처럼 height 와 width 의 값을 필드에 먼저 선언할 수 있습니다. 또한 기본값과 같이 선언될 수도 있습니다. 이를 통해 self-documenting 에 가까워 지고 언제나 존재하는 상태가 되었습니다.(새로운 인스턴스를 통해 height 와 width가 제공되기 전까지는 존재하지 않는 값이라는 의미로 받아들여지네요.)
*self-documenting : 자체 문서화를 의미합니다. 자체 문서화의 목표는 아래와 같습니다.
- 소스 코드를 읽고 이해하기 쉽게 만들기
- 레거시 시스템을 유지 관리하거나 확장하는 데 필요한 노력 최소화
- 시스템 사용자와 개발자가 코드 주석 이나 소프트웨어 설명서 와 같은 보조 문서 소스를 참조할 필요성을 줄입니다
- 독립적인 지식 표현을 통한 자동화 촉진
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
클래스의 바깥에서 private field에 접근하려고 하면 에러를 발생시킨다고 합니다. 클래스 내부에서만 사용할 수 있고, 이는 클래스의 버전업으로 인해 내부구현이 바뀔 때 클래스 사용자에서 아무런 영향을 받지 않는 장점이 있습니다.
extends 키워드는 클래스선언 or 클래스표현식에서 다른 클래스의 자식클래스를 생성하기 위해 사용됩니다.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // super class 생성자를 호출하여 name 매개변수 전달
}
speak() {
console.log(`${this.name} barks.`);
}
}
let d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
Animal 클래스에 constructor 가 존재할 경우 this를 사용하기 전에 super()를 호출해야 합니다. 여기서 super()는 객체의 부모가 가지고있는 메서드를 호출하기 위해 사용됩니다.
javascript 는 이미 객체지향 프로그래밍 언어입니다. 그럼에도 클래스를 사용하는 이유는 존재합니다.