1. class
Class를 사용하면 객체 지향 프로그래밍에서 사용되는 다양한 기능을 자바스크립트에서도 사용 할 수 있습니다.
class User {
constructor({name}){
this.name = name;
}
welcome(){
console.log(`Welcome ${this.name}`)
}
}
let user1 = new User({name : 'Bob'});
user1.welcome(); // 'Welcome Bob'
클래스를 new 와 함께 호출하면 다음과 같은 일이 일어납니다.
1. 새로운 객체가 생성됩니다.
2. 넘겨받은 인수와 함께 constructor가 자동 실행됩니다.
constructor() 는 new에 의해 자동 호출되므로, 별다른 동작없이 객체를 초기화 할 수 있습니다.
2. 클래스는 함수이다.
console.log(typeof User) // function
class가 함수라는 증거입니다.
class User {...} 문법 구조의 동작은 위 그림과 같습니다.
1. User라는 함수를 만듭니다. 함수의 본문은 생성자 메서드인 constructor에서 가져옵니다.
생성자 메서드가 없다면, 본문이 비워진 함수를 만듭니다.
2. 클래스 내에서 정의한 메소드를 User.prototype에 저장합니다.
class User {
constructor({name}){
this.name = name;
}
welcome(){
console.log(`Welcome ${this.name}`)
}
}
let user1 = new User({name : 'Bob'});
console.log(user1.__proto__) // {constructor: ƒ, welcome: ƒ}
new User 호출로 객체를 만들었습니다.
user1 인스턴스의 [[Prototype]]은 User.prototype 입니다.
prototype 상속을 통해
인스턴스 객체에서 User.prototype의 호출할 수 있습니다.
console.log(User.prototype.constructor === User) // true
console.log(user1.__proto__ === User.prototype) // true
이러한 원리로 인스턴스 객체에서 class에 정의한 메서드에 접근할 수 있습니다.
3. class는 순수 함수로 class를 구현한 것과 엄연히 다르다.
순수 함수로도 class역할을 하는 함수를 선언 할 수 있지만,
다음과 같은 중요한 차이점들이 있습니다.
1. class로 만든 함수에는 특수 내부 프로퍼티인 [[ IsClassConstructor ]] : true 가 있습니다.
이 프로퍼티는 다음과 같은 상황에 class를 구별하는 데 사용됩니다.
- class 생성자 함수를 new 키워드와 함께 호출했는지 유무에 따라 에러가 발생합니다.
- class를 문자열로 형변환 하면 class... 로 시작하는 문자열이 됩니다.
2. class에 정의된 메서드는 열거할 수 없습니다.
class의 prototype 프로퍼티에 추가된 매서드의 enumerable 플래그가 false이기 때문입니다.
3. class는 항상 엄격 모드로 실행됩니다.
class 생성자 코드 내부에는 자동으로 엄격 모드가 적용됩니다.
4. class 표현식
class도 표현식으로 만들 수 있고,
기명 함수 표현식과 유사하게 이름을 붙일 수 있으며,
동적으로 생성하는 것도 가능합니다.
let User = class { // class 표현식
constructor({name}){
this.name = name;
}
welcome(){
console.log(`Welcome ${this.name}`)
}
}
let Person = class ImClass { // 기명 class 표현식
imClass(){
console.log(ImClass)
}
}
new Person().imClass()
// class ImClass {
// imClass() {
// console.log(ImClass);
// }
// }
function makeDynamicClass(msg){
return class{
speak(){
console.log(msg)
}
}
}
let Dynamic = makeDynamicClass('Im Dynamic') // 동적 생성
new Dynamic().speak() // 'Im Dynamic'
5. class의 getter, setter, computed property
class도 getter, setter, computed property를 지원합니다.
class User {
constructor({name}){
this.name = name;
}
get name(){
return this._name;
}
set name(value){
if(typeof value !== 'string'){
console.log('문자열만 입력하세요!')
return
}
this._name = value;
}
}
let user1 = new User({name : 'Bob'});
console.log(user1.name); // Bob
let user2 = new User({name : 1}) // '문자열만 입력하세요'
console.log(user2.name) // undefined
getter와 setter도 User.prototype에 정의됩니다.
class User { // class 표현식
constructor({name}){
this.name = name;
}
[`hello`](){
console.log(`hello ${this.name}`);
}
}
let user1 = new User({name : `Bob`});
user1.hello() // hello Bob
computed property도 사용 가능합니다.
6. class field (클래스 필드)
클래스 필드 문법을 사용하면 어떤 종류의 프로퍼티도 추가할 수 있습니다.
class User {
constructor(name){
this.name = name
}
classFieldHi = () =>{ // class field
console.log('hello ' + this.name)
}
}
let user1 = new User('Bob');
6-1. losing this
class User {
constructor(name){
this.name = name
}
hi(){
console.log('hello ' + this.name)
}
}
let user1 = new User('Bob');
setTimeout(user1.hi, 1000) // hello ??? --> 원하는 결과가 나오지 않습니다.
자바스크립트의 this가 동적으로 결정되기 때문에
위 코드의 맨 밑줄과 같이 객체의 메서드를 여기저기 전달해 전혀 다른 컨텍스트에서 호출하면
this는 메서드가 정의된 객체를 참조하지 않습니다.
위와 같은 losing this 문제를 해결하기 위해 다음과 같은 방법을 사용할 수 있습니다.
1. 래퍼 함수 전달하기.
setTimeout(()=> user1.hi(), 1000) // hello Bob
2. 생성자 안 등에서 메서드를 객체에 바인딩하기.
3. 위 코드 예시와 같이 class field 사용하기.
class User {
constructor(name){
this.name = name
}
hi(){
console.log(`hello ${this.name}`)
}
classFieldHi = () =>{ // class field
console.log(`hello ${this.name}`)
}
}
let user1 = new User('Bob');
console.log(User.prototype) // { constructor: ƒ, hi: ƒ }
console.log(user1) // { name: 'Bob', classFieldHi: ƒ }
setTimeout(user1.hi, 1000) // hello
// 위 경우에는 this는 window 입니다.
// window.name 은 존재하지 않습니다.
setTimeout(user1.classFieldHi, 1000) // hello Bob
// classFieldHi = () => { ... } 은 각 인스턴스에 독립적인 함수를 만듭니다.
// 함수의 this를 class 객체에 바인딩시켜줍니다.
// 원하는 결과를 얻을 수 있습니다.
위 코드에서 알 수 있는 것처럼
일반적인 메소드는 클래스의 prototype 프로퍼티에 저장되지만,
클래스 필드는 인스턴스 객체에만 저장됩니다.
클래스 필드의 이러한 특징을 이용하면
class 객체에 this를 바인딩 할 수 있고 원하는 결과를 얻을 수 있습니다.
6-2. 클래스 필드의 단점.
클래스 필드에는 단점이 존재합니다.
1. 클래스 필드는 인스턴스 객체에만 저장되기 때문에 클래스 상속을 사용할 수 없습니다.
2. 클래스 필드는 각 인스턴스 마다 매번 새롭게 생성되기 때문에 메모리를 더 차지하게 됩니다.
3. 테스트케이스 작성시 spyOn( class.prototype, '메소드명' ) 으로 mocking할 수 없습니다.
(3번 테스트 케이스의 대한 내용은 추후 테스트 코드에 대한 지식이 생겼을 때 추가하도록 하겠습니다!)
위와 같은 이유로 특정 컨텍스트에 this를 바인딩 하려는 명확한 목적이 아니라면,
클래스 필드 사용은 지양하는 것이 좋을 것 같습니다.
요약
1. class는 함수이며, class 내에 정의된 메소드들은 class.prototype 객체에 추가된다.
2. class의 constructor 생성자 메서드는 인스턴스 생성시 객체를 초기화 한다.
3. class와 new를 사용하여 만든 인스턴스 객체는 class명.prototype을 상속 받는다.
4. 위 내용에 따라 인스턴스 객체에서 class에 정의된 메소드를 사용할 수 있다.
5. class 생성자에는 [[ IsClassConstructor ]] : true 라는 내부 프로퍼티가 있으며,
여러 상황에서 class를 구별하는데에 사용된다.
또한 class의 메서드는 열거할 수 없고, class는 항상 엄격 모드로 실행된다.
6. 클래스도 표현식 내부에서 정의, 전달, 반환, 할당할 수 있다.
7. class에서도 getter, setther, computed property를 사용할 수 있다.
8. class field 문법은 losing this 와 같은 문제를 해결 해 줄 수 있지만,
class.prototype에 추가 되지 않고 생성되는 인스턴스 객체 마다 새롭게 생성된다.
이에 따라 메모리문제, 테스트 케이스 작성 시 특정 함수 사용 불가능 과 같은 문제가 발생된다.
특정 컨텍스트에 this 바인딩을 하려는 명확한 목적이 있을 대만 사용하는 것이 바람직하다.
'Frontend > JS.info 정리' 카테고리의 다른 글
class - static method, property (0) | 2022.06.22 |
---|---|
class - class extends (상속) (0) | 2022.06.21 |
Prototype - 프로토타입 메서드 (0) | 2022.06.18 |
Prototype - 내장 객체의 프로토타입 (0) | 2022.06.18 |
Prototype - 함수의 prototype 프로퍼티 (0) | 2022.06.17 |