
class 키워드를 사용하여 정의한다.파스칼 케이스를 사용한다.표현식으로 정의할 수도 있다.값으로 사용할 수 있는 일급 객체 이다. (즉, 클래스는 함수다.)// 기본형태
class Person {}
// 표현식 정의형태
// 익명 클래스 표현식
const Person = class {}
// 기명 클래스 표현식
const Person = class Myclass {}
클래스 몸체에서는 다음 세 가지의 메서드를 정의할 수 있다.
클래스와 생성자함수의 정의방식은 참 닮았는데 비교해보면 다음과 같다.

앞서 살펴본 것처럼 클래스는 함수이다.
다음과같이 클래스 선언문으로 정의한 클래스는 함수 선언문처럼 평가 과정 이전에 먼저 평가되어 함수 객체를 생성한다.
class Person {}
console.log(typeof Person) // ?
생성된 함수 객체는 생성자 함수로서 호출할 수 있는 생성자(constructor) 이다.
함수 객체 생성시점에 프로토타입 도 더불어 생성이 된다.
이 때, 19장.프로토타입-리터럴 표기법에 의해 생성된 객체의 생성자 함수와 프로토타입 에서도 살펴본 바와같이 생성자 함수와 프로토타입은 단독으로 존재할 수 없고 언제나 쌍으로 존재한다.
단, 클래스는 정의 이전에 참조할 수 없다.
그래서 호이스팅이 발생하지 않는 것처럼 보이지만 그렇지 않다.
클래스 선언문도 변수 선언, 함수 정의와 마찬가지로 호이스팅이 발생하는데, let, const 키워드로 선언한 변수처럼 발생한다.
✋🏻 잠깐! 이게 무슨 소리야? let, const 키워드로 선언된 건 뭐가 다른데?
let, const 키워드로 선언한 변수는 일시적 사각지대(TDZ; Temporal Dead Zone) 에 빠지게된다.
즉, 클래스 선언문도 TDZ 에 빠지게 되므로 호이스팅이 발생했지만 하지않은 것처럼 보이는 것이다.
var, let, const, function, class 키워드로 선언된 모든 식별자는 호이스팅 된다. 왜? 모든 선언문은 런타임 이전에 실행되기 때문이다.
| 클래스는 생성자 함수이며, new 연산자와 함께 호출되어 인스턴스를 생성한다.
클래스의 존재이유는 인스턴스 생성이다. 그렇기 때문에 반드시 new 연산자와 함께 호출해야한다.
class Person {}
// ❌ 잘못 호출한 경우 → 에러발생
const me = Person()
// TypeError: Class constructor cannot be invoked without 'new'
// ✅ 올바르게 호출한 경우
const me = new Person()
인스턴스 생성 조건1.
new연산자로 호출해라
또한, 클래스 표현식으로 정의된 클래스의 경우에는 식별자를 사용해 인스턴스를 생성하지 않고, 기명 클래스 표현식의 클래스 이름을 사용해 인스턴스를 생성하면 에러가 발생한다.
const Person = class MyClass {}
const me = new Person()
console.log(MyClass) //
위와같은 오류가 발생하는 이유는 클래스 표현식에서 사용한 클래스 이름은 외부 코드에서는 접근이 불가능하기 때문이다. (like 기명함수 표현식)
인스턴스 생성 조건2. 클래스를 가리키는
식별자를 사용하여 인스턴스를 생성해라
클래스 몸체에서 정의할 수 있는 메서드는 클래스의 정의방법에서도 살펴본 것처럼 세 가지가 있다.
| 인스턴스를 생성하고 초기화하기 위한 특수한 메서드이며, 이름을 변경할 수 없다.
최대 한 개만 존재할 수 있다.this 에 인스턴스 프로퍼티를 추가한다.this 가 아닌 다른 값을 반환하는 것은 클래스의 기본 동작을 방해하는 것이기 때문에 반드시 반환문을 생략해야 한다.| 클래스 몸체에서 정의한 메서드는 생성자 함수에 의한 객체 생성 방식과는 다르게 기본적으로 프로토타입 메서드가 된다.
또한, 생성자 함수와 마찬가지로 클래스가 생성한 인스턴스는 프로토타입 체인에 포함된다.
클래스 몸체에서 정의한 메서드는 객체의 프로토타입에 존재하는 프로토타입 메서드가 되며, 객체는 프로토타입 메서드를 상속받아 사용할 수 있다.
프로토타입 체인은 기존의 모든 객체 생성방식(객체 리터럴, 생성자 함수 등), 클래스에 의해 생성된 객체에도 동일하게 적용된다.
클래스는 생성자 함수의 역할을 하는 즉, 객체를 생성하는 생성자 함수라고 볼 수 있다.
19장.프로토타입-정적 프로퍼티/메서드 에서도 살펴보았듯이 정적 메서드는 인스턴스를 생성하지 않아도 자신의 프로퍼티와 메서드를 가지기 때문에 호출할 수 있는 메서드이다.
생성자 함수에서는 정적 메서드를 다음과 같이 명시적으로 추가를 했다.
function Person(name) {
this.name = name
}
// 정적 메서드 생성
Person.sayHi = function() {
console.log("Hi!")
}
// 정적 메서드 호출
Person.sayHi() // Hi!
하지만, 클래스에서는 간단하게 메서드에 static 키워드만 붙이면 정적 메서드(클래스 메서드)가 된다. (와웅!!!!)
class Person {
constructor(name) {
this.name = name
}
// 정적 메서드 생성
static sayHi() {
console.log("Hi!")
}
}
// 정적 메서드 호출
Person.sayHi() // Hi!
클래스는 클래스 선언문이나 클래스 표현식으로 정의가 평가되는 시점에 함수 객체가 된다.
그래서 정적 메서드는 클래스 정의 후, 별다른 인스턴스를 생성하지 않아도 호출 할 수 있다.
정적 메서드와 프로토타입 메서드는 속해있는 프로토타입 체인이 다르다.
정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다.
정적 메서드는 인스턴스 프로퍼티를 참조할 수 없는 반면, 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다.
위 세 가지 내용을 다음과같이 공식처럼 정리해보았다.
static이 앞에 있다 →정적 메서드이다. → 메서드 호출방법:클래스명.메서드()static이 앞에 없다 →프로토타입 메서드(instance)이다. → 메서드 호출방법:인스턴스.메서드()- 메서드 내부에서 인스턴스 프로퍼티를 참조해야 한다. →
this사용- 메서드 내부에서 인스턴스 프로퍼티를 참조하지 않아도 된다. →
this미사용
클래스에서 정의한 메서드는 다음과 같은 특징이 있다.
function 키워드를 생략한 축약 표현을 사용한다.콤마가 필요없다.strict mode 로 실행된다.for...in문 또는 Object.keys 메서드 등으로 열거할 수 없다.[[Constructor]] 를 갖지 않으므로, new 연산자로 호출할 수 없다.1️⃣ 인스턴스 생성과 this 바인딩
new 연산자와 함께 클래스 호출 → 암묵적으로 빈 객체 생성(클래스가 생성한 인스턴스)프로토타입 으로 클래스의 프로토타입 프로퍼티가 가리키는 객체 설정2️⃣ 인스턴스 초기화
3️⃣ 인스턴스 반환
| 인스턴스 프로퍼티는 constructor 내부에서 정의해야한다.
constructor 내부에서 this에 추가한 프로퍼티는 언제나 클래스가 생성한 인스턴스의 프로퍼티가 된다.
클래스는 다른 객체지향 언어처럼 접근 제한자(private, public, protected) 를 지원하지 않기때문에 인스턴스 프로퍼티는 언제나 public하다.
| 접근자 프로퍼티는 자체적으로는 값을 가지지않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티이다.
이러한 접근자 프로퍼티는 클래스에서도 사용할 수 있다.
접근자 프로퍼티는 getter 함수와 setter 함수로 구성되어있다.
접근할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용한다.get 키워드를 사용하여 정의한다.반환 해야한다.할당할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용한다.set 키워드를 사용하여 정의한다.매개변수가 있어야한다.class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.laseName = lastName
}
// getter 함수
get fullName() {
return `${this.firstName} ${this.lastName}`
}
// setter 함수
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ')
}
}
// ...
접근자 프로퍼티는 호출하는 것이 아니라 참조(getter)하거나 값을 할당(setter)하는 형식으로 사용한다.
기본적으로 클래스의 메서드는 프로토타입 메서드가 된다.
그러므로 클래스의 접근자 프로퍼티도 프로토타입의 프로퍼티가 된다.
| 클래스 필드란 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리킨다.
클래스 기반 객체지향 언어에서의 this 는 언제나 클래스가 생성할 인스턴스를 가리킨다.
몸체에는 메서드만 선언할 수 있다.클래스 필드를 정의할 수 있도록 제안이 되어있으며, Chrome 72 이상 의 브라우저와 버전 12 이상 의 Node.js는 이 제안내용을 미리 구현해 놓았다.✋🏻 잠깐! 하지만, 2025 9월 현재 찾아본 바에 의하면
2021년 1월, TC39 프로세스의 stage3에 클래스 필드로 정의할 수 있는 새로운 표준 사양이 제안됐었고, 2025년 9월 현재 mdn에 공식적으로 등록되어 사용가능한 상태이다. 🥳👏🏻👏🏻🎉
class Person {
name = "Lee"
}
const me = new Person()
console.log(me) // Person{name: "Lee"}
이렇게 클래스 몸체에서 클래스 필드를 정의할 때에는 this 에 클래스 필드 바인딩을 해서는 안된다. ❌
⇒ this는 클래스의 constructor 와 메서드 내에서만 유효하다.
참조하는 경우에는 this 를 반드시 사용하여야 한다.초기값 을 할당하지 않으면 undefined 를 갖는다.constructor 내부에서 클래스 필드를 참조하여 초기화를 진행하여야 한다.함수는 일급 객체이므로 클래스 필드에 할당할 수 있지만 권장하지는 않는다.24장.클로저-캡슐화와 정보은닉 에서 '인스턴스 메서드와 프로토타입을 활용하여 private 흉내를 낼 수는 있지만 완전하다고 할 수는 없다.' 이런 내용을 살펴본적이 있다.
클래스도 역시나 접근 제한자를 지원하지 않아 인스턴스 프로퍼티는 항상 public 이다.
즉, 클래드 필드 정의 제안을 사용하더라도 기본적으로 public이기때문에 외부에 노출이 그대로 된다.
private 필드의 앞에 # 을 붙여서 사용을 하고, 참조시에도 마찬가지로 # 을 붙여주면 된다.
class Person {
// private 필드 정의
#name = ''
constructor(name) {
this.#name = name
}
}
const me = new Person('Lee')
// 잘못된 참조 ❌
console.log(me.#name) // SyntaxError
// 올바른 참조 ✅
console.log(me.name) // Lee
public 필드는 어디서든 참조할 수 있지만, private 필드는 클래스 내부 에서만 참조할 수 있다.
또한, private 필드는 반드시 클래스 몸체에 정의해야하며, 직접 constructor 에 정의하면 에러가 발생한다.
| 접근 가능성 | public | private |
|---|---|---|
| 클래스 내부 | ✅ | ✅ |
| 자식 클래스 내부 | ✅ | ❌ |
| 클래스 인스턴스를 통한 접근 | ✅ | ❌ |
✋🏻 잠깐! 하지만, 2025 9월 현재 찾아본 바에 의하면
2021년 1월, TC39 프로세스의 stage3에 private 필드를 정의할 수 있는 새로운 표준 사양이 제안됐었고, 2025년 9월 현재 mdn에 공식적으로 등록되어 사용가능한 상태이다. 🥳👏🏻👏🏻🎉
정적 메서드는 프로토타입 메서드에서 살펴보았듯이 static 을 앞에 붙여서 정의할 수 있다.
하지만, static 키워드를 사용하여 정적 필드 를 정의할 수는 없었다.
하지만, private 와 마찬가지로 static 또한
2021년 1월, TC39 프로세스의 stage3에 static 필드를 정의할 수 있는 새로운 표준 사양이 제안됐었고, 2025년 9월 현재 mdn에 공식적으로 등록되어 사용가능한 상태이다. 🥳👏🏻👏🏻🎉
class MyMath {
//static public 필드 정의
static PI = 22 / 7
// static private 필드 정의
static #num = 10
// static 메서드
static increment() {
return ++MyMath.#num
}
}
console.log(MyMath.PI) // 3.142857142857143
console.log(MyMath.increment()) // 11
프로토타입 기반 상속은 프로토타입 체인을 통해 상속받는 개념이였지만, 상속에 의한 클래스 확장은 기존 클래스를 상속받아 새로운 클래스를 확장하여 정의하는 것이다.
| 상속을 통해 클래스를 확장하기 위해서는 extends 키워드를 사용하여 상속받을 클래스를 정의해야한다.
// 수퍼(부모/베이스) 클래스 (상속받을 클래스)
class Base {}
// 서브(자식/파생) 클래스
class Derived extends Base {}
수퍼클래스와 서브클래스는 인스턴스의 프로토타입 체인뿐만 아니라 클래스간의 프로토타입 체인도 생성한다. ⇒ 프로토타입 메서드, 정적 메서드 모두 상속이 가능하다.
extends 키워드는 클래스뿐만 아니라 생성자 함수를 상속받아 클래스 확장을 할 수 도 있다.
(단, extends 키워드 앞에는 반드시 클래스가 와야한다.)
// 생성자 함수
function Base(a) {
this.a = a
}
// 생성자 함수를 상속받는 서브 클래스
class Derived extends Base {}
extends 키워드 다음에는 [[Constructor]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있으며, 이를 통해 동적으로 상속받을 대상을 결정할 수 있다.
앞서 살펴본 바와같이 클래스에서 constructor를 생략하면 다음과 같은 비어있는 constructor가 암묵적으로 정의된다.
constructor() {}
이걸 서브 클래스에서 생략을 하게되면 아래와 같은 형태의 constructor가 암묵적으로 정의된다.
(args는 new 연산자와 함께 클래스를 호출할 때 전달한 인수의 리스트이다.)
constructor(...args) { super(...args) }
여기서 super() 는 수퍼 클래스의 constructor(super-constructor) 이다.
| 함수처럼 호출할 수도 있고 this와 같이 식별자처럼 참조할 수 있는 특수한 키워드이다.
super 의 동작방식은 다음과 같으며 하나씩 살펴보자.
super 호출 → 수퍼 클래스의 constructor(super-constructor) 호출super 참조 → 수퍼 클래스의 메서드 호출super 호출수퍼 클래스의 constructor 내부에서 추가한 프로퍼티를 그대로 갖는 인스턴스를 생성하면 서브 클래스의 constructor 는 생략할 수 있다. (새로 추가되는 프로퍼티가 없기 때문에)
하지만, 수퍼 클래스의 프로퍼티와 서브 클래스에서 추가한 프로퍼티를 갖는 인스턴스를 생성할 때에는 서브 클래스의 constructor 는 생략할 수 없으며, 수퍼 클래스의 constructor에 해당되는(전달해야하는) 인수가 있다면 서브 클래스의 constructor에서 super 를 통해 전달한다.
super 를 호출 할때에는 몇 가지 주의사항이 있다.
1. 서브 클래스에서 constructor를 생략하지 않는 경우에는 반드시 super 를 호출해야하며, 호출하지 않을 경우 ReferenceError 가 발생한다.
2. 서브 클래스의 constructor에서 this 를 참조하려면 super 를 먼저 참조해야한다. (순서가 있다!)
class Base {}
class Derived extends Base {
constructor() {
// 잘못된 순서 ❌
this.a = 1
super()
}
}
class Base {}
class Derived extends Base {
constructor() {
// 올바른 순서 ✅
super()
this.a = 1
}
}
super는 반드시 서브 클래스의 constructor 에서만 호출해야하며, 서브 클래스가 아닌 다른 클래스에서 호출 시 SyntaxError 가 발생한다.super 참조super 를 참조할 때에도 몇 가지 알아둬야할 내용들이 있다.
super.메서드명 에서 메서드명은 수퍼 클래스의 프로토타입 메서드를 가리킨다.super참조를 통해 수퍼 클래스의 메서드를 참조하려면, super가 바인딩된 객체 (수퍼 클래스의 prototype 프로퍼티)에 바인딩된 프로토타입을 참조할 수 있어야한다.super 참조는 클래스의 전유물이 아니므로 객체 리터럴에서도 사용할 수 있다.super.메서드명 에서 메서드명은 수퍼 클래스의 정적 메서드를 가리킨다.class Base {
static sayHi() {
return "Hi!"
}
}
class Derived extends Base {
static sayHi() {
// 여기서의 sayHi는 수퍼 클래스의 정적 클래스인 sayHi 를 가리킨다.
return `${super.sayHi()} how are you doing?`
}
}
console.log(Derived.sayHi())
자바스크립트 엔진은 클래스 평가 시, 수퍼/서브 클래스 구분을 위해 base , derived 를 값으로 갖는 내부 슬롯인 [[ConstructorKind]] 를 갖는다.
클래스를 상속받지 않는 클래스는 내부 슬롯([[ConstructorKind]])의 값이 base 로 설정되고, 상속받는 클래스의 내부 슬롯([[ConstructorKind]])의 값은 derived 로 설정된다.
서브 클래스는 직접 인스턴스를 생성하지 않고, 수퍼 클래스에게 인스턴스 생성을 위임하여 상속받는다.
⇒ 서브 클래스의 constructor 에서 꼭 super 로 호출해야한다.
(super 로 호출하지 않으면 인스턴스를 생성할 수 없다.)
수퍼 클래스의 constructor 내부의 코드가 실행되기 전에 암묵적으로 빈 객체를 생성하는데, 이 빈 객체가 클래스가 생성한 인스턴스이다.
생성된 인스턴스는 this 에 바인딩된다. ⇒ 수퍼 클래스의 constructor 내부의 this 는 생성된 인스턴스를 가리킨다.
수퍼 클래스의 constructor가 실행되면 this 에 바인딩되어있는 인스턴스가 초기화된다.
super 호출이 종료되면 흐름은 서브 클래스의 constructor 로 돌아온다.
super 호출이 종료되면서 반환한 인스턴스가 this 에 바인딩되기 때문에 서브 클래스는 별도의 인스턴스를 생성하지 않고 그대로 사용한다.
이런 흐름상의 이유로 super 가 호출되지 않으면 인스턴스 생성도, this 바인딩도 할 수 없다.
앞서 super 키워드 내용을 살펴볼 때, '순서가 있다!' 라고 한 이유가 바로 이때문이다.
super 호출 이후 서브 클래스의 constructor에 있는 인스턴스의 초기화가 실행된다.
클래스의 모든 처리가 완료되면 완성된 인스턴스가 바인딩된 this 의 암묵적 반환이 실행된다.
앞서 살펴봤듯이 extends 키워드 다음에는 클래스가 아니더라도 [[Constructor]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있다.
String, Number, Array 같은 표준 빌트인 객체도 내부 메서드를 갖는 생성자 함수이므로 extends 키워드를 사용하여 확장 할 수 있다.