코드노트

자바스크립트 클래스 정리 본문

Code note/자바스크립트

자바스크립트 클래스 정리

코드노트 2022. 12. 24. 00:42

클래스

- 클래스는 생성자 함수와 유사하게 동작하지만 몇 가지의 차이를 가지고 있다.
- 클래스는 class 키워드를 사용하여 정의

// 클래스 선언문
class Person{}

// 익명 클래스 표현식
const Person = class {};

// 기명 클래스 표현식
const Person = class MyClass {};

- 클래스는 표현식으로 정의할 수 있다.

* 무명의 리터럴로 생성할 수 있고 런타임에 생성 가능
* 변수, 자료구조(객체, 배열) 저장 가능
* 함수의 매개변수에 전달 가능
* 함수의 반환값으로 사용


1. 클래스를 new 연산자 없이 호출하면 에러가 발생
- 생성자 함수를 new 연산자 없이 호출하면 일반 함수로 호출

2. 클래스는 상속을 지원하는 extends와 super 키워드를 제공
- 생성자 함수는 extends와 super키워드를 지원하지 않는다.

3. 클래스는 호이스팅이 발생하지 않는 것처럼 동작
- 함수 선언문으로 정의된 생성자 함수는 함수 호이스팅 발생
- 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅이 발생

4. 클래스 내의 모든 코드에는 암묵적으로 strict mode가 지정되어 실행되며 strict mode를 해제할 수 없다.
- 생성자 함수는 암묵적으로 strict mode가 지정되지 않는다.

5. 클래스의 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[ Enumerable ]]의 값이 false


선언 방법

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

 



클래스 사용 예제

// 클래스 선언문
class Person {
  // 생성자
  constructor(name) {
    //인스턴스 생성 및 초기화
    this.name = name;
  }
  
  // 프로토타입 메서드
  sayHi() {
    console.log(`Hi! My name is ${this.name}`);
  }
  
  // 정적 메서드
  static sayHello() {
    console.log("Hello!");
  }
}

// 인스턴스 생성
const me = new Person("Lee");

// 인스턴스의 프로퍼티 참조
console.log(me.name) // Lee

// 프로토타입 메서드 호출
me.sayHi(); // Hi! My name is Lee

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

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


클래스 호이스팅

*변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미
클래스 선언문으로 정의한 클래스함수 선언문과 같이 소스코드 평과 과정, 즉 런타임 이전에 먼저 평가 되어 함수 객체를 생성한다.
클래스 선언문은 마치 호이스팅이 발생하지 않는 것처럼 보이지만 그렇지 않다.
- 단 클래스는 클래스 정의 이전에 참조할 수 없다.
* var, let, const, function, function*, class 키워드를 사용하여 선언된 모든 식별자는 호이스팅 된다.
모든 선언문은 런타임 이전에 먼저 실행되기 때문인다.


ex ) 생성자 함수를 class로

// 생성자 함수
function Person(name) {
  this.name = name;
}
// 프로토타입 메서드
Person.prototype.getName = function () {
  return this.name + "입니다";
}
// 정적 메서드
Person.sayHello = function() {
  console.log("Hello!")
}



// Classes
class Person {
  constructor(name) {
    this.name = name;
  }
  // 프로토타입 메서드
  getName() {
    return this.name + "입니다";
  }
  static sayHello() {
    console.log("Hello!")
  }
}

- 생성자함수보다 더 간결하게 사용할 수 있다. 그 외에도 다른 메서드들을 사용할 수 있다.
- 리엑트, 뷰 등에서도 사용이 가능하기 때문에 생성자함수를 꼭 사용할 필요는 없다.
- 클래스는 new 연산자 없이 호출하면 에러가 발생 / 생성자 함수는 일반 함수로 호출



인스턴스 생성

- 클래스는 생성자 함수이며 new 연산자와 함께 호출되어 인스턴스를 생성한다.

class Person {}

// 인스턴스 생성
const me = new Person();
console.log(me); // Person {}

- 클래스는 인스턴스를 생성하는 것유일한 존재 이유이므로 반드시 new 연산자와 함께 호출해야 한다.

const Person = class MyClass {};
const me = new Person();
console.log(me); // MyClass {}
console.log(MyClass); // ReferenceError: MyClass is not defined
const you = new MyClass();
console.log(you); // ReferenceError: MyClass is not defined

- 기명 함수 표현식과 마찬가지로 클래스 표현식에서 사용한 클래스 이름은 외부 코드에서 접근 불가능하다.


메서드

- 클래스 body에는 0개 이상의 메서드만 선언할 수 있다. constructor(생성자), 프로토타입 메서드, 정적 메서드 세가지가 있다.

 

constructor

- 인스턴스를 생성하고 초기화하기 위한 특수한 메서드
- 이름을 변경할 수 없다.

class Person {
  // 생성자
  constructor(name) {
    // 인스턴스 생성 및 초기화
    this.name = name;
  }
}

- 클래스 내에 1개만 존재할 수 있다. 2개 이상 존재하면 문법 에러가 발생한다.
- 클래스는 인스턴스를 생성하기 위한 생성자 함수이다.
- constructor는 메서드로 해석되는 것이 아니라 클래스가 생성한 객체 코드의 일부가 된다.
- 생략할 수 있지만 생략을 하게 되면 빈 constructor가 빈 객체로 암묵적으로 정의된다.( 빈 객체를 생성 )
- 프로퍼티가 추가되어 초기화된 인스턴스를 생성하려면 constructor 내부에서 this에 인스턴스 프로퍼티를 추가
- return을 사용할 수 있지만 클래스의 기본 동작을 훼손하기 때문에 내부에서 return문을 반드시 생략해야한다.


프로토타입 메서드

// 생성자 함수
function Person(name) {
  this.name = name;
}

// 프로토타입 메서드
Person.prototype.sayHi = function() {
  console.log("Hi! My name is " + this.name)
};

const me = new Person("lee");
me.sayHi(); // Hi! My name is lee

---------------------------------------
// 클래스
class Person {
  // 생성자
  constructor(name) {
    // 인스턴스 생성 및 초기화
    this.name = name;
  }
  
  // 프로토타입 메서드
  sayHi() {
    console.log("Hi! My name is " + this.name)
  }
}

const me = new Person("lee");
me.sayHi(); // Hi! My name is lee

- 생성자 함수를 사용하여 인스턴스를 생성하는 경우 프로토타입 메서드를 생성하기 위해서는 명시적으로 프로토타입에 메서드를 추가
- 클래스 body에서 정의한 메서드는 생성자 함수에 의한 객체 생성방식과는 다르게
클래스의 prototype 프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 된다.

* 클래스 내에 메서드를 정의하게 되면 프로토타입 메서드!


정적 메서드

- 인스턴스를 생성하지 않아도 호출할 수 있는 메서드를 말한다.

// 생성자 함수
function Person(name) {
  this.name = name;
}

// 정적 메서드
Person.sayHi = function() {
  console.log("Hi!");
}

// 정적 메서드 호출
person.sayHi(); // Hi!

------------------------------------------

// 클래스
class Person {
  // 생성자
  constructor(name) {
    // 인스턴스 생성 및 초기화
    this.name = name;
  }
  
  // 정적 메서드
  static sayHi() {
    console.log("Hi!"); 
  }
}

// 정적 메서드 호출
// 정적메서드는 클래스로 호출한다.
// 정적 메서드는 인스턴스 없이도 호출할 수 있다.
person.sayHi(); // Hi!

- 클래스에서는 메서드에 static을 붙이면 정적 메서드가 된다.
- 생성자 함수와 비교해도 코드가 간결한걸 볼 수 있다.
- 정적 메서드는 클래스에 바인딩되기 때문에 인스턴스 프로토타입 체인 상에서 찾을 수 없다.

// 인스턴스 생성
const me = new Person("lee");
me.sayHi(); // TypeError: me.sayHi is not a function

- 정적 메서드는 인스턴스로 호출할 수 없다.



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

- 정적 메서드와 프로토타입 메서드는 자신이 속해 있는 프로토타입 체인이 다르다.
- 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다.
- 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다.

class Square {
  // 정적 메서드
  static area(width, height) {
    return width * height;
  }
}
console.log(Square.area(10, 10)); // 100

-------------------------------------------

class Square {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  // 프로토타입 메서드
  area() {
    return this.width * this.height;
  }
}

const square = new Square(10, 10);
console.log(Square.area()); // 100

- 위 예제를 보더라도 알 수있다. 정적 메서드는 2개의 인수를 받아 계산한다. 인스턴스 메서드를 참조 하지 않는다.
- 그러나 프로토타입 메서드는 인스턴스 메서드를 참조한다.
* 정적 메서드는 this가 아닌 클래스를 가르킨다.
* 프로토타입 메서드는 this를 사용하여 인스턴스 프로퍼티를 참조한다.
-> this를 사용하지 않더라도 프로토타입메서드를 정의할 수는 있다. this를 사용하지 않는 메서드는 정적 메서드로 정의하는것이 좋다



클래스 접근자 프로퍼티

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
// 접근자 함수로 구성된 접근자 프로퍼티 getter, setter 함수
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(" ");
  }
}

const me = new Person("beomjun", "kwon");

console.log(`${me.firstName} ${me.lastName}`); // beomjun kwon

me.fullName = "tube kwak";

console.log(me.fullName); // tube kwak

console.log(Object.getOwnPropertyDescriptor(Person.prototype, "fullName"));
/*
{
  get: [Function: get fullName],
  set: [Function: set fullName],
  enumerable: false,
  configurable: true
}
*/

- 접근자 프로퍼티는 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수

 

- getter는 프로퍼티를 참조하는 형식으로 사용

* 프로퍼티에 접근할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용
* 무언가를 취득할 때 사용하므로 반드시 반환

 

- setter는 프로퍼티처럼 값을 할당하는 형식으로 사용

* 무언가를 프로퍼티에 할당해야 할 때 사용
* 반드시 매개변수가 필요!


클래스 필드 정의 제안

- 클래스 기반 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가르키는 용어

class Person {
  this.name = "";
} // SyntaxError: Unexpected token '.'

- 클래스 몸체에서 클래스 필드를 정의하는 경우 this에 클래스 필드를 바인딩 해서는 안된다.
- this는 클래스의 constructor와 메서드 내에서만 유효

 

class Person {
  name = "code";
  constructor() {
    console.log(name);
  }
}
new Person(); // ReferenceError: name is not defined


class Person {
  name;
}
const me = new Person();
console.log(me); // Person { name: undefined }

- 클래스 필드에 초기값을 할당하지 않으면 undefined를 갖는다.
- 인스턴스를 생성할 때 외부의 초기값으로 클래스 필드를 초기화해야 할 필요가 있다면 constructor에서 클래스 필드를 초기화

 

class Person {
  name;
  constructor(name) {
    this.name = name;
  }
}
const me = new Person("code");
console.log(me); // Person { name: 'code' }


class Person {
  constructor(name) {
    this.name = name;
  }
}
const me = new Person("code");
console.log(me); // Person { name: 'code' }

- 클래스 필드를 초기화할 필요가 있다면 어차피 constructor 내부에서 클래스 필드를 참조하여 초기값을 할당
- 클래스가 생성한 인스턴스에 클래스 필드에 해당하는 프로퍼티가 없다면 자동 추가
* name이 없어도 constructor에서 자동 추가 된다.

- 이 외에도 함수는 일급 객체이므로 함수를 클래스 필드에 할당할 수 있다. 메서드를 정의할 수도 있다.

- 필드에 함수는 프로토타임 메서드가 아닌 인스턴스 메서드가 된다.
- 모든 클래스 필드는 인스턴스 프로퍼티가 되기 때문
- 클래스 필드에 함수를 할당하는 것은 권장하지 않는다.


private 필드 정의

class Person {
  name = "code";
}
const me = new Person();
console.log(me.name); // code

- 클래스 필드는 기본적으로 public하기 때문에 외부에 그대로 노출 된다.

 

class Person {
  #name = ""; // private 필드 정의
  constructor(name) {
    this.#name = name;
  }
}
const me = new Person("code");
console.log(me.name); // undefined



class Person {
  #name = ""; // private 필드 정의
  constructor(name) {
    this.#name = name;
  }
  get name() {
    return this.#name.trim();
  }
}
const me = new Person("  code  ");
console.log(me.name); // code

- private 필드 #name은 클래스 외부에서 참조할 수 없다.
- 접근자 프로퍼티로는 접근할 수 있다.
- private 필드는 반드시 클래스 몸체에 정의하자. constructor에 정의하면 에러 발생


상속에 의한 클래스 확장

- 프로토타입 기반의 기본 상속과는 다른 개념

- 기존 클래스를 상속받아 새로운 클래스를 확장하여 정의

 

class Traveler {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  eat() {
    return "eat";
  }
  move() {
    return "move";
  }
}
class Kwak extends Traveler {
  ssaul() {
    return "ssaul";
  }
}

const kwak = new Kwak(1, 5);

console.log(kwak); // Kwak { name: 1, age: 5 }
console.log(kwak instanceof Kwak); // true
console.log(kwak instanceof Traveler); // true

console.log(kwak.eat()); // eat
console.log(kwak.move()); // move
console.log(kwak.ssaul()); // ssaul

- 클래스 상속을 통해서 기존 클래스의 속성들을 그대로 사용하고 자신만의 고유한 속성을 추가하여 사용할 수 있다.

- 코드 재사용 관점에서도 매우 유용한 방법

- extends키워드를 사용한 클래스 확장은 간편하고 직관적


extends

// 수퍼(베이스/부모)클래스
class Traveler {
}

// 서브(파생/자식)클래스
class Kwak extends Traveler {
}

- 수퍼클래스와 서브 클래스는 인스턴스의 프로토타입 체인 뿐만 아니라 클래스 간의 프로토타입 체인도 생성

- 프로토타입 메서드, 정적 메서드 모두 상속이 가능

 

function Traveler(a) {
  this.a = a;
}

class Kwak extends Traveler {}

const tube = new Kwak(1);

console.log(tube); // Kwak {a:1}

- extends 키워드는 클래스 뿐만 아니라 생성자 함수를 상속받아 클래스 확장도 가능

- 단, extends 키워드 앞에는 반드시 클래스가 있어야 한다.

 

function Kwak() {}

class Bottle {}

let condition = true;

// 조건에 따라서 동적으로 상속 대상을 결정할 수 있다.
class Tube extends (condition ? Kwak : Bottle) {}

const traveler = new Tube();
console.log(traveler);

console.log(traveler instanceof Kwak);
console.log(traveler instanceof Bottle);

- extends 키워드 다음에는 클래스뿐만 아니라 [[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식을 사용할 수 있다.

- 조건문을 통해서 상속 대상을 결정하는 것을 볼 수있다.


서브클래스의 constructor

- constructor를 생략하게 되면 비어있는 constructor가 암묵적으로 정의

class Bottle {}

class Tube extends Bottle {}

const traveler = new Tube();
console.log(traveler);

----------------------------------------

class Bottle {
  constructor() {}
}

class Tube extends Bottle {
  constructor(...args) {
    super(...args);
  }
}

const traveler = new Tube();
console.log(traveler);

- 첫번째 예제를 보면 알 수 있듯이 constructor를 생략하게 되면 두번째 예제처럼 암묵적으로 정의가 되어 실행된다.

- 프로퍼티를 소유하는 인스턴스를 생성하려면 constructor내부에서 인스턴스에 프로퍼티를 추가해야 한다.