개요
클래스는 오브젝트를 만들기 위한 템플릿이다. 데이터와 데이터를 조작하는 코드를 하나로 추상화한다. JS 클래스는 프로토타입을 기반으로 빌드되었지만 ES5 의 클래스 같은 의미와는 다른 문법과 의미를 가진다.
클래스 정의
JS 의 '특별한 함수'이다. JS 에서는 모든 것이 오브젝트로 표현 가능하다는 점을 염두에 두자.
구조
클래스 문법에는 2가지 구성 요소가 있다.
클래스 선언 (Class Declaration
) 알아보기
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
호이스팅
const p = new Rectangle(); // ReferenceError
class Rectangle {
// ...
}
클래스 선언과 함수 선언의 차이는 함수는 선언된 곳 이전부터 사용이 가능한데, 클래스는 선언 코드가 작동한 이후에 new
키워드를 이용해 생성되어야 한다는 것이다. 위의 코드는 ReferenceError
가 나는 예제이다.
호이스팅의 기본 원리를 알고 있다면 어렵지 않게 이해할 수 있는 부분이다.
클래스 표현식 (Class Expression
)
클래스 표현식은 named
와 unnamed
로 나뉜다.
자바스크립트에서
표현식
이라는 개념은 보통 변수에 특정한 표현을 통해 해당 객체를 넣는 것을 말한다. 비슷한 예로 함수 표현식 이 있다.
// unnamed
let Rectangle = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// output: "Rectangle"
// named
Rectangle = class Rectangle2 {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
console.log(Rectangle.name);
// output: "Rectangle2"
named
와 unnamed
의 차이는 클래스 내부의 name
프로퍼티에 값을 변수명과 다르게 입력할 것인지 정도의 차이가 있다.
클래스 바디와 메서드 정의 알아보기
클래스 바디는 {}
내부에 들어간다. 내부에 클래스 멤버인 메서드, 생성자를 정의한다.
Strict mode
클래스 바디는 더 나은 성능을 제공하기 위해 Strict mode 에서 실행된다. 문법을 따르지 않으면 silent errors
가 던져질 것이다. 그리고 몇몇 키워드는 미래의 ECMA Script 를 위해 예약되어 있다.
Constructor
생성자(constructor) 메서드는 class
로 생성된 객체를 생성하고 초기화하기 위한 특수한 메서드이다. 클래스 안에 한개만 존재해야 하고, 그 이상 존재한다면 SyntaxError 가 발생한다.
생성자 내부에서는 super
키워드를 통해 부모의 생성자를 불러오는 것도 가능하다.
정적 초기화 블럭 (Static initialize blocks)
정적 초기화 블럭은 클래스 정적 프로퍼티 를 유연하게 초기화할 수 있게 해준다. 초기화 동안의 구문의 평가와 private scope
접근 권한까지 포함한다.
여러 개의 정적 초기화 블럭이 선언될 수 있으며, 정적 프로퍼티 메서드와 함께 교차되는 것도 가능하다. 모든 정적 아이템은 선언 순서대로 평가된다.
프로토타입 메서드
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
}
const square = new Rectangle(10, 10);
console.log(square.area); // 100
제너레이터 메서드
Iterators and generators 페이지에서도 볼 수 있다.
class Polygon {
constructor(...sides) {
this.sides = sides;
}
// Method
*getSides() {
for (const side of this.sides) {
yield side;
}
}
}
const pentagon = new Polygon(1, 2, 3, 4, 5);
console.log([...pentagon.getSides()]); // [1, 2, 3, 4, 5]
정적 메서드와 프로퍼티
static 키워드로 선언된 메서드는 인스턴스에서는 사용할 수 없고 클래스에서 유틸 메서드처럼 접근해 사용하는 것이 권장된다. 캐시, 고정 환경 설정 혹은 인스턴스 생성 시마다 매번 만들면 중복되는 데이터 등을 관리할 수 있다.
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static displayName = "Point";
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.hypot(dx, dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
p1.displayName; // undefined;
p2.displayName; // undefined; -> 인스턴스에서 접근 불가
p1.distance; // undefined;
p2.distance; // undefined; -> 인스턴스에서 접근 불가
console.log(Point.displayName); // "Point" -> 클래스에서 접근 가능
console.log(Point.distance(p1, p2)); // 7.0710678118654755
프로토타입과 정적메서드와 함께 this
바인딩하기
this 의 값을 클래스 바디에서 불러오게 되면, 기본적으로 undefined
가 반환된다. 그 이유는 클래스 바디에서 "use strict" 가 이미 적용되어 있기 때문이다. 아래의 예제코드를 콘솔에서 실행해보면 쉽게 알 수 있다. this
의 바인딩은 strict
인지 아닌지에 따라 달라진다.
function strict() {
"use strict";
console.log(this);
}
function nonStrict() {
console.log(this);
}
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
const obj = new Animal();
obj.speak(); // the Animal object
const speak = obj.speak;
speak(); // undefined
Animal.eat(); // class Animal
const eat = Animal.eat;
eat(); // undefined
아래와 같이 non-strict
모드를 사용하도록 하는 방식으로 prototype
이나 static method
를 설정하면 this
를 바인딩할 수 있다.
function Animal() {}
Animal.prototype.speak = function () {
return this;
};
Animal.eat = function () {
return this;
};
const obj = new Animal();
const speak = obj.speak;
speak(); // global object (in non–strict mode)
const eat = Animal.eat;
eat(); // global object (in non-strict mode)
인스턴스 프로퍼티
인스턴스 프로퍼티는 무조건 클래스 메서드 안에서 정의되어야 한다.
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
필드 선언
퍼블릭 필드 선언
let
,const
,var
키워드는 필요 없다.- 맨 위쪽(
up-front
)에 먼저 선언해둬야self-documenting
의 효과를 볼 수 있다. - 아래의 예제처럼 기본 값을 설정해둘 수도 있다.
class Rectangle {
height = 0;
width;
constructor(height, width) {
this.height = height;
this.width = width;
}
}
프라이빗 필드 선언
- 클래스 밖에서 프라이빗 필드를 이용하면 참조 에러가 발생한다.
- 클래스 바디에서만 읽거나 쓰여질 수 있다.
- 프라이빗 필드는 일반 필드처럼 나중에 프로퍼티를 이용하여 만들어낼 수 없다.
- 클래스 내부에서 구성하였으므로 이용자는 버전에 따라 바뀔 수 있는 내부 구현에 의존하지 않는다는 것을 확신할 수 있다.
class Rectangle {
#height = 0;
#width;
constructor(height, width) {
this.#height = height;
this.#width = width;
}
}
extends
키워드를 이용한 클래스 상속 (Sub classing)
- 클래스를 다른 클래스의 자식으로 만들기 위해 클래스 선언, 혹은 클래스 표현식에서 사용된다.
- 생성자에서
super()
메서드를 이용해 부모의 생성자를 호출해야 한다.
클래스 상속 예제
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Dog extends Animal {
constructor(name) {
super(name); // call the super class constructor and pass in the name parameter
}
speak() {
console.log(`${this.name} barks.`);
}
}
const d = new Dog("Mitzie");
d.speak(); // Mitzie barks.
함수를 기반으로 한 클래스 상속 예제
위와 비슷하나,
- 부모 클래스를 생성할 때
prototype
으로 공통 메서드를 지정한다. constructor
키워드를 이용해 생성자를 만들지 않는다.
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(`${this.name} makes a noise.`);
};
class Dog extends Animal {
speak() {
console.log(`${this.name} barks.`);
}
}
const d = new Dog("Mitzie");
d.speak(); // Mitzie barks.
// For similar methods, the child's method takes precedence over parent's method
생성자가 없는 일반 함수를 상속하기
생성자가 없는 일반 함수를 상속할 때는 Object.setPrototypeOf() 메서드를 이용하면 가능하다.
const Animal = {
speak() {
console.log(`${this.name} makes a noise.`);
},
};
class Dog {
constructor(name) {
this.name = name;
}
}
// If you do not do this you will get a TypeError when you invoke speak
Object.setPrototypeOf(Dog.prototype, Animal);
const d = new Dog("Mitzie");
d.speak(); // Mitzie makes a noise.
Species
직접 만든 클래스에서 Array
를 상속받아 기본 생성자가 실행된 이후에는 Array
타입을 반환할 수 있다.
class MyArray extends Array {
// 부모 Array 생성자로 species 덮어쓰기
static get [Symbol.species]() {
return Array;
}
}
var a = new MyArray(1, 2, 3);
var mapped = a.map((x) => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
static get [Symbol.species]() { return Array; }
코드가 없었다면, 둘 다true
가 나왔을 것이다.
super
를 통한 상위 클래스 호출
객체의 부모가 가진 메서드를 호출할 수 있다. 이는 프로토타입 기반 상속에 대비되는 장점 중 하나이다. 부모의 메서드와 자신의 메서드 중 선택할 수 있다.
class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(`${this.name} roars.`);
}
}
let l = new Lion("Fuzzy");
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.
믹스인 (Mix-ins)
추상 서브 클래스 혹은 믹스인이라고 불리는 이것은 클래스를 위한 템플릿이다. ECMAScript 클래스는 하나의 상위 클래스만 가질 수 있다.
슈퍼클래스를 인자로 받고 이 슈퍼클래스를 확장하는 서브 클래스를 생성하여 반환하는 함수를 구현하는 방식으로 ECMAScript 에서 믹스인(Mix-ins)을 구현할 수 있다.
var calculatorMixin = (Base) =>
class extends Base {
calc() {}
};
var randomizerMixin = (Base) =>
class extends Base {
randomize() {}
};
위 코드의 Base
에 클래스를 넣으면 된다.
class Foo {}
class Bar extends calculatorMixin(randomizerMixin(Foo)) {}
클래스 재정의
클래스 재정의는 불가능하며, 재정의를 시도하면 SyntaxError
가 발생한다.
레퍼런스
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
'자바스크립트 > 개념' 카테고리의 다른 글
오브젝트 리터럴 ('{}') 이 new Object() 보다 빠른 이유 (0) | 2022.12.22 |
---|---|
Array.prototype.sort() 자세히 알아보기 (0) | 2022.12.20 |
자바스크립트 호이스팅 매우 간단히 정리 (0) | 2022.07.30 |
자바스크립트 DOMContentLoaded vs load (onload) 의 차이 (0) | 2022.07.24 |
자바스크립트 클로저 (Closure) 란 무엇인가? (0) | 2022.07.13 |