본문 바로가기
JavaScript

클래스 (1)

by oncerun 2022. 3. 2.
반응형

 

 

Class

 

자바스크립트는 프로토타입 기반 객체지향 언어이다. 

 

프로토 타입 기반 객체지향 언어는 클래스가 필요 없는 객체지향 프로그래밍 언어이다. ES5에서는 클래스 없이도 생성자 함수와 프로토타입을 통해 객체지향 언어의 상속을 구현할 수 있다.

 

하지만 클래스 기반에 익숙한 우리들은 프로토타입 기반 프로그래밍 방식에 혼란을 느낄 수 있으며( 본인이 프로토타입에 이해가 매우 어려웠다.), 이는 하나의 자바스크립트를 이해하기 어렵게 하는 하나의 장벽처럼 인식이 되었다. 

 

 

ES6에서 도입된 클래스는 자바, C#과 같은 클래스 기반 객체지향 프로그래밍에 익숙한 프로그래머가 더욱 빠르게 학습할 수 있도록 새로운 객체 생성 메커니즘을 제시합니다. 

그렇다고 ES6의 클래스가 기존의 프로토타입 기반 객체지향 모델을 폐지하고 새롭게 클래스 기반 객체지향 모델을 제공하는 것은 아닙니다. 

 

사실 클래스는 함수이며, 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용할 수 있는 문법적 설탕이라고 볼 수 있습니다. 

 

다만 클래스와 생성자 함수는 모두 프로토타입 기반의 인스턴스를 생성하지만 정확히 동일하게 동작하지는 않습니다. 

클래스는 생성자 함수보다 엄격하며 생상자 함수에서는 제공하지 않는 기능도 제공합니다.

 

클래스와 생성자 함수의 차이

 

  • 클래스를 new 연산자 없이 호출하면 에러가 발생합니다. 하지만 생성자 함수를 new 연산자 없이 생성하면 일반 함수로써 호출하게 됩니다.
  • 클래스는 extends, super 키워드를 제공합니다. 하지만 생성자 함수에서는 지원하지 않습니다.
  • 클래스는 호이스팅이 발생하지 않는 것처럼 동작한다. 하지만 생성자 함수는 함수 선언문은 함수 호이 스팅이 발생하고 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅이 발생합니다.
  • 클래스 내의 모든 코드에는 암묵적으로 strict mode가 지정되어 실행되며 strict mode를 해제할 수 없다. 하지만 생성자 함수는 암묵적으로 strict mode가 지원되지 않습니다.
  • 클래스의 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumerable]]의 값이 false이다. 즉 열거가 불가능하다. (음.. 이터러블과 다른 개념인가?)

 

객체를 생성하는 방식에 있어 많은 차이가 발생한다. 이는 기존 생성자 함수를 통해 객체를 생성하는 방식에 문법적 설탕을 부여한 것으로 보기보다는 새로운 객체 생성 메커니즘으로 보는 것이 맞을 듯하다.

 

 

클래스 정의

 

  class 키워드를 사용하며 관습적으로 파스칼 케이스를 사용한다.

class Person{}

 

 클래스는 함수라고 했다. 자바스크립트에서 함수는 변수에 할당할 수 있다.

const Person = class {};

const Person = class MyClass {};

 

이는 클래스가 값으로 사용할 수 있는 일급 객체라는 것을 의미한다. 클래스는 일급 객체로서 다음과 같은 특징을 갖는다.

 

  • 이름이 없는 리터럴로 생성할 수 있다. 이는 호이스팅이 발생하지 않고 런타임에 소스코드 평가를 통해 생성되어야 한다. 따라서 클래스는 런타임에 생성이 가능하다.
  • 변수나 자료구조에 저장할 수 있다. (클래스를 정의하고 저장했다 필요할 때 정의하고 인스턴스를 만든다?)
  • 함수의 매개변수에게 전달할 수 있다.
  • 함수의 반환 값으로 생성할 수 있다. 

자바스크립트가 어려운 점은 활용방법이 머릿속으로 쉽게 떠오르지 않아서인 것 같다. 천천히 알아보자.

 

 * 리터럴 : 변수 및 상수에 저장되는 값 자체

 

클래스의 코드 블록 내부에는 0개 이상의 메서드만 정의할 수 있다. 

클래스 내부에 정의할 수 있는 메서드는 constructor (생성자), 프로토타입 메서드, 정적 메서드의 세 가지가 있다.

 

//클래스 선언문
class Person{
	
    //생성자
    constructor(name){
    	this.name = name;  
    }
    
    // 프로토타입 메서드
    sayHi(){
    	console.log('Hi');
    }
    
    static sayHello(){
    	console.log('Hello');
    }
    
}

const me = new Person('Bie');

// 정적 메서드 호출
Person.sayHello();

 

( 의문이 든 게 Angular에서는 this.name을 저렇게 사용하면 에러가 발생한다. class에 name이라는 프로퍼티가 정의되지 않았기 때문이다....)

 

 

기존의 생성자 함수에 정의한 내용을 constructor에 정의하였다.

 

프로토타입 메서드 정의를 하는 부분이 변경되었다.

 

( Person.prototyep.sayHi = function(){...}  =>  sayHi(){...} )

 

정적 메서드를 정의하는 부분도 static이라는 키워드를 사용했다.

 

클래스 선언문으로 정의한 클래스는 소스 코드 평가과정을 통해 런타임 이전에 평가되어 함수 객체를 생성한다. 

이때 클래스가 평가되어 생성된 함수 객체는 생성자 함수로서 호출할 수 있는 함수인 constructor를 의미한다.

이후 함수 정의가 평가되어 함수 객체를 생성하는 시점에 프로토타입도 더불어 생성된다.

 

단 클래스를 정의 이전에 참조할 수 없다.

console.log(Person);   //ReferenceError

class Person{};

 

클래스는 호이스팅이 안 되는 것일 까?  그렇지 않다. 다음 코드를 보자

const Person = 'Person';

{
    console.log(Person);
    
    class Person{};
	
}

 

만약 호이스팅이 발생하지 않는다면 전역 컨텍스트의 선언적 환경 레코드에 존재하는 Person 식별자를 스코프 체인으로 찾아 'Person'이라는 값을 출력해야 한다. 하지만 결과는 ReferenceError가 발생한다.

이는 클래스 선언문도 호이스팅이 발생함을 의미하며 let, const처럼 TDZ에 빠지기 때문에 참조하지 못하는 것으로 볼 수 있다. 

 

자바스크립트에서 var, let, const, function, function*, class 키워드를 사용하여 선언된 모든 식별자는 호이스팅된다. 

소스 코드 평가 시점 즉 런타임 이전에 먼저 실행되기 때문이다.

 

 

new 키워드를 통해 인스턴스를 생성해보자.

class Person{};

const me = new Person();

만약 new 키워드 없이 생성하려고 하면 에러가 발생한다. 이는 클래스는 인스턴스를 생성하는 것이 유일한 존재 이유이기 때문이다.

 

 

(앵귤러를 통해 컴포넌트를 정의할 때 클래스 필드를 정의했었다.  왜 위에서는 생성자 내부에 정의를 하나 했더니

https://github.com/tc39/proposal-class-fields 

 

GitHub - tc39/proposal-class-fields: Orthogonally-informed combination of public and private fields proposals

Orthogonally-informed combination of public and private fields proposals - GitHub - tc39/proposal-class-fields: Orthogonally-informed combination of public and private fields proposals

github.com

TC39에서 구현이 진행되고 있음을 확인했고 타입 스크립트 3.8부터 클래스 필드를 실험할 수 있다고 한다. 

그래서 사용이 가능한 것이었구나.. )

 

 

 

1. constructor 

 

constructor는 인스턴스를 생성하고 초기화하기 위한 메서드이다.

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

클래스는 평가되어 함수 객체가 된다. 이는 함수 객체 고유의 프로퍼티를 모두 갖고 있다. 

 

모든 함수 객체가 가지고 있는 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor 프로퍼티는 클래스 자신을 가리킨다. 이는 클래스가 인스턴스를 생성하는 생성자 함수라는 것을 의미한다. 

클래스의 생성자 내부에서 this에 추가한 name 프로퍼티가 생성된 인스턴스의 프로퍼티로 추가된 것을 확인할 수 있다.

즉 this는 생성될 인스턴스를 가리킨다.

이 클래스와 생성된 인스턴스에 constructor는 메서드로 평가되지 않기에 보이지 않는다. 

이는 클래스가 평가되어 생성한 함수 개체 코드의 일부가 되는데, 다시 말해 클래스 정의가 평가되면

constructor의 기술된 동작을 하는 함수 객체가 생성된다.

 

만약 constructor를 생략하면 빈 constructor가 암묵적으로 정의되며, 생략된 클래스는 빈 객체를 생성한다.

 

 

 

2. 프로토타입 메서드

 

클래스 몸체에서 정의한 메서드는 인스턴스의 프로토타입에 존재하는 프로토 타입 메서드가 된다.

인스턴스는 프로토타입 메서드를 상속받아 사용할 수 있다.

 

프로토타입 체인은 기존의 모든 객체 생성 방식뿐만 아니라 클래스에 의해 생성된 인스턴스에도 동일하게 적용된다. 

생성자 함수의 역할을 클래스가 대신할 뿐이다. 이처럼 클래스는 생성자 함수와 동일하게 인스턴스를 생성하는 생성자 함수로 볼 수 있고 이는 프로토타입 기반의 객체 생성 메커니즘이다.

 

 

3. 정적 메서드와 프로토타입 메서드 차이

 

함수 호출 방식과 this 바인딩을 기억해보자. 메서드 내부의 this는 메서드를 소유한 객체가 아니라 메서드를 호출한 객체에 바인딩이 된다.  프로토타입 메서드는 인스턴스로 호출해야 하기에 프로토타입 메서드 내부의 this는 프로토타입 메서드를 호출한 인스턴스를 가리킨다. 

 

정적 메서드는 클래스로 호출해야 한다. 정적 메서드 내부의 this는 인스턴스가 아닌 클래스를 가리킨다.  

즉 프로토 타입 메서드와 정적 메서드 내부의 this 바인딩이 다르다.

 

따라서 인스턴스 내부 프로퍼티를 활용하는 메서드라면 프로토타입 메서드로 정의해야 하며 내부 인스턴스 프로퍼티를 참조할 필요가 없다면 정적 메서드를 고려할 수 있다.

 

정적 메서드는 애플리케이션 전역에서 사용할 유틸리티 함수를 전역 함수로 정의하지 않고 메서드로 구조화할 때 유용하다.(매우 좋다고 생각한다. 현재 프로젝트에 사용하는 것을 고려해볼 만한 것 같다.)

 

여기까지 정리하길 클래스 내부에 정의할 수 있는 함수 몸체는 3가지로 constructor, 프로토타입 메서드, 정적 메서드가 있고 이들의 특성과 차이점을 알아보았다. 

 

 

이번에는 클래스의 인스턴스 생성과정을 알아본다.

 

new 연산자를 사용해 클래스를 호출하면 생성자 함수와 마찬가지로 클래스의 내부 메서드

constructor가 호출된다. 

 

1. 인스턴스 생성과 this 바인딩

 

클래스를 new 연산자로 호출하면 constructor의 내부 코드가 실행되기에 앞서 암묵적으로 빈 객체가 생성된다. 이 빈 객체가 클래스가 생성할 인스턴스이다. 

이때 클래스가 생성한 인스턴스의 프로토타입으로 클래스의 prototype 프로퍼티가 가리키는 객체가 설정된다.

 

그리고 암묵적으로 생성된 빈 객체, 즉 인스턴스는 this에 바인딩된다.

constructor의 내부 코드 실행 전에 빈 객체가 생성되고 이 객체의 프로토타입으로 클래스의 prototype 프로퍼티가 가리키고 있는 객체를 설정하고, 빈 객체에 this를 바인딩한 후 constructor의 내부 코드가 실행된다. 

따라서 constructor 내부의 this는 클래스가 생성한 인스턴스를 가리킨다. 

 

2. 인스턴스 초기화

 

constructor 내부 코드가 실행될 대 this에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 초기 값 설정을 했다면 이 초기값으로 인스턴스의 프로퍼티 값을 초기화한다.

 

3. 인스턴스 반환

 

클래스의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.

 

 

즉 코드상으로는 다음과 같다.

class Person{

	constructor(name){
    	//코드 실행전 클래스가 암묵적으로 생성한 빈 객체 {}가 this에 바인딩된다.
        
        //클래스의 prototype이 가리키는 객체를 빈 객체의 prototype에 설정한다.
        console.log(Object.getPrototypeOf(this) === Person.prototype); //true
        
        //this에 바인딩 된 인스턴스를 초기화
        this.name = name;
        
        //클래스의 모든처리가 끝나면 암묵적으로 this가 반환된다.
    }

}

 

 

** 프로퍼티 어트리뷰트 공부 후 진행해야 클래스 내부 프로퍼티에 대해서 조금 더 쉽게 이해가 가능할 것 같다.

인스턴스 생성과정까지 멈추고 다시 앞장으로 돌아가 보자. 

 

반응형

'JavaScript' 카테고리의 다른 글

클래스 (2)  (0) 2022.03.03
Property Attribute  (0) 2022.03.03
-집가서 확인  (0) 2022.03.02
클로저의 활용  (0) 2022.02.24
클로저  (0) 2022.02.24

댓글