본문 바로가기

프로그래밍/js

ES6 lessons 7. 클래스

2023.04.02

자바스크립트 스터디 11회차

공부 사이트: https://poiemaweb.com/

 

7. 클래스

자바스크립트는 프로토타입 기반 객체지향 언어이다. 프로토타입 체인과 클로저 등으로 객체지향 언어의 상속, 캡슐화 개념을 구현할 수 있다.

ES6의 클래스는 클래스 기반 언어에 익숙한 프로그래머를 위한 문법을 제시한다.

 

1) 클래스 정의

클래스는 class 키워드를 사용하여 정의하고, constructor()를 사용하여 생성자를 만든다.

또한 객체의 멤버변수는 (_)로 시작하도록 만든다. (멤버변수 = 클래스 필드)

class Person {
	constructor(name){
    	this._name = name;
    }
    
    sayHi(){
    	console.log(`Hi ${this._name}`);
    }
}

const me = new Person('홍길동');
me.sayHi();

// Person 클래스의 인스턴스 me
console.log(me instanceof Person);

 

잘 사용하지는 않지만, 표현식으로 클래스 정의가 가능하다.

클래스도 함수이 일종이기에, 이름을 갖지 않을 수도 있다.

표현식으로 클래스 정의 시, 클래스가 할당된 변수를 사용하지 않고 기명 클래스의 이름을 사용해서 클래스를 생성하면 에러가 발생한다.

→ 기명 클래스의 이름은 클래스 몸체 내부에서만 유효한 식별자

// 표현식으로 클래스 정의
const Foo = class MyClass {};

// 정상
const foo = new Foo();

// 참조 에러
new MyClass();

 

 

2) 인스턴스 생성

new 연산자를 사용해서 인스턴스를 생성할 수 있다.

class Foo {}

const foo = new Foo();

new 연산자와 함께 호출한 Foo는 클래스 이름이 아닌 constructor(생성자)다.

선언식으로 정의한 클래스의 이름은 constructor와 동일하다.

 

 

3) constructor

constructor

- 인스턴스를 생성하고 클래스 필드를 초기화하기 위한 특수 메소드

- 클래스 내에 한 개만 존재할 수 있음

- 인스턴스 생성 시 new 연산자와 함께 호출하는 것이 constructor

- constructor의 파라미터에 전달한 값은 클래스 필드에 할당함

- 클래스 생성 시 constructor 생략 가능

 

 

4) 클래스 필드

클래스 바디에 클래스 필드(멤버 변수)를 선언하면 문법 에러가 발생.. 했었으나.

최신 브라우저 또는 Node.js 버전 12 이상에서 실행하면 정상적으로 동작한다.

class Foo {
	name = ''; // 문법 에러..가 동작했었음
    
    constructor() { }
}

 

constructor 내부에서 선언한 클래스 필드는 인스턴스를 가리키는 this에 바인딩한다.

- 바인딩된 클래스 필드는 생성된 인스턴스의 프로퍼티가 됨. 

- 인스턴스를 통해 클래스 외부에서 언제나 참조 가능 → public 하다.

 

 

5) Class field declarations proposal

클래스 바디에 클래스 필드를 선언하면 정상적으로 동작하는 이유가 "Class field declarations proposal"를 구현했기 때문이다.

TC39 프로세스의 stage 3에서 "Class field declarations proposal"과 "Static class  features"가 등장했는데, 여기서 클래스와 관련하여 몇 가지 새로운 표준안이 제안되었다.

# TC39: ECMASCript의 관리를 담당하는 위원회

 

① Field declarations : 바디에 필드 선언 가능

② Private field : private 필드 선언 가능

③ Static public fields : static public 필드 선언 가능

 

이로인해 아래 예제가 정상적으로 동작할 수 있다.

class Foo {
	x = 1;     // 클래스 필드 선언
    #p = 0;    // private 필드
    static y = 20;     // Static public 필드
    static #sp = 30;   // Static private 필드
    
    static #sm() {     // Static private 메소드
    	console.log('static private method'); 
    }
    
    privateFieldTest() {
    	this.#p = 10;    // 바디에 선언된 private 필드를 참조함
        this.p = 10;    // 새로운 public 필드를 동적으로 추가 가능
        return this.#p;
    }
	
	staticPrivateFieldTest(){
		return Foo.#sp; 
		// this.#sp  타입 에러 발생
	}
	
	staticPrivateMethodTest(){
		Foo.#sm();
		// 만약 #sm 이 static이 아니라면 this.#sm 과 같이 접근
	}
}

const foo = new Foo();

console.log(foo);
console.log(foo.x);
console.log(Foo.y);
console.log(foo.privateFieldTest());
console.log(foo.staticPrivateFieldTest());
foo.staticPrivateMethodTest();

console.log(foo.y);

// console.log(foo.#p);   문법 에러
// console.log(Foo.#sp);   문법 에러

 

[기억할 점]

- static 필드는 클래스 이름을 통해 직접 접근을 해야 한다. 클래스 자체에 속하는 멤버이기 때문.

- #을 사용해서 생성한 private 필드는 클래스 바디 내부에서만 접근 가능하다.

- 클래스 바디에 있는 private 필드에 인스턴스가 접근하려면, 따로 클래스 메소드를 구현한 뒤에 인스턴스가 메소드를 호출하면 된다.

- static private 필드는 클래스 메소드 내부에서만 접근 가능한데, 이 때 Foo.#sp 와 같이 접근해야 한다.

- static private 메소드도 마찬가지. 클래스 메소드 내부에서만 접근 가능하다. Foo.#sm 과 같이 접근해야 한다.

 

요약

- static필드는 클래스 이름을 사용해서 접근 가능 (인스턴스를 통해 접근 불가능)

- private 필드는 클래스 바디 내부에서만 접근 가능 (this 통해 접근)

- static private : 클래스 바디 내부에서 클래스 이름을 사용하여 접근 가능

 

 

6) getter, setter

getter와 setter모두 클래스 필드에 접근 시 클래스 필드 값을 다루기 위해 사용한다.

클래스에서 새롭게 도입된 기능은 아니고, 접근자 프로퍼티이다.

 

① getter

메서드 이름 앞에 get 키워드를 사용해서 정의

getter는 클래스 필드를 참조하기 위해 사용됨

반드시 무언가를 반환해야 함

 

② setter

메소드 이름 앞에 set 키워드를 사용해서 정의

setter는 클래스 필드에 값을 할당하기 위해 사용됨

파라미터가 존재해야 함

 

[예제]

class Foo {
    constructor(arr = []){
    	this._arr = arr;
    }

    get firstElem(){
		return this._arr.length ? this._arr[0] : null;
	}
    
    set firstElem(elem){
    	this._arr = [elem, ...this._arr];
    }
}

const foo = new Foo([1,2]);

foo.firstElem = 100;     // setter가 호출
console.log(foo.firstElem);    // getter가 호출

 

 

7) 클래스 상속

① extends 키워드

부모 클래스를 상속받는 자식 클래스를 정의할 때 사용한다.

class Circle {
	constructor(radius){
		this._radius = radius;    //반지름
	}
	
	getDiameter() {
		return 2 * this._radius;
	}
	
	// 원의 둘레
	getPerimeter() {
		return 2 * Math.PI * this._radius;
	}
	
	// 원의 넓이
	getArea() {
		return Math.PI * Math.pow(this._radius, 2);
	}
}

// 원기둥
class Cylinder extends Circle {
	constructor(radius, height) {
		super(radius);
		this._height = height;
	}
	
	// 부모 클래스 메소드를 오버라이딩
	getArea() {
		return (this._height * super.getPerimeter()) + (super.getArea() * 2);
	}
	
	// 원통의 부피
	getVolume() {
		return super.getArea() * this._height;
	}
}

const cylinder = new Cylinder(2,10);

console.log(cylinder.getDiameter());
console.log(cylinder.getPerimeter());
console.log(cylinder.getArea());
console.log(cylinder.getVolume());

console.log(cylinder instanceof Cylinder);
console.log(cylinder instanceof Circle);

오버라이딩: 상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의하여 사용하는 것

오버로딩: 매개변수 타입 또는 개수가 다르지만 이름은 같은 메소드를 구현하고 싶을 때 사용하는 방식. 매개변수에 의해 메소드를 구별하여 호출함.

 

② super 키워드

부모 클래스를 참조할 때, 부모 클래스의 constructor를 호출할 때 사용하는 키워드이다.

super가 메소드로 사용될 때, 객체로 사용될 때가 각각 다르게 동작한다.

 

ㅇ 메소드로 사용

super 메소드는 자식 클래스의 constructor 내부에서 부모 클래스의 constructor를 호출한다.

→ 부모 클래스의 인스턴스를 생성

자식 클래스의 constructor에서 super() 호출 전에는 this를 참조할 수 없다.

 

ㅇ 키워드로 사용

부모 클래스에 대한 참조이다. 부모 클래스의 필드 또는 메소드를 참조하기 위해 사용한다.

 

③ static 메소드와 protorype 메소드의 상속

자식 클래스의 정적 메소드 내부에서 super 키워드를 사용하여 부모 클래스의 정적 메소드를 호출할 수 있음.

자식 클래스는 프로토타입 체인에 의해 부모 클래스의 정적 메소드를 참조 가능

자식 클래스의 일반 메소드(프로토타입 메소드) 내부에서 super 키워드를 사용하여 부모 클래스의 정적 메소드 호출 불가능함.

자식 클래스의 인스턴스는 프로토타입 체인에 의해 부모 클래스의 정적 메소드를 참조 불가능

 

 

 

 

 

'프로그래밍 > js' 카테고리의 다른 글

ES6 lessons 9. 프로미스  (0) 2023.04.03
ES6 lessons 8. 모듈  (0) 2023.04.03
ES6 lessons. 6 Destructuring  (0) 2023.03.28
ES6 lessons 5. 향상된 객체 리터럴  (0) 2023.03.26
ES6 lesson 4. 확장 파라미터 핸들링  (0) 2023.03.26