![[js] Class](/_next/image?url=%2Fimages%2Fempty.png&w=2048&q=75)
Class
클래스는 프로토타입의 문법적 설탕인가?
클래스의 정의
클래스 호이스팅
인스턴스 생성
메서드
constructor
프로토타입 메서드
정적 메서드
정적 메서드와 프로토타입 메서드의 차이
클래스에서 정의한 메서드의 특징
클래스와 인스턴스 생성과정
프로퍼티
인스턴스 프로퍼티
접근자 프로퍼티
클래스 필드 정의 제안
private 필드 정의 제안
stasic 필드 정의 제안
클래스 상속과 생성자 함수 상속
extends 키워드
동적 상속
서브클래스의 constructor
super 키워드
상속클래스의 인스턴스 생성과정
Class
2022년 8월 22일클래스는 프로토타입의 문법적 설탕인가?
-
자스 : 프로토타입 기반 객체지향 언어 -> 클래스가 필요 x
-
프로토타입(Prototype - 원형) : 본격적인 개발에 앞서 요소나 구현 가능성 등을 검증하기 위해 간단하게 핵심 기능만 구현해 제작한것을 뜻함
-
Syntactic sugar : 문법에 설탕을 뿌려 더 달콤하게 사용한다는 의미다. 문법적으로 더 읽기 쉽고 이해하기 편하도록 표현되어지는 것을 말하는 것으로 적절하게 사용되면 가독성(readibility)을 높이기 매우 좋다.
//생성자 함수 var 닝겐 = (function () { function 닝겐(이름) { this.이름 = 이름; } //프로토타입 메서드 닝겐.prototype.안녕 = function () { console.log("안녕? 나는 " + this.이름); }; //생성자 함수 반환 return 닝겐; })(); // 인스턴스 생성 var 나 = new 닝겐("yelim"); 나.안녕(); // 안녕? 나는 yelim
- 클래스와 생성자 함수는 모두 프로토 타입 기반의 인스턴스를 생성 -> 동일하게 동작 x
- 클래스는 생성자 함수보다 엄격, 생성자 함수에서 제공하지 않는 기능도 제공
- 클래스를 new 연산자 없이 호출시 에러 발생, 생성자 함수 new 연산자 없이 호출시 일반함수로서 호출
- 클래스 상속을 지원하는 extense와 super 키워드를 제공한. 생성자 함수는 extense와 super 키워드를 지원하지 x
- 클래스는 호이스팅이 발생하지 않는것처럼 동작 -> 함수선언문(함수호이스팅), 함수표현식(변수호이스팅) 발생
- 클래스 모든코드 -> stric mode (암묵적)/ 생성자 함수 -> stric mode x
- 클래스 constructor, 프로토타입 메서드, 정적 메서드 -> 프로토타입 어트리뷰트 값 -> false -> 열거 x
- 클래스 : 새로운 객체 생성 매커니즘
클래스의 정의
//클래스 선언문 - 파스칼케이스 사용 class 닝겐 {} //익명 클래스 표현식 const 닝겐 = class {}; //기명 클래스 표현식 const 닝겐 = class 내클 {};
-
클래스를 표현식으로 정의가 된다? 클래스가 값으로 사용되는 일급객체라는 것을 의미
- 무명의 리터럴로 생성/ 런타임에 생성 ㄱㄴ
- 변수나 자료구조(객체, 배열 등)저장 ㄱㄴ
- 함수의 매개변수에게 전달 ㄱㄴ
- 함수반환 값으로 사용 ㄱㄴ
-
클래스는 함수
-
클래스 몸체에는 0개이상의 메서드만 정의 ㄱㄴ(constructor(생성자), 프로토타입 메서드, 정적 메서드)
//클래스 선언문 class 닝겐 { //생성자 constructor(이름) { //인스턴스 생성 및 초기화 this.이름 = 이름; ///name 프로퍼티는 public 하다. } //프로토타입 메서드 안녕() { console.log(`안녕? 나는 ${this.이름}`); } //정적 메서드 static 안녕해() { console.log("안녕!"); } } //인스턴스 생성 const 나 = new 닝겐("yelim"); //인스턴스의 프로퍼티 참조 console.log(나.이름); // yelim //프로토타입 메서드 호출 나.안녕(); //안녕? 나는 yelim //정적 메서드 호출 닝겐.안녕해(); //안녕!
- 클래스와 생성자 함수의 정의 비교(그림25-1 420p, eb - 454p)
- ㄹㅇ 문법적 설탕.. 꿀빠는 그자체
클래스 호이스팅
- 클래스는 뭐다? 함수다
//클래스 선언문 class 닝겐 {} console.log(typeof 닝겐); //function
- 함수선언문과 같이 런타임이전 먼저평가
- 클래스가 평가되어 생성된 함수객체 -> constructor(생성자 함수로서 호출 ㄱㄴ), 프로토타입도 더불어 생성 -> 언제나 쌍으로 존재하기 때문
- 클래스는 정의 이전에 참조 x
console.log(닝겐); //ReferenceError //클래스 선언문 class 닝겐 {}
- 클래스는 호이스팅 발생 안하는것 처럼 보임! 하지만 아님 !
const 닝겐 = ""; { //호이스팅이 발생하지 않는다면 ''이 출력되어야 함 console.log(닝겐); //ReferenceError //클래스 선언문 class 닝겐 {} }
- 클래스는 let, const 키워드로 선언한 변수처럼 호이스팅이 됨
- var, let, const, function, function*, class 키워드를 사용하여 선언한 모든 식별자는 호이스팅이 됨 -> 모든 선언문은 런타임 이전에 먼저 실행되기 때문
인스턴스 생성
- 클래스는 생성자 함수 -> new 연산자와 함께 호출 -> 인스턴스 생성
- 반드시 new 연산자와 함께 ! 호출 !(인스턴스를 생성하는 유일한 존재이유)
class 닝겐 {} //인스턴스 생성(new o) const 나 = new 닝겐(); console.log(나); //닝겐 {} //인스턴스 생성(new x) const 나 = 닝겐(); console.log(나); //TypeError --------------- // 기명 클래스 표현식 이름 사용 const 나 = class 내클 {}; // 함수 표현식과 마찬가지로 인스턴스 생성해야함 const 나 = new 닝겐(); //클래스 이름 내클은 함수와 동일하게 클래스 몸체 내부에서만 유효함 console.log(내클);// ReferenceError const 너 = new 내클(); // ReferenceError
메서드
- constructor, 프로토타입, 정적 메서드 3가지
constructor
- 인스턴스를 생성 및 초기화 하기 위한 특수한 메서드, 이름변경 x
class 닝겐 { //생성자 constructor(이름) { //인스턴스 생성 및 초기화 this.이름 = 이름; } } //클래스는 인스턴스를 생성하기 위한 생성자 함수 console.log(typeof 닝겐); //function console.log(닝겐); //class 닝겐 { constructor(이름){this.이름 = 이름;}} // 1. 함수는 평가되어 함수 객체가 됨 // 2. 클래스도 함수 객체 고유의 프로퍼티를 갖고 있음 // 3. 자신의 스코프 체인을 구성 //인스턴스의 내부를 들여다보자 const 나 = new 닝겐("yelim"); console.log(나); //닝겐 {이름: 'yelim'} // constructor 내부의 인스턴스 프로퍼티 추가됨
- constructor은 메서드로 해석 x, 클래스로 평가되어 생성한 함수 객체 코드의 일부가 됨
클래스의 consrtructor 메서드와 프로토타입이 constructor 프로퍼티 -> 직접적인 관련 x, 프로토타입의 constructor 프로퍼티는 모든 프로토타입이 가지고 있는 프로퍼티며 생성자 함수를 가리킴
- 클래스의 consrtructor 메서드의 차이
- 클래스 내에 최대 한개만 존재
- 생략 가능(생략해도 암묵적으로 추가)
- 프로퍼티가 추가되어 초기화된 인스턴스 생성시 -> constructor 내부에서 this에 인스턴스 프로퍼티를 추가
- 인스턴스를 초기화 하려면 constructor을 생략하면 x
- 별도의 반환문(return)을 갖지 x why? new 연산자와 함께 클래스가 호출되면 생성자 함수와 동일하게 암묵적으로 this, 인스턴스를 반환하기 때문
프로토타입 메서드
- 프로토타입 메서드 생성하기 위해 -> 명시
- 굳이 prototype 프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 됨
- 클래스가 생성한 인스턴스는 프로토타입 체인의 일원이 된다.
- 그림(25-4)
- 프로토타입 체인은 기존 객체생성방식(객체리터럴, 생성자 함수, Object.creat 메서드 등)뿐만 아니라 인스턴스에도 동일하게 적용
정적 메서드
- 인스턴스 생성하지 않아도 호출 o
- 생성자 함수에서는 메서드 추가해야함
- 클래스에서는 static 키워드를 붙이면 정적 메서드가 됨
- 인스턴스로 호출할수 x, 인스턴스의 프로토타입 체인 상에는 클래스가 존재 x -> 클래스의 메서드를 상속받지 x
정적 메서드와 프로토타입 메서드의 차이
- 정적 메서드와 프로토타입 메서드는 자신이 속해 있는 프로토타입 체인이 다름
- 정적 메서드는 클래스로 호출, 프로토타입 메서드는 인스턴스로 호출
- 정적 메서드는 인스턴스 프로퍼티 참조 x, 프로토타입 메서드는 참조 o
- 둘이 this 바인딩이 다름
- 전역에 사용할 유틸리티 함수를 전역함수로 정의 x 메서드로 구조화할때 유용
클래스에서 정의한 메서드의 특징
- function 키워드를 생략한 메서드 축약표현을 사용함
- 객체리터럴과는 다르게 클래스에 메서드를 정의할때는 콤마가 필요 x
- 암묵적으로 stric mode로 실행됨
- for...in 문이나 Object.keys 메서드 등으로 열거할 수 없음
- 내부메서드 [[Construct]]를 갖지 x, new 연산자와 함께 호출 x
클래스와 인스턴스 생성과정
- 인스턴스 생성과 this 바인딩
- 인스턴스 초기화
- 인스턴스 반환
class 닝겐 { //생성자 constructor(이름) { //1. 암묵적으로 인스턴스가 생성되고 this에 바인딩됨 console.log(this); // 닝겐{} console.log(Object.getPrototypeOf(this) === 닝겐.prototype); //true //2. this에 바인딩되어 있는 인스턴스를 초기화함 this.이름 = 이름; //3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환 } }
프로퍼티
인스턴스 프로퍼티
- 인스턴스 프로퍼티는 constructor 내부에서 정의
class 닝겐 { constructor(이름) { //인스턴스 프로퍼티 this.이름 = 이름; } } const 나 = new 닝겐("yelim"); console.log(나); //닝겐 {이름 : 'yelim'}
- constructor 내부코드 실행이전 빈 객체가 바인딩이 되어있음
- 인스턴스에 프로퍼티가 추가되어 인스턴스 초기화
- 인스턴스 프로퍼티는 언제나 public 하다.
접근자 프로퍼티
- 접근자 프로퍼티 : 자체적으로 값([[Value]]내부슬롯)을 갖지않고 다른 데이터 프로퍼티 값을 읽거나 저장할때 사용하는 접근자 함수로 구성된 프로퍼티
class 닝겐 { constructor(이름, 성) { this.이름 = 이름; this.성 = 성; } //풀네임은 접근자 함수로 구성된 접근자 프로퍼티 //getter 함수 get 풀네임() { return `${this.이름} ${this.성}`; } //setter 함수 set 풀네임(이름) { [this.이름, this.성] = 이름.split(" "); } } const 나 = new 닝겐("예림", "홍"); //데이터 프로퍼티를 프로퍼티 값의 통한 참조 console.log(`${나.이름} ${나.성}`); //예림 홍 //접근자 프로퍼티를 통한 프로퍼티 값의 저장 //접근자 프로퍼티 풀네임에 값을 저장하면 setter 함수가 호출 나.풀네임 = "예림 홍"; console.log(나); // 닝겐 {이름: '예림', 성: '홍'} //데이터 프로퍼티를 프로퍼티 값의 통한 참조 //getter 함수 호출 console.log(나.풀네임); //예림 홍 //풀네임은 접근자 프로퍼티임 console.log(Object.getOwnPropertyDescriptor(닝겐.prototype, "풀네임")); //{enumerable: false, configurable: true, get: ƒ, set: ƒ}
- 접근자 프로퍼티 -> getter 함수, setter 함수
- getter : 인스턴스 프로퍼티에 접근할 때마다(참조하는 형식)
- setter : 인스턴스 프로퍼티에 값을 할당할때(할당하는 형식)-> 반드시 매개변수(단하나)
클래스 필드 정의 제안
- 클래스 필드 : 클래스기반 객체지향 언어에서 '클래스가 생성할 인스턴스의 프로퍼티'를 가리키는 용어
- 클래스 기반 객체지향 언어의 this는 언제나 클래스가 생성할 인스턴스를 가리킨다.
- this는 클래스 필드가 생성자 또는 메서드의 매개변수 이름과 동일할때 클래스 필드임을 명확히 하기 위해 사용
- 자스 클래스 몸체에는 메서드만 선언 ㄱㄴ
class 닝겐 { //클래스 필드 정의 이름 = "예림"; } const 나 = new 닝겐(); console.log(나); //닝겐 {이름: '예림'}
- '클래스 필드 정의 제안' 은 아직 ECMAScript의 표준 사양으로 승급되지 않음 하지만 최신 브라우저는 선제적으로 구현해놓음
- this 클래스 필드를 바인딩 해서는 안됨 -> constructor와 메서드 내에서만 유효
- 자스에서는 this 반드시 사용
- 인스턴스 생성시 클래스 필드를 초기화해야할 필요가 있다면 constructor에서 클래스 필드를 초기화 해야함 -> 안하면 undefined 뜸
- 함수는 일급객체 -> 클래스 필드에 할당 ㄱㄴ -> 프로토타입 메서드가아닌 인스턴스 메서드가 된다 -> 권장하지 x
private 필드 정의 제안
- js는 캡슐화를 완전하게 지원 x
- 언제나 public ~
class 닝겐 { 이름 = "예림"; // 인스턴스 프로퍼티는 기본적으로 public 함 } const 나 = new 닝겐("예림"); console.log(나.이름); //예림
- private 선두에는 #을 붙여준다. private 필드를 참조할 때로 #을 붙여주어야함
class 닝겐 { //private 필드 정의 #이름 = ""; constructor(이름) { //private 필드 참조 this.#이름 = 이름; } } const 나 = new 닝겐("홍"); //private 필드 #이름은 클래스 외부에서 참조할 수 없다. console.log(나.#이름); //SyntaxError
ts : 자스의 상위 확장인 타입스크립트는 클래스 기반 객체지향언어가 지원하는 접근 제한자인 public, private, protected를 모두 지원, 의미같음
- private 필드는 클래스 내부에서만 참조 ㄱㄴ
- 접근자 프로퍼티로 간접 접근 ㄱㄴ
stasic 필드 정의 제안
- static 키워드를 사용하여 정적메서드 정의 ㄱㄴ, 정적필드는 정의 x
클래스 상속과 생성자 함수 상속
- 기존 클래스를 상속받아 새로운 클래스를 확장하여 정의 하는것
- 코드 재사용 관점에서 매우 유용
- 다른 클래스를 확장할 수 있는 문법인 extends 키워드가 기본적으로 제공(간편, 직관)
extends 키워드
- 상속을 통해 클래스를 확장하려면 extends 키워드를 사용하여 상속받을 클래스를 정의
// 수퍼(베이스/부모) 클래스 class Base {} //서브(파생/자식)클래스 class Derived extends Base {}
- 서브클래스 : 상속을 통해 확장된 클래스
- 수퍼클래스 : 서브클래스에게 상속된 클래스
- 프로토타입 메서드, 정적 메서드 모두 상속이 ㄱㄴ
동적 상속
- extends 키워드는 클래스뿐만 아니라 생성자 함수를 상속받아 클래스를 확장 -> 키워드 앞에는 반드시 클래스가 와야함
//생성자 함수 function 기본(a) { this.a = a; } //생성자 함수를 상속받는 서브클래스 class 내놔 extends 기본 {} const 내놔라 = new 내놔(1); console.log(내놔라); //내놔 {a: 1}
서브클래스의 constructor
- 암묵적 정의 되는 constructor
constructor(...args) { super(...args); } //super()는 수퍼클래스의 constructor을 호출하여 인스턴스를 생성
super 키워드
- super 키워드 : 함수처럼 호출할 수도 있고, this같이 식별자처럼 참조할 수 있는 특수한 키워드
- super을 호출하면 constructor를 호출한다.
- super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.
- super 호출
- super를 호출하면 수퍼클래스의 constructor를 호출
//수퍼클래스 class 기본 { constructor(a, b) { //(4) this.a = a; this.b = b; } } //서브클래스 class 내놔 extends 기본 { constructor(a, b, c) { //(2) super(a, b); //(3) this.c = c; } } const 내놔라 = new 내놔(1, 2, 3); //(1) console.log(내놔라); //내놔 {a: 1, b: 2, c: 3}
-
new 연산자와 함께 내놔 클래스를 호출(1)하면 전달한 인수 (1, 2, 3)은 내놔클래스의 constructor(2)에 전달되고 super 호출(3)을 통해 기본 클래스의 constructor(4)에 일부가 전달된다.
-
super 호출시 주의사항 ! ! 01. 서브클래스에서 constructor을 생략하지 않는 경우 서브클래스의 constructor에서는 반드시 super을 호출해야함 02. 서브클래스의 constructor에서 super을 호출하기 전에는 this 참조할수 x 03. super는 반드시 서브클래스 constructor에서만 호출함 -> 아닌곳은 에러발생
- super 참조
- 메서드 내에서 super을 참조하면 수퍼클래스의 메서드를 호출할 수 있다. 01. 서브클래스의 프로토타입 메서드 => 수퍼클래스의 프로토타입 메서드 (식별자 같을경우) 02. 서브클래스의 정적 메서드 => 수퍼클래스 정적 메서드 (식별자 같을경우)
상속클래스의 인스턴스 생성과정
- 어떻게 협력하여 인스턴스를 생성하는가?
-
서브클래스의 super 호출
- 서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임함 -> 서브클래스의 constructor에서 반드시 super을 호출해야하는 이유
-
수퍼클래스의 인스턴스 생성과 this 바인딩
- 수퍼클래스의 constructor 내부의 this는 생성된 인스턴스를 가리킴
- 인스턴스는 new.target이 가리키는 서브클래스가 생성한 것으로 처리됨
-
수퍼클래스의 인스턴스 초기화
- this에 바인딩 되어있는 인스턴스에 프로퍼티 추가, constructor가 인수로 전달받은 초기값으로 인스턴스의 프로퍼티를 초기화함
-
서브클래스 constructor로의 복귀와 this 바인딩
- 서브클래스는 별도의 인스턴스를 생성하지않고 super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용한다.
- super가 호출되지 않으면 인스턴스가 생성 x, this 바인딩도 할수 x
-
서브클래스의 인스턴스 초기화
-
인스턴스 반환