관련있는 변수나 함수들을 묶을때 사용하는 문법을 '클래스' 라고 한다. 클래스를 이용해서 상속과 다형성이 일어날 수 있는데, 이런 모든 것들이 가능한 것이 객체 지향 언어이다.
기존의 prototype을 베이스로 하던 JS에 기반해서 그 위에 간편하게 쓸 수 있도록 class라는 문법만 추가된 것이며, 이런 것을 문법적 설탕(syntactical sugar)이라고 한다.
JavaScript 클래스는 JavaScript 객체용 템플릿이다. 비유하자면, 객체는 붕어빵이고 클래스는 붕어빵을 만드는 틀이라고 생각하면 된다.
클래스를 사용하는 이유는 객체 단위로 코드를 그룹화 할 수 있고, 코드 재사용을 위해서 이다.
클래스는 함수의 한 종류이다.
키워드 class
를 사용하여 클래스를 만든다.
📌 인스턴스(instance)와 객체
일반적인 함수의 경우, 함수를 선언한 뒤 함수를 호출하여 동작시키듯이, 클래스의 경우엔 클래스 인스턴스를 생성해야 클래스에 들어 있는 내부 기능들을 사용할 수 있다.
let 인스턴스 = new 클래스이름(); // 일반적으로 인스턴스를 생성하는 방법
기본문법
class ClassName {
constructor() { ... }
}
클래스 생성 및 사용
class Person {
// constructor
constructor(name, age){
// fields
this.name = name;
this.age = age;
// this 라는 것은, this(생성된오브젝트).name 이라는 것이므로 'seul'이 출력되는 것.
}
// methods
speak(){
console.log(`${this.name}:hello!`);
}
}
const my = new Person('seul', 15);
console.log(my.age); // 15
console.log(my.speak()); // seul:hello! <- 이렇게 함수를 호출할 수 있다.
✍️ 생성과 호출 과정에 대해
클래스 생성
(1) class
라는 키워드를 이용해서 Person
이라는 클래스를 생성한다.
(2) constructor
를 이용해서, 이후 오브젝트를 만들때 필요한 데이터를 전달한다.
( 예제의 클래스에는 두 가지의 fields와 methods가 존재한다. )
(3) 전달받은 데이터를 이 클래스에 존재하는 두 가지의 fields(name, age)에 바로 할당 해준다.
오브젝트 생성
새로운 오브젝트를 만들 때는 new
라는 키워드를 쓴다.
new Person('seul', 15)를 호출하면 두가지 일이 발생한다.
(1) 새로운 객체가 생성된다.
(2) 넘겨받은 인수와 함께 constructor
가 자동으로 실행되고, 인수가 this.name
와 this.age
에 할당된다.
❗️ 클래스와 관련된 표기법은 객체 리터럴 표기법과 차이가 있다. 메서드 사이에는 쉼표가 없으니 주의하자. 쉼표를 넣으면 문법 에러가 발생한다.
클래스와 순수함수
클래스와 클래스 표현식, 그리고 동일한 기능을 하는 순수함수에 대해 비교해보자.
📌 클래스 키워드를 사용해서 선언 ▼
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
const my = new User("seul");
my.sayHi();
📌 클래스 역할을 하는 함수를 선언 ▼
// 생성자 함수를 만든다.
function User(name) {
this.name = name;
}
// 모든 함수의 프로토타입은 'constructor' 프로퍼티를 기본적으로 갖고 있으므로
// constructor 프로퍼티를 명시적으로 만들 필요가 없다.
// property에 메서드를 추가한다.
User.prototype.sayHi = function () {
alert(this.name);
};
let user = new User("lee");
user.sayHi();
✍️ 이 두 가지를 방법의 결과는 거의 같지만 차이가 있다.
(1) 클래스는 항상 엄격 모드(use strict)로 실행된다.
(2) 클래스 메서드는 열거할 수 없다.
(3) class로 만든 함수엔 특수 내부 프로퍼티인 [[FunctionKind]]:"classConstructor"
가 붙는다.
생성자를 호출할 때에는 반드시 new 연산자를 사용해야 하는 이유이기도 하다. JS 엔진은 함수에[[FunctionKind]]:"classConstructor"
가 있을 때 new 와 함께 호출하지 않으면 에러를 발생시킨다.
Class 표현식은 class를 정의하는 또 다른 방법이다.
Class 표현식은 이름을 가질 수도 있고, 갖지 않을 수도 있는데, 이름을 가진 class 표현식의 이름은 클래스 body의 local scope에 한해 유효하다. 쉽게 말해 클래스 내부에서만 사용 가능하다는 것이다.
// ex.1) 앞에서 본 예제를 표현식으로 바꾸면 이렇게 된다.
let User = class {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
};
const my = new User("seul");
my.sayHi();
console.log(User.name); // User
// ex.2)
let User = class User2{
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
};
const my = new User("seul");
my.sayHi();
console.log(User.name); // User2
console.log(User2); // ReferenceError: User2 is not defined
✍️ 하지만, 클래스의 (인스턴스 이름이 아닌) name 속성을 통해서는 찾을 수 있다.
❓ 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('나이는 음수로 설정할 수 없어요!');
* } // 이렇게 적을수도 있다. */
this._age = value < 0 ? 0 : value;
}
}
const user1 = new User("seul", "lee", -15);
const user2 = new User("eun", "kim", 16);
console.log(user1.age);
console.log(user2.age);
❗️ 아래 user1 에 'seul lee' 의 나이를 '-15'로 '잘못' 입력했다.
✍️ 사용자가 실수로 나이를 -로 설정하면 그대로 -15로 출력될 것이다. 이것은 객체지향적인 개념으로 봤을 때 맞지 않는다고 생각될 수 있다. 이렇게 클래스를 사용하는 사용자, 혹은 내가 만든 클래스를 동료가 잘못(의도치 않게) 사용하더라도, 우리가 먼저 선방어적으로 만들 수 있도록 도와주는 것이 getter와 setter라고 이해했다.
(1) get
이라는 키워드를 이용해서 값을 리턴하고, set
을 이용해서 값을 설정한다. 대신 set
은 값을 설정하므로 값을 받아와야 한다.(value)
+) 여기서 this.age
라고 설정하면 에러가 발생한다.
age getter
를 정의하는 순간, 5행의 this.age
는 메모리에 올라가있는 데이터를 읽어오지 않고 바로 getter를 호출한다.age setter
를 정의하는 순간, 바로 메모리에 값을 할당하는 것이 아니라 setter를 호출하게 된다.value
를 this.age
에 할당할 때, 메모리의 값을 업데이트 하는 것이 아니고, value;
는 setter(자기자신)를 호출하게 되는 것이 반복된다.(2) 이렇게 User라는 클래스 안에는 총 3개의 필드가 있다. (firstName, lastName, _age)
(3) 이제 set
을 이용해서 if문 혹은 예제와 같이 작성할 수 있다.
📌 filed
는 _age
이지만, 우리가 .age
라고 호출할 수 있는 것과 _age
에 값을 할당할 수 있는 것은 내부적으로 getter와 setter를 이용하기 때문!
생성자를 쓰지 않고
fields
를 정의할 수 있다. 해쉬기호#
을 붙이면 private class 필드를 선언한다.
class Experiment {
publicField = 2;
#privateField = 0;
}
const experiment = new Experiment();
console.log(experiment.publicField); //2
console.log(experiment.privateField); //undefined
private fields
는 클래스 내부에서는 값을 보거나 접근, 변경이 가능하지만 클래스 외부에서는 읽거나 변경이 불가능하다.
prototype
가 아닌 클래스 함수 자체에 메서드를 설정할 수도 있는데, 이러한 것을 정적(static) 메서드라고 부른다.static
키워드를 붙여 만들 수 있다.
📌 기본문법 : static methodName() { ... }
(1) 정적 메서드
class User {
static staticMethod(){
console.log(this === User);
}
}
User.staticMethod(); // true
정적 메서드는 클래스의 인스턴스 없이 호출이 가능하며 클래스가 인스턴스화되면 호출할 수 없다. (종종 어플리케이션의 유틸리티 함수를 만드는데 사용된다.)
특정 클래스 인스턴스가 아닌 클래스 '전체’에 필요한 기능을 만들 때 사용할 수 있다.
(2) 정적 프로퍼티
class Article {
static publisher = "Hello";
}
alert( Article.publisher ); // Hello
(예제)
class Article {
static publisher = 'Hello!';
constructor(articleNumber) {
this.articleNumber = articleNumber;
}
static printPublisher() {
console.log(Article.publisher);
}
}
const article1 = new Article(1);
console.log(article1.publisher); // undefined ◀
console.log(Article.publisher); // 'hello' ◀
Article.printPublisher(); // 'hello'
console.log(article1.articleNumber); // 1
✍️ console.log(article1.publisher);
에서 undefined 가 뜨는 이유는 static은 오브젝트 마다 할당되어 지는 것이 아니고 Article 이라는 클래스 자체에 주어지기 때문이다.
그래서 console.log(Article.publisher);
처럼 입력하면 값을 받아 올 수 있는 것이다. 😀
reference
web_club javascript_info MDN