:ledger: 클래스와 기본 문법을 알아보자

:one: 클래스란

아래의 코드를 예시로 이해해보자.

class User {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    alert(this.name);
  }
}

// User가 함수라는 증거
alert(typeof User); // function

class User {...} 문법 구조가 하는 일은 아래와 같다.

  1. User 라는 이름을 가진 함수를 만든다. 함수 본문은 생성자 메서드 constructor에서 가져오며, 생성자 메서드가 없으면 본문이 비워진 채로 함수가 만들어진다.
  2. sayHi 같은 클래스 내에서 정의한 메서드를 User.prototype에 저장한다.

new User를 호출해 객체를 만들고, 객체의 메서드를 호출하면 함수의 prototype 프로퍼티에서 설명한 것처럼 메서드를 prototype 프로퍼티를 통해 가져오고 이 과정이 있기에 객체에서 클래스 메서드에 접근할 수 있는 것이다.

:two: 클래스 기본 문법

클래스는 class 키워드를 사용해 정의할 수 있으며, constructor 메서드를 통해 객체 초기화를 수행한다.

:pushpin: 2-1) 기본 클래스 선언

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name}이(가) 소리를 냅니다.`);
  }
}

const dog = new Animal("강아지");
dog.speak(); // "강아지가 소리를 냅니다."

:three: 클래스는 단순한 편의 문법이 아니다.

class 라는 키워드 없이 클래스 역할을 하는 함수를 선언할 수 있기 때문에 클래스는 ‘편의 문법’에 불과하다고 이야기한다. 참고로 기능은 동일하나 기존 문법을 쉽게 읽을 수 있게 만든 문법편의 문법(syntactic sugar, 문법 설탕)이라고 한다.

// class User와 동일한 기능을 하는 순수 함수를 만들어보겠습니다.

// 1. 생성자 함수를 만듭니다.
function User(name) {
  this.name = name;
}
// 모든 함수의 프로토타입은 'constructor' 프로퍼티를 기본으로 갖고 있기 때문에
// constructor 프로퍼티를 명시적으로 만들 필요가 없습니다.

// 2. prototype에 메서드를 추가합니다.
User.prototype.sayHi = function () {
  alert(this.name);
};

// 사용법:
let user = new User("John");
user.sayHi();

위의 예시처럼 순수 함수로 클래스 역할을 하는 함수를 선언하는 방법과 class 키워드를 사용하는 방법의 결과는 거의 같다.

:pushpin: 3-1) 함수 클래스와 클래스의 차이

  1. class로 만든 함수엔 특수 내부 프로퍼티인 [[IsClassConstructor]]: true가 이름표처럼 붙는다. 이것만으로도 두 방법엔 분명한 차이가 있음을 알 수 있다. 자바스크립트는 다양한 경우에 [[IsClassConstructor]]: true를 활용합니다. 클래스 생성자를 1와 함께 호출하지 않으면 에러가 발생하는데 이 때 [[IsClassConstructor]]: true가 사용된다.
  2. 클래스에 정의된 메서드는 열거할 수 없다(non-enumerable). 클래스의 prototype 프로퍼티에 추가된 메서드의 enumerable 플래그는 false입니다. for..in으로 객체를 순회할 때, 메서드는 순회 대상에서 제외하고자 하는 경우가 많으므로 이 특징은 꽤 유용하다.
  3. 클래스는 항상 엄격 모드로 실행된다(use strict). 클래스 생성자 안 코드 전체엔 자동으로 엄격 모드가 적용된다.

:four: 클래스 표현식

클래스 또한 함수처럼 다른 표현식 내부에서 정의, 전달, 반환, 할당할 수 있다.

// 클래스 표현식 예시
let User = class {
  sayHi() {
    alert("hi");
  }
};

:pushpin: 4-1) 기명 클래스 표현식

  • 기명 함수 표현식과 유사하게 클래스 표현식에도 이름을 붙일 수 있다.
  • 클래스 표현식에 이름을 붙이면, 이 이름은 오직 클래스 내부에서만 사용할 수 있다.
// 기명 클래스 표현식(Named Class Expression)
// (명세서엔 없는 용어이지만, 기명 함수 표현식과 유사하게 동작한다.)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // MyClass라는 이름은 오직 클래스 안에서만 사용할 수 있다.
  }
};

new User().sayHi(); // 원하는대로 MyClass의 정의를 보여준다.

alert(MyClass); // ReferenceError: MyClass is not defined, MyClass는 클래스 밖에서 사용할 수 없다.

:pushpin: 4-2) 클래스를 동적으로 생성

필요에 따라 동적으로 생성하는 것도 가능함

function makeClass(phrase) {
  // 클래스를 선언하고 이를 반환함
  return class {
    sayHi() {
      alert(phrase);
    }
  };
}

// 새로운 클래스를 만듦
let User = makeClass("안녕하세요.");

new User().sayHi(); // 안녕하세요.

:five: getter와 setter

리터럴을 사용해 만든 객체처럼 클래스도 gettersetter, 계산된 프로퍼티를 지원한다. getset을 이용해 user.name을 조작할 수 있게 해보자.

class User {
  constructor(name) {
    // setter를 활성화합니다.
    this.name = name;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    if (value.length < 7) {
      alert("이름이 너무 짧습니다.");
      return;
    }
    this._name = value;
  }
}

let user = new User("rarrit");
alert(user.name); // rarrit

user = new User(""); // 이름이 너무 짧습니다.

:six: 클래스 필드

클래스 필드는 최근에 더해진 기능으로 구식 브라우저에선 폴리필이 필요할 수 있다.

  • ‘클래스 필드(class field)’라는 문법을 사용하면 어떤 종류의 프로퍼티도 클래스에 추가할 수 있다.
class User {
  name = "rarrit";

  sayHi() {
    alert(`${this.name}님 안녕하세요!`);
  }
}

new User().sayHi(); // rarrit님 안녕하세요!

클래스를 정의할 때 <프로퍼티 이름> = <값>을 써주면 간단히 클래스 필드를 만들 수 있으며, 중요한 특징 중 하나는 User.prototype이 아닌 개별 객체에만 클래스 필드가 설정된다는 점이다.

class User {
  name = "rarrit";
}

let user = new User();
alert(user.name); // rarrit
alert(User.prototype.name); // undefined

:pushpin: 6-1) 복잡한 표현식 및 함수 호출 결과 사용

클래스 필드엔 복잡한 표현식이나 함수 호출 결과를 사용할 수 있다.

class User {
  name = prompt("이름을 알려주세요.", "rarrit");
}

let user = new User();
alert(user.name); // rarrit

:pushpin: 6-2) 클래스 필드로 바인딩 된 메서드 만들기

자바스크립트의 this는 동적으로 결정되며 객체 메서드를 여기저기 전달해 전혀 다른 컨텍스트에서 호출하게 되면 this는 메서드가 정의된 객체를 참조하지 않는다. 아래의 예시를 보자

class Button {
  constructor(value) {
    this.value = value;
  }

  click() {
    alert(this.value);
  }
}

let button = new Button("안녕하세요.");

setTimeout(button.click, 1000); // undefined

// 해결 방법
class Button {
  constructor(value) {
    this.value = value;
  }
  click = () => {
    // 화살표 함수 사용

    alert(this.value);
  };
}

let button = new Button("안녕하세요.");

setTimeout(button.click, 1000); // 안녕하세요.

:fire: 정리

  • 클래스 개념
    • 생성자 함수보다 직관적인 문법으로 prototype 기반 메서드를 정의.
    • 내부적으로 함수이며, new 없이 호출 불가.
  • 주요 특징
    • constructor로 객체 초기화, 메서드는 자동 prototype 저장.
    • 내부 엄격 모드 적용, 메서드는 열거되지 않음.
  • 클래스 필드 & getter/setter
    • 필드는 개별 객체에 저장되어 prototype과 독립적.
    • get/set으로 속성 제어 가능, 화살표 함수 사용 시 this 고정.

:pushpin: 참고 문서

태그:

카테고리: ,

업데이트: