🙄클래스는 프로토타입의 문법적 설탕일까요?
자바스크립트는 프로토타입 기반의 객체지향 언어이고
프로토타입 기반 객체지향 언어는 클래스가 필요 없는 객체지향 언어입니다.
자바스크립트의 클래스는 사실 함수이고 기존의 프로토타입 기반의 패턴을
클래스 기반 패턴처럼 사용할 수 있도록 해주는 문법적 설탕이라고 볼수도 있습니다.
다만 자바스크립트에서의 클래스는 생성자 함수 기반의 객체 생성보다 명료하다는 장점이 있습니다.
따라서 클래스는 단순 문법적 설탕이라기보다는
새로운 객체 생성 메커니즘으로 보는게 좀 더 합당하다네요
🙄클래스와 생성자 함수의 차이에 대해 말해주세요
구분 | 클래스 | 생성자 함수 |
new연산자 | new 연산자 없이 호출 시 에러 | new 연산자 없이 호출하면 일반 함수로 호출 됨 에러 안남 |
상속키워드 | extends, super키워드 제공 | extends, super 키워드 없음 |
호이스팅 | 발생하지 않는 것처럼 동작 | 함수 선언문으로 정의된 생성자함수는 함수 호이스팅이 발생함 함수 표현식으로 정의된 생성자함수는 변수 호이스팅이 발생함 |
strict mode | 클래스 내의 모든 코드는 암묵적으로 strict mode가 지정되어 실행되며 해제할 수 없음 |
생성자 함수는 암묵적으로 strict mode가 지정되지 않음 |
[[Enumerable]] | 클래스의 constructor, 프로토타입 메서드 정적 메서드는 모두 프로퍼티 어트리뷰트 값이 [[Enumerable]] 값이 false입니다. 즉 열거되지 않습니다. |
? |
🙄클래스 정의
클래스는 class 키워드를 사용해서 정의합니다.
클래스 이름은 생성자 함수처럼 파스칼 케이스를 사용하는 게 일반적이지만
파스칼 케이스를 사용하지 않아도 에러가 발생하지는 않습니다.
//클래스 선언문
class Peron {}
//익명 클래스 표현식
const person = class {}
//기명 클래스 표현식
const serson = class Myclass {}
//클래스 선언문 생성자 인스턴스 생성
class Person {
// 생성자
constructor(name) {
//인스턴스 생성 및 초기화
this.name = name ; // name 프로퍼티는 public합니다.
}
//프로토타입 메서드
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
🙄클래스는 일급객체인가요?
위 예제를 보면 클래스는 표현식으로 정의할 수 있었습니다.
이것은 클래스를 값으로 사용할 수 있다는 뜻입니다.
클래스는 일급 객체로서 다음과 같은 특징을 가집니다.
1. 무명의 리터럴로 생성할 수 있습니다 런타임에 생성이 가능합니다.
2. 변수나 자료구조에 저장할 수 있습니다.
3. 함수의 매개변수에게 전달할 수 있습니다.
4. 함수의 반환값으로 사용할 수 있습니다.
🙄클래스 몸체에 정의할 수 있는 메서드는 몇가지인가요?
총 세가지입니다.
1. 생성자 : constructor
2. 프로토타입 메서드
3. 정적 메서드
🙄클래스 호이스팅
클래스는 함수로 평가됩니다.
그렇기 때문에 클래스 선언문으로 정의한 클래스는 함수 선언문과 같이 소스코드 평가 과정
즉 런타임 이전에 먼저 평가되어서 함수 객체를 생성합니다.
이때 클래스가 평가되어 생성된 함수 객체는 constructor 입니다.
constructor가 생성되는 시점에 프로토타입도 같이 생성됩니다.
**클래스는 클래스 정의 이전에 참조할 수 없습니다.
클래스 선언문은 마치 호이스팅이 발생하지 않는 것처럼 보이지만 그렇지 않습니다.
const Person = '';
{
console.log(Person)
class Person {}
}
//클래스 선언문도 호이스팅이 발생하며 let, const 키워드랑 비슷하게 호이스팅 됨
// 하지만 클래스 선언문 이전에 TDZ에 빠지지 않기 때문에 호이스팅이 발생 안하는 것처럼 동작함
🙄인스턴스 생성
클래스는 생성자 함수이며 new 연산자와 함께 호출되어 인스턴스를 생성합니다.
클래스는 인스턴스를 생성하기 위해서만 존재하므로 반드시 new 연산자를 함께 호출해야합니다.
그렇지 않으면 타입에러가 발생해요
클래스 표현식으로 정의한 경우에는 클래스 이름을 사용하면 오류가 납니다.
클래스를 가리키는 식별자로 인스턴스를 생성해야해요!
🙄클래스의 메서드 constructor
constructor는 인스턴스를 생성하고 초기화하기 위한 특수한 메서드입니다.
constructor는 이름을 변경할 수 없습니다.
왜 다 이름을 constructor로 쓰나 했는데 변경 불가라서 그런거였군요
클래스는 함수객체이고 클래스는 따라서 함수 객체 고유의 프로퍼티도 갖고 있으며
프로토타입도 연결되어 있고 자신의 스코프 체인을 구성합니다.
1. constructor 이름 변경 불가능
2. constructor 는 별도의 반환문을 가지면 안됩니다.
3. 클래스가 생성한 인스턴스엔 constructor 메서드가 없음
🙄consturctor는 왜 반환문을 가지면 안되나요?
왜냐하면 new 연산자와 함께 클래스가 호출되면 생성자 함수와 동일하게
암묵적으로 this 즉 인스턴스가 반환되기 때문입니다.
만약 this가 아닌 다른 객체를 명시적으로 반환하면 인스턴스가 반환되지 못하고
return 문에 명시한 객체가 반환되어 버리기 때문에 인스턴스를 반환받지 못하는 문제가 생깁니다.
🙄왜 클래스가 생성한 인스턴스는 constructor 메서드가 없나요?
constructor는 메서드로 해석되는 것이 아니라
클래스가 평가되어 생성한 함수 객체 코드의 일부가 됩니다.
클래스 정의가 평가되면 constructor의 기술된 동작을 하는 함수 객체가 생성된다는 뜻입니다.
🙄constructor의 특징을 말해주세요
1. constructor는 클래스내에 최대 한개만 존재할 수 있습니다.
만약 클래스가 두개 이상의 constructor를 포함하면 문법에러가 발생합니다.
2. constructor는 생략할 수 있습니다.
만약 constructor를 생략하면 클래스에 빈 constructor가 암묵적으로 정의됩니다.
생략한 클래스는 암묵적으로 정의된 constructor에 의해 빈 객체를 생성합니다.
🙄프로퍼티가 추가되어 초기화된 인스턴스를 생성하려면 어카나요?
constructor 내부에서 this에 인스턴스 프로퍼티를 추가해주면 됩니다.
아래 예제를 보면 constructor 내에서는
인스턴스의 생성과 동시에 인스턴스 프로퍼티 추가를 통해 인스턴스의 초기화를 실행합니다.
따라서 인스턴스를 초기화하고자 한다면 constructor를 생략해서는 안됩니다.
class Person {
constructor() {
//고정값으로 인스턴스 초기화
this.name = "lee"
this.address = "Seoul"
}
}
const me = new Person()
console.log(me)
class Person {
constructor(name, address) {
//인수로 인스턴스 초기화
this.name = name
this.address = address
}
}
//인수로 초기값을 전달한다. 초기값은 constructor에 전달됩니다.
const me = new Person("lee","seoul")
console.log(me)
🙄프로토타입 메서드
생성자 함수를 사용하여 인스턴스를 생성하는 경우에는
아래 예제와 같이 명시적으로 프로토타입에 메서드를 추가해줘야 합니다.
//생성자함수
function Person(name) {
this.name = name;
Person.prototype.sayHi = function () {
console.log(`hi my name is ${this.name}`)
}
}
const me = new Person()
me.sayHi()
반면 클래스를 사용하는 경우는 다음과 같습니다.
클래스 몸체에서 정의한 메서드는 생성자 함수에 의한 객체 생성 방식과는 다르게
클래스의 prototype 프로퍼티에 메서드를 추가하지 않아도
기본적으로 프로토타입 메서드가 됩니다.
class Person {
constructor (name) {
this.name = name
}
//프로토타입 메서드
sayHi() {
console.log(`hi my name is ${this.name}`)
}
}
const me = new Person('lee')
me.sayHi()
훨씬 편리하네요
.prototype.sayhi() 이런식으로 쓰지 않아도 되니까요!!
또한 생성자 함수와 마찬가지로 클래스가 생성한 인스턴스는
프로토타입 체인의 일원이 됩니다.
// me 객체의 프로토타입은 Person.prototype 입니다.
console.log(Object.getPrototypeOf(me) === Person.prototype) // true
console.log(me instanceof Person) // true
// Person.prototype의 프로토타입은 object.prototype 입니다.
console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype) //true
console.log(me instanceof Object) //true
//me 객체의 constructor는 person 클래스입니다.
console.log(me.constructor === Person) // true
이렇게 클래스 몸체에서 정의한 메서드는 인스턴스의 프로토타입에 존재하는
프로토타입 메서드가 됩니다.
인스턴스는 프로토타입 메서드를 상속받아 사용할 수 있습니다.
결국 클래스는 생성자 함수처럼 인스턴스를 생성해주는 생성자 함수라고 할 수 있고
프로토타입 기반의 객체 생성 매커니즘이라고 볼 수 있습니다.
🙄정적 메서드에 대해 말해주세요
정적 메서드는 인스턴스를 생성하지 않아도 호출할 수 있는 메서드를 말합니다.
생성자 함수의 경우에는 정적 메서드를 생성하기 위해서
아래와 같이 생성자 함수에 명시적으로 메서드를 추가해줘야 합니다.
function Person(name) {
this.name = name
}
//정적 메서드 추가해주기
Person.sayHi = function () {
console.log(hi)
}
// 정적 메서드 호출
Person.sayHi()
하지만 클래스에서는 그냥 메서드 안에 static 키워드만 붙여주면 정적 메서드가 됩니다.
클래스는 클래스 정의가 평가되는 시점에 함수 객체가 되므로
인스턴스와 달리 별도의 생성 과정이 필요 없습니다.
따라서 정적 메서드는 클래스 정의 이후 인스턴스를 생성하지 않아도 호출 할 수 있습니다.
정적 메서드는 프로토타입 메서드처럼 인스턴스로 호출하지 않고 클래스로 호출합니다.
또한 정적 메서드는 인스턴스로 호출할 수 없습니다.
정적 메서드는 클래스에 바인딩 되어 있는 메서드이기 때문에
인스턴스의 프로토타입 체인에 존재하지 않기 때문입니다.
class Person {
constructor (name) {
this.name = name
}
static sayHi() {
console.log('hi')
}
}
Person.sayHi()
🙄정적 메서드와 프로토타입 메서드의 차이
구분 | 정적 메서드 | 프로토타입 메서드 |
프로토타입 체인 | 정적메서드와 프로토타입 메서드는 속해있는 프로토타입 체인이 다름 | |
호출 | 클래스로 호출해야함 | 인스턴스로 호출해야함 |
참조 | 인스턴스 프로퍼티 참조 불가능 | 인스턴스 프로퍼티 참조 가능 |
🙄 정적 메서드의 장점은 뭔가요?
클래스나 생성자 함수를 하나의 네임스페이스로 사용하여서 정적 메서드를 모아두면
이름 충돌 가능성을 줄여주고 관련 함수들을 구조화할 수 있다는 장점이 있습니다.
따라서 정적 메서드는 애플리케이션 전역에서 사용할 유틸리티 함수를
전역 함수로 정의하지 않고 구조화할 때 유용합니다.
🙄클래스에서 정의한 메서드의 특징은 뭔가요?
1. function 키워드를 생략한 메서드 축약 표현을 사용합니다.
2. 객체 리터럴과는 다르게 클래스에 메서드를 정의할 때는 콤마가 필요 없습니다.
3. 암묵적으로 strict mode로 실행됩니다.
4. 열거 불가능합니다.
5. non-constructor여서 new 연산자와 함께 호출할 수 없습니다.
🙄클래스의 인스턴스 생성과정에 대해 설명해주세요
new 연산자와 클래스를 함께 호출하면 생성자 함수와 마찬가지로
construct 내부 메서드가 호출되며 클래스는 new 연산자 없이 호출이 안됩니다.
1. 인스턴스 생성과 this 바인딩
이 시점에서 암묵적으로 빈 객체가 생성됩니다 이때 클래스가 생성한 인스턴스의 프로토타입으로
클래스의 prototype 프로퍼티가 가리키는 객체가 설정됩니다.
그리고 인스턴스는 this에 바인딩됩니다.
2. 인스턴스 초기화
constructor 내부 코드가 실행되며 this에 바인딩되어있는 인스턴스를 초기화해줍니다.
만약 constructor가 생략되었다면 이 과정은 생략됩니다.
3. 인스턴스 반환
클래스의 모든 처리가 끝나면 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환됩니다.
🙄인스턴스 프로퍼티에 대해 말해주세요
인스턴스 프로퍼티는 constructor 내부에서 정의해줘야 합니다.
constructor 내부 코드가 실행되기 전에 이미
constructor의 내부에는 빈 객체가 바인딩되어 있습니다.
생성자 함수에서 생성자 함수가 생성할 인스턴스의 프로퍼티를 정의하는 것처럼
constructor 내부에서 this에 인스턴스 프로퍼티를 추가해주면
인스턴스에 프로퍼티가 추가되어 초기화됩니다
다만 constructor 내부에서 this에 추가한 프로퍼티는 언제나 클래스가 생성한 인스턴스의 프로퍼티가 되는데
자바스크립트의 클래스는 항상 Public하다는 문제점이 있습니다.
다른 객체지향언어들은 private, protected 등의 접근제한자를 제공하는 것과 대비됩니다.
🙄접근자 프로퍼티에 대해 말해주세요
접근자 프로퍼티는 자체적으로 값[[Value]]내부슬롯을 갖지 않고 다른 데이터 프로퍼티의
값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티입니다.
getter, setter함수를 떠올리면 됩니다.
이 접근자 프로퍼티는 클래스에서도 사용할 수 있습니다.
클래스의 메서드는 기본적으로 프로토타입 메서드가 되며 따라서 클래스의 접근자 프로퍼티 또한
인스턴스 프로퍼티가 아니라 프로토타입의 프로퍼티가 됩니다.
즉 getter는 호출하는 것이 아니라 프로퍼티처럼 참조하는 형식으로 사용됩니다.
참조 -> 내부적으로 getter 호출 / 참조 -> 내부적으로 setter 호출 이런식인거네요
🙄클래스 필드?가 뭔가요?
클래스 필드가 뭔가요?
클래스 필드란 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어입니다.
🙄상속에 의한 클래스 확장이 뭔가요?
상속에 의한 클래스 확장은 기존 클래스를 상속받아
새로운 클래스를 확장(extends)하여 정의하는 것을 말합니다.
클래스와 생성자 함수는 둘 다 인스턴스를 생성한다는 점에선 비슷하지만
클래스는 상속을 통해 기존 클래스를 확장할 수 있는 문법이 제공되는 한편
생성자 함수는 그렇지 않다는 특징이 있습니다.
class Animal {
constructor(age, weight) {
this.age = age
this.weight = weight
}
eat() {return 'eat'};
move() {return 'move'};
}
//상속을 통해 Anima 클래스를 확장한 bird 클래스
class Bird extends Animal {
fly() {return 'fly'}
}
const bird = new Bird(1,5)
console.log(bird)
console.log(bird.eat())
console.log(bird.fly())
extends 키워드를 통해 상속을 받을 수가 있군요
이렇게 상속을 통해서 확장된 Bird 클래스 같은 경우를 서브 클래스라고 부르며
서브클래스에게 상속된 클래스를 수퍼클래스라고 부릅니다.
수퍼클래스와 서브클래스는 인스턴스의 프로토타입 체인 뿐아니라
클래스 간의 프로토타입 체인도 생성하며
이를 통해서 프로토타입 메서드, 정적 메서드 모두 상속이 가능합니다.
🙄동적상속??
extends 키워드는 클래스 뿐만 아니라 생성자 함수를 상속받아서 클래스를 확장할 수도 있습니다.
다만 extends 키워드 앞에는 반드시 클래스가 와야합니다.
extends 키워드는 동적으로 상속받을 대상을 결정할 수 있습니다.
function Base1 () {}
class Base2 {}
let condition = true
class Derived extends (condition ? Base1 : Base2) {};
const derived = new Derived()
console.log(derived)
삼항연산자로 상속받을 수퍼클래스를 고를수가있네요..?
🙄서브클래스의 constructor
서브클래스에서 constructor를 생략하면 클래스에 다음과 같은 constructor가 암묵적으로 정의됩니다.
constructor(...args) { super(...args)}
🙄super 키워드
super 키워드는 함수처럼 호출할 수도 있고 this와 같이 식별자처럼 참조할 수도 있는 특수한 키워드입니다.
super는 다음과 같이 동작합니다.
1. super를 호출하면 수퍼클래스의 constructor를 호출합니다.
2. super를 참조하면 수퍼클래스의 메서드를 호출할 수 있습니다.
//수퍼클래스
class Base {
constructor(a,b) {
this.a = a
this.b = b
}
}
//서브클래스
class Derived extends Base {
//다음과 같이 암묵적으로 constructor가 정의됩니다.
//constructor(...args) {super(...args)}
}
const derived = new Derived(1,2)
console.log(derived)
위 예제와 같이 수퍼클래스의 constructor 내부에서 추가한 프로퍼티를 그대로 갖는 인스턴스를 생성한다면
서브클래스의 construcotr를 저렇게 생략해도 암묵적으로 정의된
constructor의 super 호출을 통해 수퍼클래스의 cconstructor에 전달됩니다.
class Yes extends Base {
constructor(a,b,c) {
super(a,b)
this.c = c
}
}
const yes = new Yes(1,2,3)
console.log(yes)
super를 호출할 때에는 주의사항이 있습니다.
1. 서브클래스에서 constructor를 생략하지 않은 경우에는 반드시 서브 클래스의 constructor에서
super를 호출해야 합니다.
2. 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없습니다.
3. super는 반드시 서브클래스의 constructor에서만 호출합니다.
서브클래스가 아닌 클래스의 constructor나 함수에서 super를 호출하면 에러가 발생합니다.
또한 메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출할 수도 있습니다.
//수퍼클래스
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?`
}
}
const d = new Derived('dsamk') // hi dsamk how are you doing?
console.log(d.sayHi())
super 참조를 통해 수퍼클래스의 메서드를 참조하려면
super가 수퍼클래스의 메서드가 바인딩 된 객체를 참조할 수 있어야합니다.
위 예제에서 super는 자신을 참조하고 있는 메서드가 바인딩되어 있는 객체의 프로토타입을 가리킵니다.
따라서 super.sayHi는 Base.prototype.sayHi를 가리킵니다.
1. 서브클래스의 프로토타입 메서드 내에서 super.메서드는 수퍼클래스의 프로토타입 메서드를 가리킵니다.
2. 서브클래스의 정적 메서드 내에서 super.메서드는 수퍼클래스의 정적 메서드를 가리킵니다.
🙄상속클래스의 인스턴스 생성 과정
1. 서브클래스의 super 호출
// 자바스크립트 엔진은 클래스를 평가할 때 수퍼클래스와 서브클래스를 구분하기 위해
"base" 또는 "derived"를 값으로 갖는 내부 슬롯 [[constructorKind]]를 가집니다.
다른 클래스를 상속받지 않는 클래스와 생성자 함수는 이 내부슬롯의 값이 base이고
다른 클래스를 상속받는 서브클래스는 이 내부 슬롯의 값이 "derived"입니다.
이를 통해서 수퍼클래스와 서브클래스는 new 연산자와 함께 호출되었을때 동작이 구분됩니다.
서브클래스는 자신이 직접 인스턴스를 생성하는게 아니라 수퍼클래스에게 인스턴스 생성을 위임합니다.
이것이 서브클래스의 constructor에 반드시 super를 호출해야하는 이유입니다.
서브클래스가 new와 함께 호출되면 super 키워드가 함수처럼 호출되며
super 키워드가 호출되면 수퍼클래스의 constructor가 호출됩니다.
만약 서브 클래스의 constructor 내부에 super 호출이 없으면 에러가 발생합니다.
why?
실제로 인스턴스를 생성하는 것은 수퍼클래스이므로 수퍼클래스의 constructor를 호출해주는
super 키워드가 호출되지 않으면 인스턴스를 생성할수가없기 때문입니다.
2. 수퍼클래스의 인스턴스 생성과 this 바인딩
// 수퍼클래스의 constructor 내부의 코드가 실행되기 이전에 빈 객체를 생성합니다.
그리고 이 빈 객체는 this에 바인딩됩니다.
수퍼클래스의 constructor 내부의 this는 생성될 인스턴스를 가리킵니다.
** 인스턴스는 수퍼클래스가 생성하지만 new 연산자와 함께 호출된 클래스가 서브클래스라면
new 연산자와 함께 호출된 함수를 가리켜주는 new.target은 서브클래스를 가리킵니다.
즉 인스턴스는 new.target이 가리키는 서브 클래스가 생성한 것으로 처리됩니다.
3. 수퍼클래스의 인스턴스 초기화
//this에 바인딩되어있는 인스턴스를 초기화합니다.
즉 this에 바인딩되어있는 인스턴스에 프로퍼티를 추가하고 인수로 전달받은 초기값으로
인스턴스를 초기화해주는 작업을 실행합니다.
4. 서브클래스 constructor로 복귀 this 바인딩
//super 호출이 끝나고 제어 흐름이 서브클래스의 constructor로 돌아옵니다.
이때 super가 반환한 인스턴스가 this에 바인딩됩니다.
서브클래스는 별도의 인스턴스를 생성하는게 아닌 super가 반환한 인스턴스를 this에 바인딩해서 사용합니다.
즉 super가 호출되지 않으면 인스턴스도 생성되지 않고 this 바인딩도 할 수 없으며
서브클래스의 constructor에서 super를 호출하기 전엔 this를 참조할 수 없는 이유가 이 때문입니다.
5. 서브클래스의 인스턴스 초기화
//super 호출 이후 서브 클래스의 constructor에 기술되어있는 인스턴스 초기화가 실행됩니다.
this에 바인딩되어있는 인스턴스에 프로퍼티를 추가해주고 constructor가 인수로 전달받은 초기값으로
인스턴스의 프로퍼티를 초기화해줍니다.
6. 인스턴스 반환
모든 처리가 끝나면 인스턴스가 바인딩된 this가 암묵적으로 반환됩니다.
수고하셨습니다.
반응형
'javascript' 카테고리의 다른 글
요약 정리는 못 참지 않을까요? (10) 배열 (0) | 2023.01.16 |
---|---|
요약 정리는 못 참지 않을까요? (9) ES6 함수의 추가 기능 (0) | 2023.01.16 |
요약 정리는 못 참지 않을까요?(7) 클로저 (0) | 2023.01.13 |
자바스크립트의 this를 이해해보자 (0) | 2023.01.12 |
요약 정리는 못 참지 않을까요? (6) (0) | 2023.01.12 |