본문 바로가기

JavaScript

[Deep dive] 25장 클래스 (2)

25.7 프로퍼티 

 

25.7.1 인스턴스 프로퍼티 

 

- 인스턴스 프로퍼티는 constructor 내부에서 정의해야 함

- constructor 내부에서 this에 추가한 프로퍼티는 언제나 클래스가 생성한 인스턴스의 프로퍼티가 됨

- 인스턴스 프로퍼티는 언제나 public 하다 

class Person {
  constructor(name) {
    //인스턴스 프로퍼티 
    this.name = name; //name 프로퍼티는 public하다
  }
}

const me = new Person('Lee');

//name은 public하다
console.log(me.name); //Lee

 

25.7.2 접근자 프로퍼티 

 

- 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수 (accessor function)로 구성된 프로퍼티 

 

- getter

 - 인스턴스 프로퍼티에 접근할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 때 사용

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

 - 호출하는 것이 아니라 프로퍼티처럼 참조하는 형식으로 사용

 - 참조 시에 내부적으로 getter가 호출 

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

 

- setter

 - 인스턴스 프로퍼티에 값을 할당할 때마다 프로퍼티 값을 조작하거나 별도의 행위가 필요할 떄 사용

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

 - 호출하는 것이 아니라 프로퍼티처럼 참조하는 형식으로 사용 

 - 할당 시에 내부적으로 setter가 호출

 - 반드시 매개변수가 있어야 함

 

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  //fullName은 접근자 함수로 구성된 접근자 프로퍼티 
  //getter 함수 
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  //setter 함수 
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(' ');
  }
}

const me = new Person('Ungmo','Lee');

//데이터 프로퍼티를 통한 프로퍼티 값의 참조 
console.log(`${me.firstName} ${me.lastName}`); //Ungmo Lee

//접근자 프로퍼티를 통한 프로퍼티 값의 저장 
//접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출
me.fullName = 'Heegun Lee';
console.log(me); //Person { firstName: 'Heegun', lastName: 'Lee' }

//접근자 프로퍼티를 통한 프로퍼티 값의 참조 
//접근자 프로피터 fullName에 접근하면 getter 함수가 호출 
console.log(me.fullName); //Heegun Lee

 

25.7.3 클래스 필드 정의 제안

 

클래스 필드 (class field)

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

 

클래스 필드 정의 제안 (Class field declarations)

- 인스턴스 프로퍼티를 마치 클래스 기반 객체지향 언어의 클래스 필드처럼 정의할 수 있는 새로운 표준 사항인 "Class field declartions"가 TC39 프로세스의 stage3(candidate)에 제안 

 

class Person {
 //클래스 필드 정의 
 name = 'Lee';
}

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

- 클래스 몸체에서 클래스 필드를 정의하는 경우 this에 클래스 필드를 바인딩해서는 안됨 

class Person {
 //this에 클래스 필드를 바인딩해선느 안됨
 this.name =' '; //SyntaxError
}

- 클래스 필드를 참조하는 경우 자바스크립트에서는 this를 반드시 사용해야함

- 클래스 필드에 초기값을 할당하지 않으면 undefined를 가짐

 

- 인스턴스를 생성할 때 외부의 초기값으로 클래스 필드를 초기화해야 할 필요가 있다면 constructor에서 클래스 필드를 초기화 해야함

class Person {
  name;
  constructor(name) {
    //클래스 필드 초기화 
    this.name = name;
  }
}

const me = new Person('Lee');
console.log(me); // Person {name:"Lee"}

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

class Person {
  //클래스 필드에 문자열을 할당
  name = 'Lee';

  //클래스 필드에 함수를 할당
  getName = function() {
    return this.name;
  }
}

const me = new Person();
console.log(me); //Person { name: 'Lee', getName: [Function: getName] }
console.log(me.getName()); //Lee

 

25.7.4 private 필드 정의 제안

 

- 자바스크립트에서 인스턴스 프로퍼티는 인스턴스를 통해 클래스 외부에서 언제나 참조가능 (언제나 public)

 

private 필드 정의 제안 

- TC39 프로세서의 stage 3(candidate)에는 private 필드를 정의할 수 있는 새로운 표준 사양이 제안됨

 

- private 필드의 선두에는 #을 붙여줌 

- private 필드를 참조할 때도 #을 붙여줘야 함

- private 필드는 클래스 내부에서만 참조 가능

- private 필드는 반드시 클래스 몸체에서 정의해야함

class Person {
   //private 필드 정의 
   #name = '';

   constructor(name) {
    //private 필드 참조 
    this.#name = name;
   }
}

const me = new Person('Lee');

//private 필드 #name은 클래스 외부에서 참조할 수 없다
console.log(me.#name); 
//SyntaxError: Private field '#name' must be declared in an enclosing class

- 접근자 프로퍼티를 통해 간접적으로 접근하는 방법은 유효 

class Person {
   //private 필드 정의 
   #name = '';

   constructor(name) {
 
    this.#name = name;
   }

   //name은 접근자 프로퍼티다
   get name() {
    //private 필드를 참조하여 trim한 다음 반환
    return this.#name.trim();
   }
}

const me = new Person(' Lee ');
console.log(me.name); //Lee

 

25.7.5 static 필드 정의 제안 

 

static 필드 정의 제안(static class features)

 - static public 필드, staic private 필드, static private 메서드를 정의할 수 있는 새로운 표준 사양이 TC39 프로세스의 stage3(candidate)에 제안

 

class MyMath {
  //static public 필드 정의 
  static PI = 22/7;

  //static private 필드 정의 
  static #num = 10;

  //static 메서드 
  static increment() {
    return ++MyMath.#num;
  }
}

console.log(MyMath.PI); //3.142857142857143
console.log(MyMath.increment());//11

 

25.8 상속에 의한 클래스 확장

 

25.8.1 클래스 상속과 생성자 함수 상속

 

- 프로토타입 기반 상속은 프로토타입 체인을 통해 다른 객체의 자산을 상속받는 개념

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

 

ex) 상속을 통해 Animal 클래스를 확장한 Bird 클래스 

class Animal {
  constructor(age,weight) {
    this.age = age;
    this.weight = weight;
  }

  eat() {return 'eat';}

  move() {return 'move';}
}

//상속을 통해 Animal 클래스를 확장한 Bird 클래스 
class Bird extends Animal {
  fly() { return 'fly';}
}

const bird = new Bird(1,5);

console.log(bird); //Bird { age: 1, weight: 5 }
console.log(bird instanceof Bird); //true
console.log(bird instanceof Animal); //true

console.log(bird.eat()); //eat
console.log(bird.move()); //move
console.log(bird.fly()); //fly

 

25.8.2 extedns 키워드 

 

- extends 키워드를 사용하여 상속받을 클래스를 정의

 

- 서브 클래스(subclass) = 파생 클래스 (derived class) = 자식 클래스 (child class)

 -> 상속을 통해 확장된 클래스 

 

- 수퍼 클래스 (super class) = 베이스 클래스 (base class) = 부모 클래스 (parent class)

 -> 서브클래스에게 상속된 클래스 

 

//수퍼 클래스 
class Base {}

//서브 클래스 
class Base extends Base {}

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

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

 

 

25.8.3 동적 상속

 

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

//생성자 함수 
function Base(a) {
  this.a = a;
}

//생성자 함수를 상속받는 서브클래스 
class Derived extends Base {}

const derived = new Derived(1);
console.log(derived); //Derived { a: 1 }

- extends 키워드 다음에는 [[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식 사용 가능 

- 동적으로 상속받을 대상 결정 가능

function Base1() {}

class Base2 {}

let condition = true;

//조건에 따라 동적으로 상속 대상을 결정하는 서브클래스 
class Derived extends (condition ? Base1 : Base2) {}

const derived = new Derived();
console.log(derived); //Derived {}

console.log(derived instanceof Base1); //true
console.log(derived instanceof Base2); //false

 

25.8.4 서브클래스의 constructor

 

- 클래스에서 constructor를 생략하면 비어있는 constructor가 암묵적으로 정의 

constructor() {}

- 서브클래스에서 constructor를 생략하면 다음과 같은 constructor가 암묵적으로 정의 

constructor(...args) {super(...args);}

- super()는 수퍼클래스의 constructor를 호출하여 인스턴스를 생성

 

 

25.8.5 super 키워드 

 

1) super 호출

 

- super를 호출하면 수퍼클래스의 constructor를 호출 

 

- 수퍼클래스의 constructor 내부에서 추가한 프로퍼티를 그대로 갖는 인스턴스를 생성한다면 서브클래스의 constructor 생략 가능

//수퍼클래스
class Base {
  constructor(a,b) {
    this.a = a;
    this.b = b;
  }
}

//서브클래스 
class Derived extends Base {
  //constructor(..args) {super(...args);}
}

const derived = new Derived(1,2);
console.log(derived); //Derived { a: 1, b: 2 }

 

- 수퍼클래스에서 추가한 프로퍼티와 서브클래스에서 추가한 프로퍼티를 갖는 인스턴스를 생성한다면 서브클래스의 constructor를 생략할 수 없음

//수퍼클래스 
class Base {
  constructor(a,b) {
    this.a = a;
    this.b = b;
  }
}

//서브클래스
class Derived extends Base {
  constructor(a,b,c) {
    super(a,b);
    this.c = c;
  }
}

const derived = new Derived(1,2,3);
console.log(derived); //Derived { a: 1, b: 2, c: 3 }

- 서브클래스에서 constructor를 생략하지 않는 경우, 서브클래스의 constructor에서는 반드시 super을 호출해야함

- 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없음

- super는 반드시 서브클래스의 constructor에서만 호출 해야함

 

2) super 참조 

 

- 메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출 가능

//수퍼클래스 
class Base {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    return `Hi! ${this.name}`;
  }
}

//서브클래스 
class Derived extends Base {
  sayHi() {
    //super.sayHi는 수퍼클래스의 프로토타입 메서드를 가리킴
    return `${super.sayHi()}.how are you doing?`;
  }
}

const derived = new Derived('Lee');
console.log(derived.sayHi()); //Hi! Lee.how are you doing?

- super는 자신을 참조하고 있는 메서드가 바인딩되어 있는 객체의 프로토타입(Base.prototype)을 가리킴

- super.sayHi 메서드는 Base.prototype.sayHi를 가리킴 

- Base.prototype.sayHi를 호출할 때 call 메서드를 사용해 this를 전달해야함

//수퍼클래스 
class Base {
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    return `Hi! ${this.name}`;
  }
}

//서브클래스 
class Derived extends Base {
  sayHi() {
    //__super는 Base.prototype을 가리킴
    const __super = Object.getPrototypeOf(Derived.prototype);
    return `${__super.sayHi.call(this)} how are you doing`;
  }
}

- super 참조가 동작하기 위해선느 super를 참조하고 있는 메서드가 바인딩되어 있는 객체의 프로토타입을 찾을 수 있어야함. 이를 위해 메서드는 메서드는 내부 슬롯 [[HomeObject]]를 가짐, 자신을 바인딩하고 있는 객체를 가리킴

 

- ES6의 메서드 축약 표현으로 정의된 함수만이 [[HomeObject]]를 가짐

 

- [[HomeObject]]를 가지는 ES6의 메서드 축약 표현으로 정의된 함수만이 super 참조를 할 수 있음

const obj = {
 //foo는 ES6의 메서드 축약 표현으로 정의한 메서드 
 //[[HomeObject]]를 가짐
 foo () {},
 
 //bar는 일반함수 
 //[[HomeObject]]를 가지지 않음
 bar: function () {}
 };

 

- 서브클래스의 정적 메서드 내에서 super.sayHi는 수퍼클래스의 정적 메서드 sayHi를 가리킴

//수퍼클래스 
class Base {
  static sayHi() {
    return `Hi!`;
  }
}

//서브클래스 
class Derived extends Base {
  //super.sayHi는 수퍼클래스의 정적메서드를 가리킴
  static sayHi() {
    return `${super.sayHi()} how are you doing?`;
  }
}

console.log(Derived.sayHi()); //Hi! how are you doing?

 

25.8.6 상속 클래스의 인스턴스 생성 과정

 

//수퍼클래스 
class Rectangle {
  constructor(width,height) {
    this.width = width;
    this.height = height;
  }

  getArea() {
    return this.width * this.height;
  }

  toString() {
    return `width= ${this.width}, height = ${this.height}`;
  }
}

//서브클래스 
class ColorRectangle extends Rectangle {
  constructor(width,height,color) {
    super(width,height);
    this.color = color;
  }

  //메서드 오버라이딩
  toString() {
    return super.toString() + `, color=${this.color}`;
  }
}

const colorRectangle = new ColorRectangle(2,4,'red');
console.log(colorRectangle); //ColorRectangle { width: 2, height: 4, color: 'red' }

//상속을 통해 getArea 메서드 호출
console.log(colorRectangle.getArea()); //8
//오버라이딩된 toString 메서드 호출
console.log(colorRectangle.toString()); //width= 2, height = 4, color=red

 

1) 서브클래스의 super 호출

 

- 자바스크립트 엔진은 클래스를 평가할 때 수퍼클래스와 서브클래스를 구분하기 위해 "base" 또는 "derived"를 값으로 갖는 내부 슬롯 [[ConstructorKind]]를 가짐

- 이를 통해 수퍼클래스와 서브클래스는 new 연산자와 함꼐 호출되었을 떄의 동작이 구분

- 서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스의 인스턴스 생성을 위임

- 서브 클래스 내부의 super 키워드가 호출되면 수퍼클래스의 constructor가 호출

 

2) 수퍼클래스의 인스턴스 생성과 this 바인딩

 

- 수퍼클래스의 constructor 내부의 this는 생성된 인스턴스를 가리킴

- new 연산자와 함께 호출된 함수를 가리키는 new.target은 서브클래스를 가리킴

- 인스턴스는 new.target이 가리킨느 서브클래스가 생성한것으로 처리 

 

3) 수퍼클래스의 인스턴스 초기화 

 

- 수퍼클래스의 constructor가 실행되어 this에 바인딩되어 있는 인스턴스를 초기화 

//수퍼클래스 
class Rectangle {
  constructor(width, height) {
    //암묵적으로 빈 객체, 즉 인스턴스가 생성되고 this에 바인딩됨
    console.log(this); //ColorRectangle {}
    //new.target은 ColorRectangle
    console.log(new.target); //ColorRectangle

    //생성된 인스턴스의 프로토타입으로 ColorRectangle.prototype이 설정
    console.log(Object.getPrototypeOf(this) === ColorRectangle.prototype); //true
    console.log(this instanceof ColorRectangle); //true
    console.log(this instanceof Rectangle); //true

    //인스턴스 초기화 
    this.width = width;
    this.height = height;

    console.log(this); //ColorRectangle {width: 2, height: 4}
  }
}

 

4) 서브클래스 constructor로의 복귀와 this 바인딩

 

- 서브클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용

//서브클래스 
class ColorRectangle extends Rectangle {
  constructor(width, height, color) {
    super(width, height);

    //super가 반환한 인스턴스가 this에 바인딩
    console.log(this); //ColorRectangle {width: 2, height: 4}
  }
}

 

5) 서브클래스의 인스턴스 초기화 

 

- super 호출 이후, 서브클래스의 constructor에 기술되어 있는 인스턴스 초기화가 실행

 

6) 인스턴스 반환

 

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

 

 

25.8.7 표준 빌트인 생성자 함수 확장

 

- String, Number, Array 같은 표준 빌트인 객체도 [[Construct ]] 내부 메서드를 갖는 생성자 함수이므로 extends 키워드를 사용하여 확장 가능 

 

ex) 

Array 생성자 함수를 상속받아 확장한 MyArray 클래스

//Array 생성자 함수를 상속받아 확장한 MyArray
class MyArray extends Array {
  //중복된 배열 요소를 제거하고 반환
  uniq() {
    return this.filter((v,i,self) => self.indexOf(v) === i);
  }

  //모든 배열 요소의 평균을 구함
  average() {
    return this.reduce((pre,cur) => pre +cur,0)/this.length;
  }
}

const myArray = new MyArray(1,1,2,3);
console.log(myArray); //MyArray(4) [ 1, 1, 2, 3 ]

//MyArray.prototype.uniq 호출
console.log(myArray.uniq()); //MyArray(3) [ 1, 2, 3 ]
//MyArray.prototype.average 호출
console.log(myArray.average()); //1.75

- MyArray 클래스가 생성한 인스턴슨느 Array.prototype과 MyArray.prototype의 모든 메서드 사용 가능

- Array.prototype 메서드 중에서 map, filter와 같이 새로운 배열을 반환하는 메서드는 MyArray 클래스의 인스턴스를 반환

 -> MyArray 클래스의 메서드와 메서드 체이닝(method chaining) 가능 

'JavaScript' 카테고리의 다른 글

[Deep dive] 27장 배열(1)  (0) 2023.08.16
[Deep dive] 26장 ES6 함수의 추가 기능  (0) 2023.08.16
[Deep dive] 25장 클래스 (1)  (0) 2023.08.15
[Deep dive] 24장 클로저  (0) 2023.08.14
[Deep dive] 23장 실행 컨텍스트  (0) 2023.08.14