HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🐣
프론트엔드 데브코스 3기 교육생
/
📚
3기 스터디 가이드
/
💯
자바스크립트 Deep Dive 스터디
/
🕤
자바스크립트 Deep Dive
/
🏝️
25장 클래스 - 나라님
/
25. 클래스

25. 클래스

자바스크립트: 프로토타입 기반 객체 지향 언어(클래스가 필요 없음)
  • ES5
    • 생성자 함수와 프로토타입을 통해 상속 구현 가능
      var Person = (function () { //생성자 함수 function Person(name) { this.name = name; } //프로토타입 메서드 Person.prototype.sayHi = function () { console.log(`Hi I'm ${this.name}`); }; //생성자 함수 반환 return Person }()) //인스턴스 생성 var me = new Person('Shin'); me.sayHi(); //Hi I'm Shin var me2 = new Person('Nara'); me2.sayHi(); //Hi I'm Nara
  • ES6
    • 클래스 도입
    • 기존의 프로토타입 기반 객체지향 모델을 폐지하고 새롭게 클래스 기반 객체 지향 모델을 제공하는 것이 아니다
    • 클래스도 함수이며 기존 프로토타입 기반 패턴의 문법적 설탕(Syntatic sugar)
    • 클래스와 생성자 함수가 정확히 동일하게 동작하지는 않는다. 생성자 함수보다 엄격하며, 생성자 함수에서는 제공하지 않는 기능 제공
  • 클래스와 생성자 함수
    • 클래스와 생성자 함수는 모두 프로토타입 기반의 인스턴스를 생성한다
      ㅤ
      클래스
      생성자 함수
      new 연산자X
      에러 발생
      일반함수로서 호출됨
      extends, super
      O
      X
      호이스팅
      X
      O
      strict mode
      O
      X
      열거
      X
      O
💡
클래스는 새로운 객체 생성 매커니즘

클래스 정의

  • 클래스 선언문
    • // 파스칼 케이스를 사용하자 class Person {}
  • 표현식
    • // 익명 클래스 표현식 const Person = class {} // 기명 클래스 표현식 const Person = class MyClass {}
  • 클래스는 함수다
    • 함수 = 일급객체
    • 무명의 리터럴로 생성할 수 있다. 런타임에 생성 가능
    • 변수나 자료구조(객체 배열)에 저장할 수 있다
    • 함수의 매개변수에게 전달할 수 있다
    • 함수의 반환값으로 사용할 수 있다
//클래스 선언문 class Person { constructor(name){ //인스턴스 생성 및 초기화 this.name = name; } // 프로토타입 메서드 sayHi() { console.log(`Hi I'm ${this.name}`); } // 정적 메서드 static sayHello() { console.log('Hello'); } } //인스턴스 생성 const me = new Person('Shin'); // 인스턴스의 프로퍼티 참조 console.log(me.name); //Shin //프로토타입 메서드 호출 me.sayHi(); //Hi! I'm Shin Person.sayHello(); // Hello!
  • 클래스 몸체에서 정의할 수 있는 메서드
    • constructor(생성자)
    • 프로토타입 메서드
    • 정적 메서드

클래스 호이스팅

클래스는 함수로 평가된다
클래스 선언문으로 정의한 클래스는 함수 선언문과 같이 소스코드 평가 과정(런타임 이전)에 먼저 평가 되어 함수 객체 생성
함수 객체: 생성자 함수로서 호출할 수 있는 constructor, 프로토타입도 이 시점에 더불어 생성된다
  • 클래스는 클래스 정의 이전에 참조할 수 없다
    • console.log(Person);// Uncaught ReferenceError: Person is not defined class Person {}
      호이스팅이 발생하지 않는 것처럼 보인다
      ⇒ let, const키워드로 선언한 변수처럼 호이스팅된다. 클래스 선언문 이전에 일시적 사각지대에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작

인스턴스 생성

클래스는 생성자 함수이며 new연산자와 함께 호출되어 인스턴스를 생성한다
class Person {} const me = new Person(); console.log(me); //Person {}
클래스는 인스턴스를 생성하는 것이 유일한 존재 이유 → 반드시 new 연산자와 함께 호출

메서드

클래스 몸체에 0개 이상의 메서드만 선언 가능
  • constructor
    • 인스턴스를 생성하고 초기화 하기 위한 특수한 메서드
      이름 변경X
      class Person { // 생성자 constructor(name){ //인스턴스 생성 및 초기화 this.name = name; } }
      notion image
      클래스는 평가 되어 함수 객체가 된다
      → 함수 객체의 고유한 프로퍼티를 모두 갖고 있다
      → 함수와 동일하게 프로토타입과 연결되어 있다
      → prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor 프로퍼티는 클래스 자신을 가리키고 있다.
      ⇒ 클래스는 인스턴스를 생성하는 생성자 함수
      notion image
      constructor 내부에서 this에 추가한 프로퍼티는 인스턴스 프로퍼티가 된다
      ⇒ constructor 내부의 this는 클래스가 생성한 인스턴스를 가르킨다
      💡
      constructor는 메서드로 해석되는 것이 아니라 클래스가 평가되어 생성한 함수 객체 코드의 일부가 된다. ⇒ 클래스 정의가 평가되면 constructor의 기술된 동작을 하는 함수 객체가 생성된다
    • constructor는 클래스 내에 최대 한개 존재할 수 있다
    • 생략 할 수 있다 → 빈 constructor가 암묵적으로 정의됨
    • 별도의 반환문을 갖지 않아야 한다
      • class Person { // 생성자 constructor(name, address){ //인수로 인스턴스 초기화 this.name = name; this.address = address; } } //인수로 초기값 전달. 초기값은 constructor에 전달됨 const me = new Person('Shin', 'Daejeon'); console.log(me); //Person {name: 'Shin', address: 'Daejeon'}
  • 프로토타입 메서드
    • 클래스 몸체에서 정의한 메서드는 클래스의 prototype 프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 된다
      //클래스 선언문 class Person { constructor(name){ //인스턴스 생성 및 초기화 this.name = name; } // 프로토타입 메서드 sayHi() { console.log(`Hi I'm ${this.name}`); } } //인스턴스 생성 const me = new Person('Shin'); me.sayHi(); //Hi! I'm Shin
      클래스가 생성한 인스턴스는 프로토타입 체인의 일원이 된다
      notion image
       
      notion image
       
  • 정적 메서드
    • 인스턴스를 생성하지 않아도 호출할 수 있는 메서드
      //생성자 함수 function Person(name) { this.name = name; } Person.sayHi = function () { console.log('Hi!'); }; Person.sayHi(); //Hi!
      //클래스 선언문 class Person { constructor(name){ //인스턴스 생성 및 초기화 this.name = name; } static sayHi() { console.log('Hi!'); } } Person.sayHi(); //Hi!
      notion image
      정적메서드는 클래스에 바인딩된 메서드이다
      인스턴스로 호출할 수 없다 → 인스턴스의 프로토타입 체인상에 존재하지 않기 때문에 상속 받을 수 없다
       
  • 정적 메서드와 프로토타입 메서드의 차이
      1. 정적메서드와 프로토타입 메서드는 자신이 속해있는 프로토타입 체인이 다르다
      1. 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다
      1. 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다
      → 메서드 내부에서 인스턴스 프로퍼티를 참조할 필요가 있다면 프로토타입 메서드로 정의, 참조해야 할 필요가 없다면 정적메서드로 정의
       
  • 클래스에서 정의한 메서드의 특징
      1. function 키워드를 생략한 메서드 축약 표현 사용
      1. 객체 리터럴과 다르게 클래스에서 메서드를 정의할 때는 콤마가 필요없다
      1. 암묵적으로 strict mode로 실행된다
      1. for…in 문이나 Object.keys 메서드 등으로 열거할 수 없다
      1. [[Constructor]]를 갖지 않는 non-constructor → new 연산자와 함께 호출할 수 없다

클래스의 인스턴스 생성 과정

생성자 함수의 인스턴스 생성 과정과 유사하다
class Person { //생성자 constructor(name) { // 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩 된다 console.log(this); // Person {} console.log(Object.getPrototypeOf(this) === Person.prototype); //true // 2. this에 바인딩 되어있는 인스턴스를 초기화 한다 this.name = name; // 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다 } }

프로퍼티

  • 인스턴스 프로퍼티
    • constructor 내부에서 정의
      인스턴스에 프로퍼티가 추가되어 인스턴스가 초기화 된다
      인스턴스 프로퍼티는 언제나 public하다
      //클래스 선언문 class Person { constructor(name){ //인스턴스 프로퍼티 this.name = name; // name 프로퍼티는 언제나 public } } //인스턴스 생성 const me = new Person('Shin'); console.log(me.name); //Shin
  • 접근자 프로퍼티
    • 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티
      //클래스 선언문 class Person { constructor(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } //fullName은 접근자 함수로 구성된 프로퍼티 //getter get fullName() { return `${this.firstName} ${this.lastName}`; } //setter set fullName(name) { [this.firstName, this.lastName] = name.split(' '); } } //인스턴스 생성 const me = new Person('Ungmo', 'Lee'); //데이터 프로퍼티를 통한 프로퍼티 값의 참조 console.log(`${me.firstName} ${me.lastName}`); //Ungmo Lee //접근자 프로퍼티를 통한 프로퍼티 값의 저장 //setter 함수가 호출된다 me.fullName = 'Nara Shin'; //getter 함수가 호출된다 console.log(me.fullName); // Nara Shin
      notion image
      인스턴스 프로퍼티처럼 사용 → 프로퍼티를 참조하는 형식으로 사용
      클래스의 메서드는 기본적으로 프로토타입의 메서드가 된다 → 접근자 프로퍼티 또한 프로토타입의 프로퍼티가 된다
      notion image
  • 클래스 필드
    • 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어
      class Person { name = 'Lee'; } const me = new Person('Lee');
      자바스크립트의 클래스 body에는 메서드만 선언 가능 → 클래스 필드를 선언하면 Syntax Error
      위 코드는 최신 브라우저에서 정상 동작
      ⇒ 자바스크립트에서도 인스턴스 프로퍼티를 마치 클래스 기반 객체지향 언어의 클래스 필드처럼 정의할 수 있는 표준 사양인 “Class field declarations”가 TC39프로세스의 stage3에 제안되어 있다
      notion image
      클래스 몸체에서 클래스 필드를 정의하는 경우 this에 클래스 필드 바인딩X → this는 클래스의 constructor와 메서드 내에서만 유효
      class Person { // 초기값을 할당하지 않으면 undefined를 갖는다 name; constructor(name) { //클래스 필드 초기화 //클래스 필드 참조시 this사용 this.name = name; } } const me = new Person('Lee'); console.log(me) //Person {name: "Lee"}
      클래스 필드에 함수할당 → 모든 클래스 필드는 인스턴스 프로퍼티가 되기 때문에 프로토타입의 메서드가 된지 않으므로 권장하지 않는다
    • private 필드
      • 인스턴스 프로퍼티는 인스턴스를 통해 클래스 외부에서 언제나 참조 가능 → 언제나 public
        클래스필드도 기본적으로 public
        class Person { //private 필드 정의 // 클래스 몸체 내부에서 정의해야 한다 #name = ''; constructor(name) { // private 필드 참조 this.#name = name; } } const me = new Person('Lee');
        notion image
        private 필드 #name은 클래스 외부에서 참조할 수 없다 → 클래스 내부에서만 참조 가능
        접근자 프로퍼티를 통해 간접적으로 호출 가능
        class Person { //private 필드 정의 #name = ''; constructor(name) { // private 필드 참조 this.#name = name; } get name() { return this.#name.trim(); } } const me = new Person(' Lee '); console.log(me.name)
    • static 필드
      • class MyMath { //static public 필드 정의 static PI = 22 / 7; //static private 필드 정의 static #num = 10; //static 메서드 static increment() { return ++MyMath.#num; } } console.log(MyMath.PI); console.log(MyMath.increment());

상속에 의한 클래스 확장

  • 프로토타입 기반 상속: 프로토타입 체인을 통해 다른 객체의 자산을 상속받는 것
  • 클래스: 기존 클래스를 상속받아 새로운 클래스를 확장(extends)하여 정의
// 수퍼(베이스/부모)클래스 class Animal { constructor(age, weight) { this.age = age; this.weight = weigth; } eat() { return 'eat'; } move() { return 'move'; } } // 상속을 통해 Animal 클래스를 확장한 Bird 클래스 // 서브(파생/자식)클래스 class Bird extends Animal { fly() { return 'fly'; } } const bird = new Bird(1, 5); console.log(bird); // Bird { age: 1, wieight: 5 } console.log(bird instanceof Bird); //true console.log(bird instanceof Animal); //true console.log(bird.eat()); // eat console.log(bird.move()); // move console.log(bird.fly()); // fly
  • extends 키워드
    • 상속을 통해 클래스를 확장하기 위해 extends 키워드를 사용하여 상속받은 클래스를 정의
      → 수퍼클래스와 서브클래스 간의 상속 관계 설정, 클래스도 프로토타입을 통해 상속관계 구현
      // 수퍼(베이스/부모)클래스 class Base {} // 서브(파생/자식)클래스 class Derived extends Base {}
      notion image
 
  • 동적 상속
    • extends 키워드는 생성자 함수를 상속받아 클래스를 확장할 수도 있다(extends키워드 앞에는 반드시 클래스가 와야한다)
      function Base(a) { this.a = a; } class Derived extends Base{} const derived = new Derived(1); console.log(derived); // Derived {a: 1}
      extends 키워드 앞에는 클래스뿐만 아니라 [[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있다 → 동적으로 상속받을 대상을 결정할 수 있다
      function Base1() {} class Base2 {} let condition = true; class Derived extends (condition ? Base1 : Base2) {} const derived = new Derived(); console.log(derived); //Derived {} console.log(derived instanceof Base1); //true console.log(derived instanceof Base2); //false
       
  • 서브클래스의 constructor
    • 서브클래스에서 constructor 생략시 다음과 같은 constructor가 암묵적으로 정의된다
      constructor(...args) { super(...args); }
      super()는 수퍼클래스의 constructor를 호출하여 인스턴스 생성
      class Base { constructor() {} } class Derived extends Base { //constructor(...args) { super(...args); } } const derived = new Derived(); console.log(derived); //Derived {}
 
  • super 키워드
    • 함수처럼 호출할 수도 있고, this와 같이 식별자처럼 참조할 수 잇는 특수한 키워드
    • super 호출
      • super를 호출하면 수퍼클래스의 constructor(super-constructor)를 호출한다
        //수퍼클래스 class Base { constructor(a, b) { this.a = a; this.b = b; } } //서브클래스 class Derived extends Base { //암묵적으로 constructor가 정의된다 //constructor(...args) { super(...args); } } const derived = new Dervied(1, 2); console.log(derived); //Derived {a: 1, b: 2}
        //수퍼클래스 class Base { constructor(a, b) { this.a = a; this.b = b; } } //서브클래스 class Derived extends Base { constructor(a,b,c){ super(a,b); this.c = c; } } const derived = new Dervied(1, 2, 3); console.log(derived); //Derived {a: 1, b: 2, c: 3}
        💡
        - 서브클래스에서 constructor를 생략하지 않을 경우 서브클래스의 constructor에는 반드시 super를 호출해야한다. - 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없다 - super는 반드시 서브클래스의 constructor에서만 호출한다.
    • super 참조
      • 메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.
        //수퍼클래스 class Base { constructor(name) { this.name = name; } sayHi() { return `Hi ${this.name}`; } } //서브클래스 class Derived extends Base { // super.sayHi는 수퍼클래스의 프로토타입 메서드를 가리킨다 sayHi() { return `${super.sayHi()}. Hello~~` } } const derived = new Dervied(1, 2, 3); console.log(derived); //Derived {a: 1, b: 2, c: 3}
      // 수퍼 클래스 class Circle { constructor(radius) { this.radius = radius; // 반지름 } // 원의 지름 getDiameter() { return 2 * this.radius; } // 원의 둘레 getPerimeter() { return 2 * Math.PI * this.radius; } // 원의 넓이 getArea() { return Math.PI * Math.pow(this.radius, 2); } } //서브 클래스 class Cylinder extends Circle { constructor(radius, height) { // ① super 메소드는 부모 클래스의 constructor를 호출하면서 인수를 전달한다. super(radius); this.height = height; } // 원통의 넓이: 부모 클래스의 getArea 메소드를 오버라이딩하였다. getArea() { // (원통의 높이 * 원의 둘레) + (2 * 원의 넓이) // ② super 키워드는 부모 클래스(Base Class)에 대한 참조 return (this.height * super.getPerimeter()) + (2 * super.getArea()); } // 원통의 부피 getVolume() { // ② super 키워드는 부모 클래스(Base Class)에 대한 참조 return super.getArea() * this.height; } } // 반지름이 2, 높이가 10인 원통 const cylinder = new Cylinder(2, 10);
  • 상속 클래스의 인스턴스 생성 과정
    • class Rectangle { constructor(width, height) { this.width = width; this.height = height; } getArea() { return this.width * this.height; } toString() { return `width = ${this.width}, height = ${this.height}` } } //서브 클래스 class ColorRectangle extends Rectangle { constructor(width, height, color){ super(width, height); this.color = color; } //메서드 오버라이딩 toString() { return super.toString() + `color = ${this.color}`; } } const colorRectangle = new ColorRectangle(2, 4, 'red'); console.log(colorRectangle); //ColorRectangle {width: 2, height: 4, color: 'red'} console.log(colorRectangle.getArea()); //8 console.log(colorRectangle.toString()); //width = 2, height = 4, color= red
      notion image
       
      1. 서브클래스의 super 호출
        1. [[ConsructorKind]] - base, derived
          서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임 → super클래스를 호출해야 하는 이유
           
      1. 수퍼클래스의 인스턴스 생성과 this 바인딩
        1. 수퍼클래스의 constructor 내부에의 코드가 실행되기 이전에 암묵적으로 빈객체 생성 → this에 바인딩
          this는 생성된 인스턴스를 가리킴
          인스턴스는 new.target이 가리키는 서브클래스가 생성한 것으로 처리된다
           
      1. 수퍼클래스의 인스턴스 초기화
        1.  
      1. 서브클래스 constructor로의 복귀와 this 바인딩
        1. super가 반환한 인스턴스가 this에 바인딩된다.
          서브클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴를 this에 바인딩하여 그대로 사용
          ⇒ super가 호출되지 않으면 인스턴스가 생성되지 않으며, this 바인딩도 할 수 없다
           
      1. 서브클래스의 인스턴스 초기화
        1.  
      1. 인스턴스 반환
       
  • 표준 빌트인 생성자 함수 확장
    • 표준 빌트인 객체도 [[Constructor]] 내부 메서드를 갖는 생성자 함수 → extends 키워드 사용 가능
      class MyArray extends Array { average() { return this.reduce((pre, cur) => pre + cur, 0) / this.length; } } const myArray = new MyArray(1, 1, 2, 3); console.log(myArray); //MyArray(4) [1, 1, 2, 3] console.log(myArray.average()); //1.75