sungyup's.

javascript_algorithm / 데이터 구조 / 4.1 Data Structures Intro

4.1Data Structures Intro

Class 문법 기초

추억의 쪽지 시험

데이터 구조에는 다양한 것들이 있고, 각자의 필요가 있어 쓰인다. 이번 섹션에선 각각의 데이터 구조를 직접 자바스크립트 ES6 class 문법을 통해 구현한다.

class란?

A blueprint for creating objects with pre-defined properties and methods

class는 객체를 생성할 수 있는 일종의 청사진, 붕어빵 틀이다. 공통 속성과 메소드를 정의한 틀을 만들고, 그 틀을 바탕으로 여러 객체 인스턴스를 만들 수 있다.

사실 자바스크립트는 Java, Python과 같은 클래스 기반 언어(class-based OOP) 가 아니라 프로토타입 기반 언어(prototype-based) 이다. class 문법은 ES6 이후 도입된 syntactic sugar, 즉 문법적으로 더 읽기 쉽도록 만든 표현일 뿐이며, 내부적으로는 여전히 프로토타입 체인을 기반으로 동작한다.

The Class Keyword

javascript
// class명은 관례상 대문자로 시작 class Student { constructor(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } } const firstStudent = new Student("Sungyup", "Ju");
  • constructor는 클래스 인스턴스를 생성할 때 자동으로 호출된다.
  • this인스턴스 자신을 가리킨다.

인스턴스 메소드

인스턴스마다 사용할 수 있는 메소드는 클래스 내부에 메소드로 정의한다.

javascript
class Student { constructor(firstName, lastName, year){ this.firstName = firstName; this.lastName = lastName; this.tardies = 0; // 지각 횟수 } fullName(){ return `Your full name is ${this.firstName} ${this.lastName}.` } markLate(){ this.tardies += 1; if(this.tardies >= 3){ return "YOU ARE EXPELLED!" } return `${this.firstName} ${this.lastName} has been late ${this.tardies} time(s).` } } const firstStudent = new Student("Sungyup", "Ju"); firstStudent.fullName(); // Your full name is Sungyup Ju. firstName.markLate(); // Sungyup Ju has been late 1 time(s).
  • 모든 인스턴스는 같은 fullName()markLate() 메소드를 공유한다.
  • 실제, 내부적으로는 Student.prototype.fullName과 같은 형태로 정의된다.

클래스 메소드

때로는 특정 기능이 개별 인스턴스에 속할 필요 없이, 클래스 그 자체에 속하는 것이 더 적절한 경우가 있다. static 키워드로 각 인스턴스가 아닌, 클래스 자체에 메소드를 추가할 수 있다.

예를 들어, 아래의 Point 클래스에서 두 점 사이의 거리를 구하는 메소드는 각 인스턴스의 메소드로 등록할 수는 없다. 이런 유틸리티 함수공통 계산 함수 등을 구현할 때 클래스 자체에 static 메소드를 추가한다.

javascript
class Point { constructor(x, y){ this.x = x; this.y = y; } static distance(a, b){ const dx = a.x - b.x; const dy = a.y - b.y; return Math.hypot(dx, dy); // 빗변의 길이(hypotenuse)를 구함 } } const p1 = new Point(5, 4); const p2 = new Point(1, 3); Point.distance(p1, p2);

🤠 개인 탐구

클래스 관련 기본 사항들을 짚고 넘어가는 김에 다른 문법들도 복습하고 가자.

상속과 super: extendssuper

클래스는 다른 클래스를 상속할 수 있다. 즉, 기존 클래스(부모 클래스)의 기능을 재사용하고 확장할 수 있다.

상속을 위해선 extends 키워드를 사용하고, 생성자(constructor) 안에서 부모 클래스의 생성자를 호출하기 위해 super()를 사용한다.

javascript
class Person { constructor(name){ this.name = name; } greet(){ return `Hello, I'm ${this.name}`; } } class Students extends Person { constructor (name, grade){ super(name); // 부모(Person) constructor 호출 this.grade = grade; // 확장 } }
  • extends는 부모 클래스의 모든 속성과 메소드를 자식 클래스에 물려준다.
  • super()는 부모의 constructor를 호출해 초기화 작업을 수행한다.
  • 부모 클래스의 메소드도 모두 그대로 사용 가능한데, 덮어쓸 수도 있다.

접근 제어: gettersetter

속성처럼 보이지만 계산을 필요로 하는 경우, getter를 통해 계산하는 함수를 작성하고 속성에 접근할 수 있도록 한다. 또, 객체의 값을 바꿀 때는 직접 덮어쓰는 것보다 안전하게 setter를 활용해 유효성 검사를 실행할 수 있다.

javascript
class Rectangle { constructor(width, height){ // 내부에서만 사용하는 변수에는 관례상 _를 붙임 this._width = width; this._height = height; } // 게터 : 속성처럼 보이지만 사실 계산을 통해 나오는 값 get area(){ return this._width * this._height; } // 세터: 값을 덮어 쓸 때 set width(value){ if(value <= 0) throw new Error('Width must be positive!'); this._width = value; } set height(value){ if(value <= 0) throw new Error('Height must be positive!'); this._height_ = value; } } const rect = new Rectangle(10, 5); rect.area; // 50. 속성에 접근하는것 같지만 계산된 값 rect.width = 20; // 덮어씌우는 것 같지만 세터로 변경됨 rect.area; //100

Private: 외부에서 접근 못하게 막기

#를 통해 private 필드를 선언할 수 있다.(ES2022)

API를 설계할 때 사용자가 건드리면 안 되는 속성이나 로직을 감출 때(비밀번호, 캐시, 내부 계산 결과 등) 사용한다.

javascript
class Counter{ #count = 0; increment(){ this.#count++; return this.#count; } get value(){ return this.#count; } } const c = new Counter(); c.increment(); //1 c.value; // 1 c.#count; // SyntaxError: Private field must be declared in an enclosing class

참고로, TypeScript의 private는 컴파일 단계에서 접근 제한을 적용해주지만, 실제 런타임에서 보호해주지는 않는다. JS의 #으로 보호해야 한다.

그래서, 왜 class를 사용할까?

모든 인스턴스들이 메소드를 공유한다는 것은 메소드들이 프로토타입에 올라가고, 모든 인스턴스마다 메소드를 복사하지 않는다는 것을 의미한다.

즉, __proto__Object.create()를 통해 프로토타입 체인을 구성하기 때문에 메모리 구조가 효율적이다.

javascript
class A { greet() { return "hello"; } } const a = new A(); const b = new A(); a.greet(); // 실제로는 A.prototype.greet() b.greet(); // 실제로는 A.prototype.greet()