팀원들과 함께하는 모던 JS 딥다이브 스터디 9차 💕
클래스 필드의 이름과 constructor 내부에서 this에 할당한 프로퍼티 이름이 같을 때 어느 것이 우선시될까?
클래스 필드가 선언된 위치에 상관없이 이름이 중복될 경우 constructor가 항상 우선시되는 걸 확인할 수 있었다.
클래스 필드 또한 인스턴스의 프로퍼티로 할당이 될 텐데, 클래스 필드를 아래에 선언했음에도 불구하고 constructor가 왜 우선시될까?
생성자는 static 메서드로 자신의 메서드를 갖지만 인스턴스는 어떻게 자신의 메서드를 가질까?
클래스 필드를 통해 인스턴스 메서드를 정의할 수 있다.
모든 클래스 필드는 인스턴스의 프로퍼티가 되기 때문이다.
class Person {
name = 'Jeongs'
getName = function () {
return this.name
}
}
const me = new Person()
me.getName() // 'Jeongs'
console.log(Object.getPrototypeOf(me)) // constructor
Person.prototype을 출력한 결과 getName 메서드가 존재하지 않고,
인스턴스의 메서드로 호출된 것을 확인할 수 있다.
따라서 인스턴스의 메서드를 정의하고 싶을 땐 클래스 필드에 함수를 할당하고,
클래스 몸체에서 메서드 축약 표현을 통해 메서드를 정의할 경우 프로토타입의 메서드가 된다.
마지막으로 클래스 자체에 메서드를 정의하고 싶을 땐 정적 메서드 키워드 static을 활용해 정의한다.
클래스 필드에 관한 토론
클래스 필드 사용 목적 추론
- Java에서 가진 클래스 기능 중하나인 private, static 프로퍼티 기능을 사용하기 위해서 추가되었다고 볼수 있다.
- private을 의미하는 #prop 은, constructor 메소드 내부에서 선언되어질수 없다. 이를 활용하려면 반드시 클래스 필드에서의 선언이 필요함. static도 마찬가지이다.
- 이때를 제외하면, constructor가 가지는 강점인 프로퍼티의 동적할당을 사용하지 못하는점에서, 큰 활용 가능성을 가지지 못함
- 그리고 아직 공식적으로 추가된 기술이 아니다. ( 크롬과 NodeJS 12v 이상에선 사용 가능 )
class Base {
constructor(a, b) { // 인스턴스 초기화
this.a = a
this.b = b
}
}
class Derived extends Base {
constructor() {
super()
console.log('super') //
}
}
const derived = new Derived(1, 2)
console.log(derived) // Derived { a: undefined, b: undefined }
서브클래스의 constructor를 호출 시 super에 아무것도 전달하지 않으면 수퍼 클래스에 인수를 전달하지 못하므로 undefined가 출력되는 것을 확인했다.
class Base {
constructor(a, b) { // 인스턴스 초기화
this.a = a
this.b = b
}
}
class Derived extends Base {
constructor(...args) {
super(...args)
console.log('super') //
}
}
const derived = new Derived(1, 2)
console.log(derived) // Derived { a: 1, b: 2 }
new 키워드로 Derived 클래스를 호출할 때의 인수를 ...args를 통해 인수로 받고 super의 인자로 넣어줬을 때야 비로소 상속받아 출력결과로 나타낼 수 있었다.
왜 서브클래스에서 super를 직접 표기해서 사용해야 할까? 왜 자동으로 상속시켜주지 않을까?
만약 자동으로 슈퍼클래스의 프로퍼티를 상속 받아서 입력 받을 파라미터들이 추가됐다면, 서브클래스에서 전달 받는 인수들이 어떤 파라미터에 들어가야 하는지 그 순서를 보장할 수 있는 방법이 마땅치 않아서 그런 것 아닐까?
만약 key-value 형태인 객체를 인수로 전달받는다면 슈퍼클래스에서 어떤 순서로 파라미터를 가져오더라도 key 값을 통해서 매칭시키기 때문에 순서가 섞일 일이 없다. 마치 리액트에서 prop을 '객체' 형태로 넘겨주듯이..!
그래서 클래스의 인스턴스를 생성할 때 객체 형태로만 값을 받았다면 자동으로 상속시켜주는 기능이 구현될 수도 있지 않았을까?
super 키워드를 통해 수퍼 클래스를 참조할 경우 내부메서드 [[HomeObject]]으로 메서드 자신을 바인딩하고 있는 객체의 프로토타입을 찾을 수 있다.
class Base {
constructor(name) {
this.name = name
}
sayHi() {
return `sayHi!`
}
}
class Derived extends Base {
sayHi() {
return `${super.sayHi()}`
}
}
const derived = new Derived('Jeongs')
console.log(derived.sayHi()) // 'sayHi!'
예를 들어 서브 클래스에서 super 참조로 sayHi를 호출할 경우를 예로 들어보자.
super를 참조하고 있는 메서드 sayHi의 [[HomeObject]]가 자신을 바인딩하고 있는 객체, 즉 Derived.prototype 의 prototype인 Base.prototype을 가리키게 된다.
Derived.prototype에 왜 Base가 적혀있는 걸까?
constructor가 Derived 클래스를 가리키고 있는 걸로 봐선 맞는데...
모든 프로토타입의 프로토타입은 Object 이므로 프로토타입 체인을 따라가기 위해선 proto 나 Object.getPrototypeOf로 확인해보았다.
Base.prototype의 프로토타입 또한 Object.prototype으로 잘 출력되었다.
Base의 의미가 궁금하다 나중에 스터디원들과 논의해봐야겠다!
Derived.prototype에 왜 상위 프로토타입의 constructor인 Base의 이름이 찍힐까?
여러가지 추론을 해본 결과 마땅한 의견이 나오지 않고 Node.js 환경과 브라우저 환경에서 나오는 출력 결과가 달라 단순히 실수일 거라고 결론이 나왔는데 영 찝찝하다.
(Node.js 환경에서는 원래의 Derived.prototype이 가리켜야 하는 Derived의 이름이 잘 출력되었다.)