자바스크립트 클래스
- ES6 가 나오기 이전에는 프로토타입을 이용해 클래스처럼 구현하는 것이 가능했다.
- ES2015(ES6) 가 나온 이후에는 클래스처럼이 아닌 클래스를 구현하는 것이 가능해졌다.
클래스 기본 문법 살펴보기
class Color {
constructor(r = 0, g = 0, b = 0) {
this.r = r;
this.g = g;
this.b = b;
}
get rgb() {
return `rgb(${this.r}, ${this.g}, ${this.b})`;
}
set rgb(value) {
// TODO ...
}
toString() {
return this.rgb;
}
static fromCSS(css) {
// TODO ...
}
}
let c = new Color(30, 144, 255); // new 키워드를 통한 생성
c = Color.fromCSS("00A"); // 정적 팩토리 메서드를 통한 생성
클래스는 아래의 요소들을 가진다.
- 생성자 (
constructor
)- 보통 여기서 유효성 검사까지 많이 한다.
- 데이터 필드 (
r
,g
,b
) - 접근자 속성 (
get()
,set()
) - 프로토타입 메서드 (
toString()
)- 인스턴스 메서드라고 하지 않는 이유는 프로토타입에서 상속되기 때문이다.
- 정적 메서드 (
fromCSS
)
클래스 정의 방법
// 일반 정의
class Color {}
// 익명 클래스 정의
let Color = class {};
// 클래스 정의를 변수로 참조하기
let C = class Color {};
- 클래스 선언은 함수 선언처럼 호이스팅 되지 않는다.
- 임시 데드존과 같이 호이스팅 된다.
let
,const
와 마찬가지로 전역에서 선언해도 전역 객체의 속성이 아니다.
클래스 생성자의 특징
new
키워드를 사용해야만 생성자 호출이 가능하다.- 생성자 함수를 직접 호출하려하면, 오류가 발생한다.
- 사실
Reflect
를 이용하면 호출이 가능하긴 하다.
ES5 에서 함수를 클래스처럼 이용하는 방식은 사실상
new
키워드 없이도 함수호출이 되어 혼란스러운 버그를 초래하기도 했다.this instanceof Color
와 같은 구문을 통해 예외처리를 해주어야만 했다.
Color(); // TypeError: Class constructor Color cannot be invoked without 'new'
클래스 내부 코드의 특징
- 항상 엄격 모드(
"strict mode"
)이다. - 클래스 내부 속성을 정의할 때 세미콜론이나 컴마를 쓰지 않는다.
- 내부 메서드를 선언적이고 간결하게 정의할 수 있다.
- ES5는
Color.prototype.toString = function toString() {...}
와 같은 방식으로 정의해왔다.
- ES5는
이전보다 메모리 효율이 좋다.
// ES2015 `class` 키워드를 이용한 클래스와 메서드 정의
class Color {
print() {
console.log("color");
}
}
var c = new Color();
console.log(typeof c.print.prototype); // undefined
// ES5 `function` 키워드를 이용한 클래스와 메서드 정의
function ColorFunction() {}
const Color2 = ColorFunction;
Color2.prototype.print = function () {
console.log("color");
};
var c2 = new Color2();
console.log(typeof c2.print.prototype); // object
class
키워드를 이용한 클래스 메서드에는prototype
이 안붙어있다.function
키워드를 이용한 클래스 메서드에는prototype
이 붙어있다.
정적 메서드 코드 살펴보기
class Color {
constructor(r = 0, g = 0, b = 0) {
this.r = r;
this.g = g;
this.b = b;
}
get rgb() {
return `rgb(${this.r}, ${this.g}, ${this.b})`;
}
set rgb(value) {
// TODO ...
}
toString() {
return this.rgb;
}
static fromCSS(css) {
const match = /^#?([0-9a-f]{3}|[0-9a-f]{6});?$/i.exec(css);
if (!match) {
throw new Error("invalid CSS code: " + css);
}
let vals = match[1];
if (vals.length === 3) {
vals = vals[0] + vals[0] + vals[1] + vals[1] + vals[2] + vals[2];
}
return new this(
parseInt(vals.substring(0, 2), 16),
parseInt(vals.substring(2, 4), 16),
parseInt(vals.substring(4, 6), 16)
);
}
}
static
키워드가 붙은 메서드는 개별 인스턴스 메서드가 아닌Color
클래스 자체의 메서드가 된다.
Color.fromCSS = function fromCSS(css) {};
- ES5 식으로는 위와 같이 표현된다. 프로토타입 속성이 아님에 유의하자.
접근자 속성 코드 살펴보기
class Color {
constructor(r = 0, g = 0, b = 0) {
this.r = r;
this.g = g;
this.b = b;
}
get rgb() {
return `rgb(${this.r}, ${this.g}, ${this.b})`;
}
set rgb(value) {
let s = String(value);
let match = /^rgb\((\d{1, 3}),(\d{1, 3}), (\d{1,3})\)$/i.exec(
s.replace(/\s/g, "")
);
if (!match) {
throw new Error(`Invalid rgb color string '${s}'`);
}
}
toString() {
return this.rgb;
}
static fromCSS(css) {
const match = /^#?([0-9a-f]{3}|[0-9a-f]{6});?$/i.exec(css);
if (!match) {
throw new Error("invalid CSS code: " + css);
}
let vals = match[1];
if (vals.length === 3) {
vals = vals[0] + vals[0] + vals[1] + vals[1] + vals[2] + vals[2];
}
return new this(
parseInt(vals.substring(0, 2), 16),
parseInt(vals.substring(2, 4), 16),
parseInt(vals.substring(4, 6), 16)
);
}
}
let c = new Color();
r.rgb = "rgb(30, 144, 255)";
ES5
ES5 에서는 아래와 같이 작성해야 했다.
Object.defineProperty(Color.prototype, "rgb", {
get: function () {
return `${this.r}, ${this.g}, ${this.b}`;
},
set: function (value) {
// ...
},
configurable: true,
});
static 접근자
원한다면 정적인 접근자 속성 정의도 가능하다.
class StaticAccessorExample {
static get cappedClassName() {
return this.name.toUpperCase();
}
}
계산된 메서드 이름
런타임에 결정되는 이름으로 메서드를 만들고 싶을 때 유용하다.
let name = "foo" + Math.floor(Math.random() * 1000);
class SomeClass {
[name]() {
// ...
}
}
- 대괄호 안에 어떤 표현이든 넣을 수 있다.
- 클래스 정의가 평가되는 시점에 식이 평가된다.
- 결과가 문자열이나 심볼이 아닌 경우, 문자열로 반환된다.
- 결과가 메서드 이름으로 사용된다.
정적 메서드
class Guide {
static [6 * 7]() {
// Guide["42"]() -> logging "GOOD"
console.log("GOOD!");
}
}
ES5
var name = "foo" + Math.floor(Math.random() * 1000);
SomeClass.prototype[name] = function () {
// ...
};
서브클래스
- ES5 에서 상속을 구현하는 방식은 꽤 복잡했다.
class
문법이 생기면서 이러한 부분이 많이 개선되었다.
class ClassWithAlpha extends Color {}
extends
를 이용하면, 두 개의 상속 체인이 생성된다. 해당 생성자로 생성된 객체에 대한 병렬상속관계라고 한다. 하나는 생성자 자체에 있으며, 하나는 생성자의 프로토타입 객체에 있다.
생성자 상속 체인
ColorWithAlpha
서브클래스 생성자를 만든다.Color
(슈퍼클래스 생성자 함수)Color
의 프로토타입을 만들어 모든 정적 속성/메서드를ColorWithAlpha
에서 접근 가능하게 한다.Function.prototype
이외의 프로토타입을 갖는 함수의 개념이 등장한다.
프로토타입 체인
- 서브클래스 프로토타입 객체인
ColorWithAlpha.prototype
을 만든다.Color.prototype
을 객체의 프로토타입으로 만들어ColorWithAlpha
로 만든 객체가 슈퍼클래스 속성과 메서드를 상속하도록 한다.
- 생성자 상속 체인은 ES2015 의 새로운 기능이다.
두 개의 체인 살펴보기
ColorWithAlpha
->Color
->Function.prototype
->Object.prototype
ColorWithAlpha.prototype
->Color.prototype
->Object.prototype
const color = new Color(10, 10, 10);
Object.getPrototypeOf(color); // {constructor: ƒ, toString: ƒ}constructor: class Colorrgb: (...)toString: ƒ toString()get rgb: ƒ rgb()set rgb: ƒ rgb(value)[[Prototype]]: Object
Object.getPrototypeOf(color.__proto__); // {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
const colorWithAlpha = new ColorWithAlpha(10, 10, 10, 1);
Object.getPrototypeOf(colorWithAlpha); // Color {constructor: ƒ}
Object.getPrototypeOf(colorWithAlpha.__proto__); // {constructor: ƒ, toString: ƒ}
Object.getPrototypeOf()
메서드로 살펴볼 수 있다.
super
키워드
super
: 서브클래스 생성자에서 상위 클래스의 생성자를 호출하거나 상위 클래스의 속성 및 메서드를 참조하는데 사용할 수 있다.
서브 클래스의 기본(자동 생성) 생성자
명시적 생성자가 없더라도, 슈퍼 클래스의 생성자를 기본적으로 호출한다. 많은 경우에 서브 클래스에 직접 생성자를 작성할 필요가 없다.
// 서브 클래스에 자동 생성된 생성자
constructor(/* 인수들 */) {
super(/* 전달 */)
}
ES5 에서는 아래와 같은 과정을 거쳐야 했다.
var ColorWithAlpha = function ColorWithAlpha() {
Color.apply(this, arguments);
};
ColorWithAlpha.prototype = Object.create(Color.prototype);
ColorWithAlpha.prototype.constructor = ColorWithAlpha;
ColorWithAlpha
에서Color
의 정적 속성과 메서드를 사용할 수 없었다.- 실수하기 쉬운 코드이다.
서브 클래스의 커스텀 생성자
class ColorWithAlpha extends Color {
constructor(r = 0, g = 0, b = 0, a = 1) {
super(r, g, b);
this.a = a; // super() 뒤에 와야 에러가 나지 않는다.
}
}
super()
를 통해 상위클래스의 생성자를 먼저 호출해야this
를 사용할 수 있다.- 엔진마다 다른 에러가 난다.
- ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
- ReferenceError: Must call super constructor before using |this| in
ColorWithAlpha
class constructor - ReferenceError: this is not defined
- 당연히
super()
를 두번 호출해도 에러가 난다.
ES5 에서는 아래와 같이 작성할 수 있었다.
var ColorWithAlpha = function ColorWithAlpha(r, g, b, a) {
this.a = a; // 여기서는 클래스를 먼저 생성하지 않아도 에러가 나지 않는다.
Color.call(this, r, g, b);
};
서브 클래스의 슈퍼 클래스 메서드 접근
ES 2015
class Color {
// ...
brightness() {
return Math.sqrt(
(this.r * this.r * 0.299) +
(this.g * this.g * 0.587) +
(this.b * this.b * 0.114) +
)
}
}
먼저, 슈퍼 클래스의 내용은 위와 같다.
사람의 눈이 빨, 녹, 파의 밝기를 다르게 인식하여 상수가 각각 다르다.
class ColorWithAlpha extends Color {
// ...
brightness(bgColor) {
let result = super.brightness() * this.a;
if (bgColor && this.a !== 1) {
result = (result + bgColor.brightness() * (1 - this.a)) / 2;
}
return result;
}
}
- 밝기 정의는 불투명도를 고려해야 하는
ColorWithAlpha
에서는 조금 다르게 동작해야 한다. Color
클래스의brightness()
를 이용하여,ColorWithAlpha
의brightness()
메서드를 구현했다.
ES5
ColorWithAlpha.prototype.brightness = function brightness(bgColor) {
var result = Color.prototype.brightness.call(this) * this.a;
if (bgColor && this.a !== 1) {
result = (result + bgColor.brightness() * (1 - this.a)) / 2;
}
return result;
};
super
로 간단하게 표현할 수 있는 부분을 길게 적었어야 했다.
var superproto = Object.getPrototypeOf(ColorWithAlpha.prototype);
var result = superproto.brightness.call(this) * this.a;
- 위와 같이
Object.getPrototypeOf()
를 이용하여 참조해도 된다.
정적 메서드 상속
class Color {
// 이전 내용과 동일...
}
class ColorWithAlpha extends Color {
// 이전 내용과 동일...
}
const ca = ColorWithAlpha.fromCSS("#1E90FF");
console.log(String(ca)); // "rgba(30, 144, 255, 1)"
console.log(ca.constructor.name); // "ColorWithAlpha"
console.log(ca instanceof ColorWithAlpha); // true
- 여기서 사용된
fromCSS()
는Color
클래스의 정적메서드인데 어떻게ColorWithAlpha
클래스를 생성했을까?- 이전에 배웠던, 생성자 상속 체인 덕에 가능하다.
ColorWithAlpha
->Color
->Function.prototype
->Object.prototype
ColorWithAlpha.prototype
->Color.prototype
->Object.prototype
class Color {
// ...
static fromCSS(css) {
const match = /^#?([0-9a-f]{3}|[0-9a-f]{6});?$/i.exec(css);
if (!match) {
throw new Error("invalid CSS code: " + css);
}
let vals = match[1];
if (vals.length === 3) {
vals = vals[0] + vals[0] + vals[1] + vals[1] + vals[2] + vals[2];
}
return new this(
parseInt(vals.substring(0, 2), 16),
parseInt(vals.substring(2, 4), 16),
parseInt(vals.substring(4, 6), 16)
);
}
}
new Color
가 아닌new this()
를 이용했기 때문에ColorWithAlpha
에서 호출한 경우, 새ColorWithAlpha
를 반환한다.ColorWithAlpha
에서의this
는ColorWithAlpha
이다.
새 인스턴스를 반환하는 메서드
Array.prototype
의slice
,map
과 같은 메서드는 새 인스턴스를 생성한다.- 이전의
fromCSS
도new this()
를 통해 새로운 인스턴스를 생성했다.
class Color {
constructor(r = 0, g = 0, b = 0) {
this.r = r;
this.g = g;
this.b = b;
}
static fromCSS(css) {
const match = /^#?([0-9a-f]{3}|[0-9a-f]{6});?$/i.exec(css);
if (!match) {
throw new Error("invalid CSS code: " + css);
}
let vals = match[1];
if (vals.length === 3) {
vals = vals[0] + vals[0] + vals[1] + vals[1] + vals[2] + vals[2];
}
return new this(
parseInt(vals.substring(0, 2), 16),
parseInt(vals.substring(2, 4), 16),
parseInt(vals.substring(4, 6), 16)
);
}
halfBright() {
const ctor = this.constructor || Color;
return new ctor(
Math.round(this.r / 2),
Math.round(this.g / 2),
Math.round(this.b / 2)
);
}
}
class ColorSubclass extends Color {}
- 새 인스턴스가 반환하는 타입에 집중해보자.
ColorSubclass.fromCSS()
와colorSubclass.halfBright()
은 각각ColorSubclass
타입을 반환하게 될 것이다.
[Symbol.species]
를 이용하여 일괄적으로 타입 변경해보기
class Color {
constructor(r = 0, g = 0, b = 0) {
this.r = r;
this.g = g;
this.b = b;
}
static fromCSS(css) {
const match = /^#?([0-9a-f]{3}|[0-9a-f]{6});?$/i.exec(css);
if (!match) {
throw new Error("invalid CSS code: " + css);
}
let vals = match[1];
if (vals.length === 3) {
vals = vals[0] + vals[0] + vals[1] + vals[1] + vals[2] + vals[2];
}
const ctor = (this && this[Symbol.species]) || Color;
return new ctor(
parseInt(vals.substring(0, 2), 16),
parseInt(vals.substring(2, 4), 16),
parseInt(vals.substring(4, 6), 16)
);
}
halfBright() {
const ctor =
(this && this.constructor && this.constructor[Symbol.species]) || Color;
return new ctor(
Math.round(this.r / 2),
Math.round(this.g / 2),
Math.round(this.b / 2)
);
}
}
class ColorSubclass extends Color {
static get [Symbol.species]() {
return Color;
}
}
[Symbol.species]
를 통해, 한번에 반환 타입을 변경할 수 있는 패턴을 적용했다.- 이 패턴은 표준 라이브러리에서 종종 사용되는데, 보통 정적 메서드에는 사용되지 않는다.
Color
를 반환하는ColorWithAlpha.fromCSS()
같은 오해의 소지가 있는 코드를 작성할 수 있기 때문이다.
- 이 패턴이 적용된 클래스를 상속하여, 모든 내장 메서드가 어떤 타입을 따르게 할지 오버라이드할 수 있다.
fromCSS()
에서는this && this[Symbol.species]
를 사용하고,halfBright()
에서는this && this.constructor && this.constructor[Symbol.species]
을 사용한 이유는 그냥 다양한 케이스를 보여주기 위해서이다. 서로 바꿔도 에러가 나지 않는다.
[Symbol.species]
테스트
class Base {
constructor(data) {
this.data = data;
}
static get [Symbol.species]() {
return this;
}
// species 패턴을 따르지 않음
static create(data) {
const ctor = this || Base;
return new ctor(data);
}
// species 패턴을 따름
clone() {
const ctor =
(this && this.constructor && this.constructor[Symbol.species]) || Base;
return new ctor(this.data);
}
}
class Sub1 extends Base {}
class Sub2 extends Base {
static get [Symbol.species]() {
return Base;
}
}
const b = Sub1.create(2); // Sub1
console.log(b);
const bclone = b.clone(); // Sub1
console.log(bclone);
const c = Sub2.create(2); // Sub2
console.log(c);
const cclone = c.clone(); // Base
console.log(cclone);
species
패턴을 따르는 경우, 오버라이드 된 타입 (Base
) 을 사용한다.
[Symbol.species]
테스트2 : null
값 반환해보기
class Base {
constructor(data) {
this.data = data;
}
static get [Symbol.species]() {
return this;
}
// species 패턴을 따르지 않음
static create(data) {
const ctor = this || Base;
return new ctor(data);
}
// species 패턴을 따름
clone() {
const ctor =
(this && this.constructor && this.constructor[Symbol.species]) || Base;
return new ctor(this.data);
}
}
class Sub1 extends Base {}
class Sub2 extends Base {
static get [Symbol.species]() {
return null; // Base 클래스에서 작성해둔 기본 값을 사용한다. 일반적인 내장 클래스는 이렇게 동작한다.
}
}
const b = Sub1.create(2); // Sub1
console.log(b);
const bclone = b.clone(); // Sub1
console.log(bclone);
const c = Sub2.create(2); // Sub2
console.log(c);
const cclone = c.clone(); // Base
console.log(cclone);
... || Base
와 같이 코딩한 이유는Array
같은 내장 클래스가 그렇게 하기 때문이다.
내장 객체 상속
- ES2015 에서 내장 객체를 class 로 서브클래싱하는 것이 편해졌다.
class Elements extends Array {
select(source) {
if (source) {
if (typeof source === "string") {
const list = document.querySelectorAll(source);
list.forEach((element) => this.push(element));
} else {
this.push(source);
}
}
return this;
}
style(props) {
this.forEach((element) => {
for (const name in props) {
element.style[name] = props[name];
}
});
return this;
}
}
new Elements()
.select("div")
.style({ color: "green" })
.slice(1)
.style({ border: "1px solid red" });
Array
는 이전에 배웠던species
패턴을 사용한다.[Symbol.species]
를null
로 재정의하면, 체인에서.select()
의 반환 타입이Array
로 변경되어style()
메서드를 찾지 못할 것이다.
super
를 이용할 수 있는 곳
- 메서드에서는
super
키워드를 마음껏 이용할 수 있다. - 프로퍼티에 할당된 함수에서는
super
키워드를 이용할 수 없다.
class SuperClass {
test() {
return "SuperClass's test";
}
}
class SubClass extends SuperClass {
test1() {
return "SubClass's test1: " + super.test();
}
}
SubClass.prototype.test2 = function () {
return "SubClass's test2: " + super.test(); // 에러 발생, `super` 키워드를 사용할 수 없다.
};
const obj = new SubClass();
obj.test1();
obj.test2();
super
클래스를 사용할 수 없는 이유는 속성에 할당된 함수에는 링크가 없기 때문이다.super
는[[HomeObject]]
라는 포함 함수의 내부 필드에 의존한다.- 자바스크립트 엔진은
[[HomeObject]]
필드에서 객체를 가져오고 프로토타입을 가져온 후 아래와 같이 해당 객체에서method
속성을 찾는다.
let method = (the running method);
let homeObject = method.[[HomeObject]];
let proto = Object.getPrototypeOf(homeObject);
let value = proto.foo
3계층 구조 살펴보기
class Base {
test() {
return "Base test";
}
}
class Sub extends Base {
test() {
return "Sub test> " + super.test();
}
}
class SubSub extends Sub {
test() {
return "SubSub test> " + super.test();
}
}
const obj = new SubSub();
console.log(obj.test()); // SubSub test> Sub test> Base test
- 프로토타입 체인은
obj
->SubSub.prototype
->Sub.prototype
->Base.prototype
->Object.prototype
순이다.
잘못된 3계층 구조
function getFakeSuper(o) {
return Object.getPrototypeOf(Object.getPrototypeOf(o));
}
class Base {
test() {
console.log("Base's test");
return "Base test";
}
}
class Sub extends Base {
test() {
console.log("Sub's test");
return "Sub test> " + getFakeSuper(this).test.call(this);
}
}
class SubSub extends Sub {
test() {
console.log("SubSub's test");
return "Subsub test> " + getFakeSuper(this).test.call(this);
}
}
const obj = new SubSub();
console.log(obj.test());
this
는 계속obj
(SubSub
) 이다.obj
(SubSub
) 이기 때문에,getFakeSuper(this).test.call(this)
는Sub.test()
를 계속하여 호출하게 된다.- Stack overflow 가 일어나게 된다.
그림으로 살펴보기
- 위의 그림처럼 각 메서드의
[[HomeObject]]
를 통해 다음 프로토타입을 가져오는 방식이 되어야 한다.
mixin
메서드 주의하기
// 믹스인
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
},
};
// 사용법:
class User {
constructor(name) {
this.name = name;
}
}
// 메서드 복사
Object.assign(User.prototype, sayHiMixin);
// 이제 User가 인사를 할 수 있습니다.
new User("Dude").sayHi(); // Hello Dude!
- 위는 믹스인 코드의 예제이다.
mixin
메서드에서는super
를 사용하면, 복사된 객체의 프로토타입이 아닌 원래HomeObject
의 프로토타입을 계속 사용하므로 주의해야 한다.- 사용한다면, 새로운 계층이 아닌 원래 계층 내에서 계속 작동할 계획일 때 사용하자.
Object.prototype
제거하기
class A {
constructor() {}
}
class B extends Object {
constructor() {
super();
}
}
A.prototype
의 프로토타입 과B.prototype
의 프로토타입 모두Object.prototype
이 된다.- 두 클래스는 사실상 동일하다.
class X extends null {}
const o = new X();
console.log(o.toString); // undefined
console.log(Object.getPrototypeOf(X.prototype) === null); // true
toString()
,hasOwnProperty()
등 오브젝트의 기본 메서드를 원하지 않는다면null
을 상속하면 된다.- 몇몇 환경에서는 오류로 판명되는 경우도 있다.
new.target
함수와 생성자를 호출하는 방법은 두가지가 있다.
- 직접 호출
- 객체 생성의 일부로서 호출 (
super
혹은Reflect.construct
를 통해)
추상 클래스(서브 클래스의 일부로 인스턴스 생성만 허용) 혹은 최종 클래스(서브 클래스를 허용하지 않음)를 만들고 싶을 수 있다.
new.target
은 함수가 어떻게 호출되었는지 알 수 있게 해준다.
기본 사용법
class Base {
constructor() {
console.log(new.target.name);
}
}
new Base(); // "Base"
class Sub extends Base {
constructor() {
super();
}
}
new Sub(); // "Sub"
Sub
에서 super()
로 호출하여 결과가 "Sub"
가 된다.
추상 클래스 만들기
class Shape {
constructor(color) {
if (new.target === Shape) {
throw new Error("Shape can't be directly instantiated");
}
this.color = color;
}
toString() {
return (
"[" +
this.constructor.name +
", slides = " +
this.sides +
", color = " +
this.color +
"]"
);
}
}
class Triangle extends Shape {
get sides() {
return 3;
}
}
class Rectangle extends Shape {
get sides() {
return 4;
}
}
const t = new Triangle("orange");
console.log(String(t)); // [Triangle, slides = 3, color = orange]
const r = new Rectangle("blue");
console.log(String(r)); // [Rectangle, slides = 4, color = blue]
const s = new Shape("red"); // ERROR
- 다만 생성자 호출 없이
Object.create()
를 이용한다면, 프로토타입으로 직접 사용하는 객체를 만들 수 있다.
파이널 클래스 만들기
class Thingy {
constructor() {
if (new.target !== Thingy) {
throw new Error("Thingy subclasses aren't supported.");
}
}
}
class InvalidThingy extends Thingy {}
console.log("Creating Thingy...");
const t = new Thingy(); // 작동
console.log("Creating InvalidThingy...");
const i = new InvalidThingy(); // ERROR: "Thingy subclasses aren't supported"
new Thingy()
생성자로 생성되지 않으면,Error
를 던진다.new Thingy()
생성자로 객체 생성 시에는new.target
이Thingy
인 것을 알 수 있다.
호출된 방식에 따라 바뀌는 함수
const TwoWays = function TwoWays() {
if (!new.target) {
console.log("Called directly; using 'new' instead");
return new TwoWays();
}
console.log("Called via 'new'");
};
console.log("With new:");
let t1 = new TwoWays();
// "Called via 'new'"
console.log("Without new:");
let t2 = TwoWays();
// "Called directly; using 'new' instead"
// "Called via 'new'"
new
키워드를 사용하지 않는 경우new.target
이 비어있다는 특성을 이용한 코드이다.new.target
의 결과가undefined
가 된다.
클래스 선언 vs 클래스 표현식
// 클래스 선언
class Class1 {}
// 익명 클래스 표현식
let color = class {};
// 명명된 클래스 표현식
let C = class Color {};
- 3가지 방식으로 클래스를 사용할 수 있다.
- 표현식이 유효한 곳에 클래스 정의가 나올 수 있다.
클래스 선언
function
선언과 유사한 점- 스코프에 클래스 이름을 추가한다.
- 끝에 세미콜론을 붙이지 않아도 된다.
function
선언과 다른 점let
과const
처럼 절반만 호이스트되며, 임시 데드존에 참여한다.let
과const
처럼 전역객체의 속성이 아닌 전역을 만든다.
Bar = 100;
class Bar {}
// Uncaught ReferenceError: Cannot access 'Bar' before initialization
- 반만 호이스팅된다.
class Bar {}
console.log(typeof this.Bar); // undefined
- 전역 객체 프로퍼티를 건드리지 않으면서, 전역을 만든다.
let
,const
와 같다.
클래스 표현식
function
표현식과 유사한 점- 명명된 방식과 익명 방식이 모두 있다.
- 생성자가 자동으로 생성된다.
- 메서드에서
name
을 사용하여 메서드 이름을 사용하거나,Class.name
을 이용하여 클래스 이름을 사용할 수 있다. - 끝에 세미콜론을 붙여야 한다.
- 대부분의 경우에는 자동 세미콜론 삽입이 처리해주긴 한다.
let name = "foo" + Math.floor(Math.random() * 1000);
const C = class TheClass {
[name]() {
console.log(
"This is the method " + name + " in the class " + TheClass.name
);
}
};
console.log(typeof TheClass); // undefined
console.log(typeof C); // function
const theClass = new C();
theClass.foo655(); // This is the method foo655 in the class TheClass
액션 플랜
생성자 함수를 사용할 바에는 클래스를 이용하자.
prototype
을 이용한 상속은 문법도 복잡하고 실수하기 쉽다.class
를 이용하자.
'자바스크립트 > 웹개발자를 위한 자바스크립트의 모든 것' 카테고리의 다른 글
웹 개발자를 위한 자바스크립트의 모든 것 6장 이터러블, 이터레이터, 제너레이터 (0) | 2023.01.16 |
---|---|
웹 개발자를 위한 자바스크립트의 모든 것 5장 새로운 객체 기능 (0) | 2023.01.02 |
웹 개발자를 위한 자바스크립트의 모든 것 3장 새로운 함수 기능 (0) | 2022.12.22 |
웹 개발자를 위한 자바스크립트의 모든 것 2장 let, const 정리 (0) | 2022.12.18 |
웹 개발자를 위한 자바스크립트의 모든 것 1장 ES2015 - ES2020 정리 (0) | 2022.12.17 |