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 |