[모던자바스크립트] 클래스 정리
클래스는 constructor와 메소드가 들어간다. new 연산자를 사용해서 객체를 생성하면 constructor가 자동으로 호출되고 메소드는 prototype에 들어간다.
정확히 말하면 밑의 예제에서 class 문법이 하는 일은 다음과 같다.
- MyClass라는 이름을 가진 함수를 만든다. 함수의 본문은 constructor의 내용으로 채워진다.
- 클래스의 메서드를 MyClass.prototype에 저장한다.
class MyClass {
// 여러 메서드를 정의할 수 있음
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
const myClass = new MyClass();
클래스는 함수이다.
alert(typeof User); // function
클래스와 생성자 함수의 차이점
- class로 만든 함수에는 특수 내부 프로퍼티
[[FunctionKind]]
라는 것이 있어서new
연산자와 함께 호출하지 않으면 에러가 발생한다. - 클래스의 메서드는 열거할 수 없다.
- 클래스는 항상 엄격모드로 실행된다.
클래스 필드
클래스를 정의할 때 프로퍼티 '<프로퍼티 이름> = <값>'을 써주면 간단히 클래스 필드를 만들 수 있다.
클래스 필드의 중요한 특징은 prototype이 아닌 개별 객체에 클래스 필드가 설정된다는 점이다.
클래스 필드는 생성자가 끝난 뒤에 처리된다.
클래스 필드를 이용하면 this를 객체에 binding 해서 사용할 수 있다.
class Button {
constructor(value) {
this.value = value;
}
click = () => {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // hello
상속
예를 들어 다음과 같이 상속을 하면,
class Rabbit extends Animal {
hide() {
alert(`${this.name} 이/가 숨었습니다!`);
}
}
let rabbit = new Rabbit("흰 토끼");
rabbit.run(5); // 흰 토끼 은/는 속도 5로 달립니다.
rabbit.hide(); // 흰 토끼 이/가 숨었습니다!
Rabbit.prototype.[[Prototype]]
을 Animal.prototype
으로 설정한다.
오버라이딩
부모 메서드를 기반으로 일부 기능만 변경을 하고 싶을 때 super를 사용한다.
class Rabbit extends Animal {
hide() {
alert(`${this.name}가 숨었습니다!`);
}
stop() {
super.stop(); // 부모 클래스의 stop을 호출해 멈추고,
this.hide(); // 숨습니다.
}
}
화살표 함수에는 자신의 this나 super가 없기 때문에 외부 함수에서 super를 가져온다.
constructor 오버라이딩
상속 클래스의 생성자에서는 반드시 super(...args);
를 호출해야한다.
자바스크립트에서는 상속 클래스의 생성자 함수와 일반 생성자 함수를 [[ConstructorKind]]:"derived"
프로퍼티를 사용해서 구분한다.
일반 클래스의 생성자 함수와 상속 클래스의 생성자 함수의 차이는
- 일반 클래스는 new 와 함께 실행되면 빈 객체가 만들어지고 this에 이 객체를 할당한다.
- 상속 클래스가 new와 함께 실행되면 상속 클래스의 생성자 함수는 빈 객체를 만들고 this에 이 객체를 할당하는 일을 부모 클래스의 생성자 함수가 처리해주길 기다린다.
따라서 상속 클래스의 생성자에서는 super를 호출해 부모 생성자를 실행해주어야한다. 아니면 this가 만들어지지 않아 에러가 발생한다.
엥 그러면 super의 constructor의 내용이 자식이 된다는건가? 상속이 아니네?
class Animal {
constructor(name="animal") {
this.speed = 0;
this.name = name;
}
a() {
}
}
class Rabbit extends Animal {
constructor(nickname) {
super();
this.nickname = nickname;
this.name = name;
}
}
let rabbit = new Rabbit("흰 토끼");
const sonRabbit = Object.create(rabbit);
sonRabbit.age = 3;
console.log(rabbit) // Rabbit { speed: 0, name: 'animal', nickname: '흰 토끼' }
console.log(sonRabbit) // Rabbit { age: 3 }
console.log(sonRabbit.nickname) //흰 토끼
필드 오버라이딩
class Animal {
name = 'animal'
constructor() {
alert(this.name); // (*)
}
}
class Rabbit extends Animal {
name = 'rabbit';
}
new Animal(); // animal
new Rabbit(); // animal
class Animal {
showName() { // this.name = 'animal' 대신 메서드 사용
alert('animal');
}
constructor() {
this.showName(); // alert(this.name); 대신 메서드 호출
}
}
class Rabbit extends Animal {
showName() {
alert('rabbit');
}
}
new Animal(); // animal
new Rabbit(); // rabbit
근데 필드값은 생성자가 끝나고 처리된다고 한거 아닌가?
클래스 필드를 지정할 때 실행 순서가 다음과 같다.
- 상속받지 않은 부모 클래스는 생성자 실행 이전에 초기화된다.
- 부모 클래스가 있는 경우 super() 실행 직후에 초기화된다.
자바스크립트는 오버라이딩시 필드와 메서드의 동작 방식이 다르다.
이런 문제는 필드를 부모 생성자에서 사용할 때만 발생한다.
super의 동작 과정
단순하게 생각하면 super.method()
를 호출하면 this.__proto__.method()
를 통해서 부모 객체의 method()
를 찾을 수 있을 것 같지만 실제로 super는 이런 방식으로 동작하지 않는다.
let animal = {
name: "동물",
eat() {
alert(`${this.name} 이/가 먹이를 먹습니다.`);
}
};
let rabbit = {
__proto__: animal,
eat() {
// call을 사용해 컨텍스트를 옮겨가며 부모(animal) 메서드를 호출합니다.
this.__proto__.eat.call(this); // (*)
}
};
let longEar = {
__proto__: rabbit,
eat() {
// longEar를 가지고 무언가를 하면서 부모(rabbit) 메서드를 호출합니다.
this.__proto__.eat.call(this); // (**)
}
};
longEar.eat(); // RangeError: Maximum call stack size exceeded
위와 같이 했을 때 에러가 나는데 그 이유는 rabbit.eat() 안에서 this가 longEar이기 때문에 무한루프에 빠지기 때문이다.
이런 문제는 this만으로 해결할 수 없기 때문에 특별한 전용 프로퍼티 [[HomeObject]]
가 있다.
[[HomeObject]]
[[HomeObject]]
super가 부모 프로토타입과 메서드를 찾는 것을 돕는다.
그런데 [[HomeObject]]
를 이용하면 메서드가 객체를 기억하기 때문에 binding이 된 함수를 변경할 수 없다.
대신 [[HomeObject]]
는 super 내부에서만 유효하다.
함수 프로퍼티가 아니라 메서드 형태로 사용해야 [[HomeObject]]
가 설정이 되기 때문에 에러가 발생하지 않는다.
정적 메서드 정적 프로퍼티
프로토타입이 아니라 클래스 함수 자체의 메서드를 static
키워드를 사용해 만들 수 있다.
private, protected 프로퍼티
객체지향에서 가장 중요한 원리 중 하나는 내부와 외부 인터페이스를 구분 짓는 것이다.
자바스크립트에는 두가지 타입의 프로퍼티가 있다.
- public: 어디서든지 접근이 가능하며 외부 인터페이스를 구성한다.
- private: 클래스 내부에서만 접근할 수 있고 내부 인터페이스를 구성할 때 쓰인다.
protected 프로퍼티
protected 프로퍼티는 자신과 자손 클래스에서만 접근을 허용한다.
자바스크립트는 protected 필드를 지원하지 않지만 모방해서 사용하는 경우가 많다.
protected 프로퍼티명 앞에는 _
이 붙여서 사용한다.
private 프로퍼티
private 프로퍼티는 #
으로 시작한다. protected 프로퍼티와 달리 private 필드는 언어 자체에 의해서 강제된다.
내장 클래스 확장
배열, 맵 같은 내장 클래스를 확장할 수 있다.
// 메서드 하나를 추가합니다(더 많이 추가하는 것도 가능).
class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
}
let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false
let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false
map이나 filter 등을 사용할 때 반환 값이 그냥 배열이 아니라 위에서 만든 PowerArray가 되었다. map이나 filter에서 객체를 구현할 때 객체의 constructor 프로퍼티를 사용하기 때문에 construcotr인 PowerArray에 따라서 객체를 만들 수 있다.
Symbol.species를 이용하면 map, filter 등에서 메서드를 호출할 때 만들어지는 개체의 생성자를 지정할 수 있다.
class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
// 내장 메서드는 반환 값에 명시된 클래스를 생성자로 사용합니다.
static get [Symbol.species]() {
return Array;
}
}
let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false
// filter는 arr.constructor[Symbol.species]를 생성자로 사용해 새로운 배열을 만듭니다.
let filteredArr = arr.filter(item => item >= 10);
// filteredArr는 PowerArray가 아닌 Array의 인스턴스입니다.
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
내장 객체와 정적 메서드 상속
내장 객체는 자체 정적 메서드를 갖는다. 일반적으로 한 클래스가 다른 클래스를 상속 받으면 정적 메서드와 그렇지 않은 메서드를 모두 상속 받는다.
그런데 내장 클래스는 정적 메서드를 상속 받지 못한다.
위 다이어그램에서 보면 Date와 Object를 이어주는 링크가 없기 때문에 정적 메서드를 사용하지 못한다.
그냥 프로터타입을 이용해서 쓰면 되는데 정적 메서드를 만든 이유가 뭐지?
믹스인
여러개의 클래스를 상속하고 싶을 때 믹스인을 사용한다.
믹스인은 다른 클래스를 상속 받을 필요 없이 이 클래스들에 구현되어 있는 메서드를 담고 있는 클래스이다.
// 믹스인
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!
위의 예제에서처럼 사용할 수 있다. 프로토타입에 믹스인을 넣는 방식으로 사용된다. 따로 문법이 있는 것은 아닌 것 같다.
Author And Source
이 문제에 관하여([모던자바스크립트] 클래스 정리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@j03y14/모던자바스크립트-클래스-정리저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)