(9장) 클래스 [자바스크립트 완벽 가이드 7판]

iberis2·2023년 2월 6일
0

이 챕터의 요약 미리보기

  • 같은 클래스에 속하는 객체는 같은 프로토타입 객체에서 프로퍼티를 상속합니다.
  • ES5 이전에는 일반적으로 먼저 생성자 함수를 정의한 뒤에야 클래스를 정의할 수 있었습니다.
    • function키워드로 생성한 함수는 prototype 프로퍼티를 가지고 있고, 이 프로퍼티 값은 함수를 new와 함께 생성자로 호출했을 때 생성되는 모든 객체의 프로토타입으로 사용됩니다.
    • prototype 객체를 초기화해서 클래스에서 공유하는 메서드를 정의할 수 있습니다.
  • 서브클래스는 클래스 선언에 extends 키워드를 사용해 정의합니다.
  • 서브클래스는 super키워드를 써서 슈퍼클래스의 생성자 또는 덮어쓰인 메서드를 호출할 수 있습니다.

클래스

자바스크립트에서 클래스같은 프로토타입 객체에서 프로퍼티를 상속하는 객체 집합니다. 따라서 프로토타입 객체가 클래스의 핵심 기능입니다.

ES5까지의 클래스

Object.create()로 프로토타입을 상속하는 객체를 생성

function range(from, to){
  let r = Object.create(range.methods);
  r.from = from;
  r.to = to;
  return r;
}

range.methods = {
  includes(x){ return this.from <= x && x <= this.to; },
  
  *[Symbol.iterator](){
    for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
  },
  
  toString(){ return`( ${this.from}...${this.to} )`; }
}

let r  = range(1, 3);
r.includes(2) // true
r.toString() // '(1...3)'
[...r] 		// [1, 2, 3]

// isPrototypeOf 객체의 프로토타입 체인에 특정 프로토타입이 존재하는 지 확인
range.methods.isPrototypeOf(r); // true

생성자 함수로 클래스 생성

함수 객체는 prototype 프로퍼티를 가집니다.
생성자 함수를 공유하는 객체는 모두 같은 객체를 상속하며, 따라서 같은 클래스의 멤버입니다.

function Range(from, to){
  this.from = from;
  this.to = to;
}

Range.prototype = {
  includes: function(x){ return this.from <= x && x <= this.to; },
  
  [Symbol.iterator]: function*(){
    for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
  },
  
  toString: function(){ return`( ${this.from}...${this.to} )`; }
};

/* new와 Range() 생성자 함수 사용 */
let r  = new Range(1, 3); 
r.includes(2) // true
r.toString() // '(1...3)'
[...r] 		// [1, 2, 3]

Range.prototype 객체를 덮어쓰는 경우,
Range.prototype.constructor도 덮어씌워지므로, 객체 내부에 명확히
Range.prototype = {constructor: Range} 로 명시하거나

Range.prototype.includes = function(x){
  return this.from <= x && x <= this.to; 
} 

메서드를 Range.prototype 객체 외부에서 확장하는 방법도 있다.

생성자와 new.target
함수 바디 안에서 new.target을 사용하면 함수가 생성자로 호출됐는지 알 수 있습니다.

function Foo() {
  if (!new.target) throw "Foo() must be called with new";
  console.log("Foo instantiated with new");
}

Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"

class 키워드로 생성된 클래스는 new 없이 생성자를 호출할 수 없습니다.

  • new.target은 new에 의해 직접 호출된 생성자를 가리킵니다.
    • 이는 그 생성자가 부모 클래스에 있고 자식 생성자로부터 위임받은 경우도 그 경우입니다.
class A {
  constructor() {
    console.log(new.target.name);
  }
}

class B extends A { constructor() { super(); } }

let a = new A(); // logs "A"
let b = new B(); // logs "B"

생성자 함수

instanceof : 인스턴스 객체가 생성자 함수를 상속하는지 확인할 수 있습니다.다.

r instanceof Range // true;

// 직접 상속하지 않아도 prototype을 상속해도 true로 평가
function Strange(){}
Strange.prototype = Range.prototype;

new Strange() instanceof Range // true
let s = new Strange();
s instanceof Range // true

생성자 프로퍼티

let F = function(){};
let p = F.prototype;
let c = p.constructor;
c === F // true
F.prototype.constructor === F // true
let o = new F();
o.constructor === F // true
  

ES6의 클래스

class 키워드를 사용하는 class

class Range{
  constructor(from, to){
  this.from = from;
  this.to = to;
  }
  
  includes(x){ return this.from <= x && x <= this.to; },
  
  *[Symbol.iterator](){
    for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
  },
  
  toString(){ return`( ${this.from}...${this.to} )`; }
};

let r  = new Range(1, 3); 
r.includes(2) // true
r.toString() // '(1...3)'
[...r] 		// [1, 2, 3]
  • 'use strict' 가 없어도 class 선언의 바디는 모두 묵시적으로 스트릭 모드로 동작합니다.
    • 바디 내에서 8진수 정수 리터럴이나 with문을 사용할 수 없으며, 변수 선언 전 사용시 syntax error가 발생합니다.
  • 함수 선언과 달리 호이스팅이 일어나지 않습니다.
    • 클래스를 선언하기 전에 인스턴스를 만들 수 없습니다.

extends 와 super

다른 클래스를 상속하는 서브 클래스를 정의할 수 있다.

class Span extends Range{
  constructor(start, length){
    if(length >= 0){
      super(start, start + length);
    } else{
      super(start+length, start);
    }
  }

}

static

인스턴스 객체에 상속되지 않는, 생성자 함수의 프로퍼티를 정의할 수 있습니다.

class Range{
  constructor(from, to){
  this.from = from;
  this.to = to;
  }
  
  static parse(s){
    console.log(`it's only for constructor`);
  }
};

let r = new Range(1, 3);
Range.parse(); // it's only for constructor
r.parse(); // Uncaught TypeError: r.parse is not a function

#private filed

클래스의 블럭 { } 내부 public field에서 바로 할당 = 한 변수는 인스턴스 객체의 프로퍼티로 호출 가능합니다.

class Buffer{
 from = 'made by buffer';
 size = 5;
}

let b = new Buffer();
b.from // 'made by buffer'
b.size // 5

반면에 #을 붙여서 private field 에서 할당한 변수는 class 바디 내부에서는 사용 가능하지만, 외부에서는 볼 수 없고 접근할 수도 없습니다.
즉 인스턴스 객체에서도 사용할 수 없습니다.

class Buffer{
 #from = 'made by buffer';
 #size = 5;
 get size(){return this.#size * 2; }
}

let b = new Buffer();
b.size // 10
b.from // undefined

Buffer.size // undefined

기존 클래스에 메서드 추가

객체를 생성 후 프로토타입의 프로퍼티가 바뀌더라도 상속 관계는 끊어지지 않습니다. 따라서 프로토타입 객체에 새로운 메서드를 추가하여 자바스크립트 클래스를 확장할 수 있습니다.

class People {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

class User extends People {}

People.prototype.greeting = function () {
  console.log("hi");
};

let j = new User("Joy", 10);
j.greeting(); // hi

심지어 내장된 클래스의 프로토타입 객체(String.prototype, Object.prototype 등)에도 새로운 메서드를 추가할 수 있습니다.
하지만 추후 자바스크립트 새 버전에서 같은 이름의 메서드를 정의할 경우 혼란을 주거나 호환성 문제를 야기할 수 있으므로 지양하는 것이 좋습니다.

  • Object.prototype에 추가한 프로퍼티는 for/in 루프에서 열거됩니다.
    • Object.defineProperty()를 써서 열거 불가로 설정할 수는 있습니다.
profile
React, Next.js, TypeScript 로 개발 중인 프론트엔드 개발자

0개의 댓글