클래스는 객체 지향 프로그래밍(OOP)에서 특정 객체를 생성하기 위해 변수와 메소드를 정의하는 일종의 틀로, 객체를 정의하기 위한 상태(멤버 변수)와 메서드(함수)로 구성된다.
클래스는 다음과 같은 기본 문법을 사용해 만들 수 있다.
class MyClass {
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
이렇게 만든 클래스는 new MyClass() 호출을 통해 내부에서 정의한 메서드가 들어있는 객체를 생성한다.
객체의 기본 상태를 설정해주는 생성자 메서드 constructor()는 new에 의해 자동으로 호출되므로 특별한 절차 없이 객체를 초기화할 수 있다.
class User {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(this.name);
}
}
// 사용법
const user = new User("John");
user.sayHi();
new User("John")를 호출하면 다음과 같은 일이 일어난다.
constructor가 자동으로 실행. 이때 인수 "John"은 this.name에 할당됨이러한 과정을 거친 후에 user.sayHi() 같은 객체 메서드를 호출할 수 있다.
자바스크립트에서 클래스는 함수의 한 종류이다.
class User {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(this.name);
}
}
console.log(typeof User); // function
class User { ... }가 하는 일은 다음과 같다.
User라는 이름을 가진 함수를 만든다. 함수 본문은 생성자 메서드 constructor에서 가져오며, 생성자 메서드가 없으면 본문이 비워진 채로 함수가 만들어진다.sayHi() 같은 클래스 내에서 정의한 메서드를 User.prototype에 저장한다.new User()를 호출해 객체를 만들고, 객체의 메서드를 호출하면 메서드를 prototype 프로퍼티를 통해 가져온다. 이 과정이 있기 때문에 객체에서 클래스 메서드에 접근할 수 있다.
class User 선언 결과를 그림으로 나타내면 다음과 같다.
위의 내용을 코드로 표현하면
class User {
constructor(name) {
this.name = name;
}
sayHi() {
console.log(this.name);
}
}
// 클래스는 함수이다.
console.log(typeof User); // function
// 정확히는 생성자 메서드와 동일하다.
console.log(User === User.prototype.constructor); // true
// 클래스 내부에서 정의한 메서드는 User.prototype에 저장된다.
console.log(User.prototype.sayHi); // [Function: sayHi]
// 현재 프로토타입에는 메서드가 두 개이다.
console.log(Object.getOwnPropertyNames(User.prototype)); // [ 'constructor', 'sayHi' ]
클래스 필드를 사용하면 어떤 종류의 프로퍼티도 클래스에 추가할 수 있다. 클래스 User에 name 프로퍼티를 추가해보자
class User {
name = "보라";
sayHi() {
console.log(`${this.name}님 안녕하세요!`);
}
}
new User().sayHi(); // 보라님 안녕하세요!
클래스를 정의할 때 '<프로퍼티 이름> = <값>'을 써주면 간단히 클래스 필드를 만들 수 있다.
클래스 필드의 중요한 특징 중 하나는 User.prototype이 아닌 개별 객체에만 클래스 필드가 설정된다는 점이다.
class User {
name = "보라";
}
const user = new User();
console.log(user.name); // 보라
console.log(User.prototype.name); // undefined
아울러 클래스 필드엔 복잡한 표현식이나 함수 호출 결과를 사용할 수 있다.
class User {
name = console.log("보라");
}
let user = new User();
user.name; // 보라
자바스크립트에서 this는 동적으로 결정된다.
따라서 객체 메서드를 여기저기 전달해 전혀 다른 컨텍스트에서 호출하게 되면 this는 메서드가 정의된 객체를 참조하지 않는다.
class Button {
constructor(value) {
this.value = value;
}
click() {
console.log(this.value);
}
}
const button = new Button("안녕하세요.");
setTimeout(button.click, 1000); // undefined
이렇게 this의 컨텍스트를 알 수 없게 되는 문제를 '잃어버린 this(losing this)'라고 한다.
두 방법을 사용해 해결할 수 있는데,
setTimeout(() => button.click(), 1000)와 같이 래퍼 함수를 전달하기가 있다.
또한 이 두 방법 말고 클래스 필드를 사용해도 문제를 해결할 수 있다.
class Button {
constructor(value) {
this.value = value;
}
click = () => {
console.log(this.value);
};
}
const button = new Button("안녕하세요.");
setTimeout(button.click, 1000); // 안녕하세요.
클래스 필드 click = () => { ... }는 각 Button 객체마다 독립적인 함수를 만들어주고 이 함수의 this를 해당 객체에 바인딩시켜준다. 따라서 button.click을 아무 곳에나 전달할 수 있고, this엔 항상 의도한 값이 들어가게 된다.
exercise
함수 스타일로 작성된 코드는 다음과 같이 클래스 문법으로 작성할 수 있다.
// 함수
function Clock() {
let timer;
function render() {
let date = new Date();
let hours = date.getHours();
if (hours < 10) hours = "0" + hours;
let mins = date.getMinutes();
if (mins < 10) mins = "0" + mins;
let secs = date.getSeconds();
if (secs < 10) secs = "0" + secs;
console.log(`${hours}:${mins}:${secs}`);
}
this.start = function () {
render();
timer = setInterval(render, 1000);
};
this.stop = function () {
clearInterval(timer);
};
}
let clock = new Clock();
clock.start();
// 클래스
class Clock {
render() {
let date = new Date();
let hours = date.getHours();
if (hours < 10) hours = "0" + hours;
let mins = date.getMinutes();
if (mins < 10) mins = "0" + mins;
let secs = date.getSeconds();
if (secs < 10) secs = "0" + secs;
console.log(`${hours}:${mins}:${secs}`);
}
start() {
this.render();
this.timer = setInterval(() => this.render(), 1000);
}
stop() {
clearInterval(this.timer);
}
}
const clock = new Clock();
clock.start();
Reference