--> 자바스크립트는 프로토타입 기반 객체지향 언어이다.
--> 프로토타입 기반 객체지향 언어는 클래스가 필요 없다.
* ES6에 도입된 자바스크립트의 클래스는 사실 함수이며 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용할 수 있도록 하는 문법적 설탕이다.
클래스 vs 생성자 함수
1) new 연산자
클래스 : new 연산자 없이 호출하면 에러 발생
생성자 함수 : new 연산자 없이 호출하면 일반 함수로서 호출
2) 상속 키워드
클래스 : extends, super 키워드 제공
생성자 함수 : 없음
3) 호이스팅
클래스 : 호이스팅 없는 것처럼 동작
생성자 함수 : 함수 및 변수 호이스팅 발생
4) strict mode
클래스 : 암묵적으로 strict mode 지정
생성자 함수 : 지정 x
5) 열거
클래스 : 열거되지 않는다.
생성자 함수 : 열거된다.
--> 클래스는 새로운 객체 생성 메커니즘이다!!
클래스 선언문
class Person {}; //익명 클래스 표현식 const Person = class {}; //기명 클래스 표현식 const Person = class MyClass {};
--> 클래스는 표현식으로 정의 가능 ==> 클래스는 일급 객체이다.
클래스 몸체에서 정의할 수 있는 메서드
1) constructor (생성자)
2) 프로토타입 메서드
3) 정적 메서드// 클래스 선언문 class Person { // constructor 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(); // 정적 메서드 호출 Person.sayHello();
함수 정의 방식 : 클래스 vs 생성자 함수
1) 생성자
클래스 : constructor()
생성자 함수 : function 함수명()
2) 프로토타입 메서드
클래스 : 함수명()
생성자 함수 : 생성자 함수명.prototype.함수명
3) 정적 메서드
클래스 : static 함수명()
생성자 함수 : 생성자 함수명.함수명
--> 클래스는 함수로 평가 ==> 런타임 이전에 먼저 평가되어 함수 객체 생성
클래스는 클래스 정의 이전에 참조할 수 없다.
--> 클래스 선언문도 호이스팅이 발생하지만, let, const 키워드로 선언한 변수처럼 호이스팅된다. ==> 일시적 사각지대에 빠진다.
클래스 ==> 생성자 함수, new 연산자와 함께 호출되어 인스턴스 생성
class Person {}
// 인스턴스 생성
const me = new Person();
console.log(me); // Person {}
--> 클래스는 new 연산자와 함께 호출하지 않으면 에러가 발생
* 기명 함수 표현식과 마찬가지로 클래스 표현식에서 사용한 클래스 이름은 외부 코드에서 접근 불가능 ==> 식별자 이름으로 접근한다.
1. constructor (생성자)
--> 인스턴스를 생성하고 초기화하기 위한 특수한 메서드 -> 이름 변경 불가능
class Person { // 생성자 constructor(name) { // 인스턴스 생성 및 초기화 this.name = name; } }
--> constructor 내부의 this는 클래스가 생성한 인스턴스를 가리킨다.
생성자 함수 vs constructor
1) constructor는 클래스 내에 최대 1개만 존재 가능!!
--> 생략 시 암묵적으로 빈 constructor 정의
constructor에 매개변수를 선언하면 클래스 외부에서 인스턴스 프로퍼티의 초기값 전달이 가능하다.class Person { constructor(name, address) { // 인수로 인스턴스 초기화 this.name = name; this.address = address; } } // 인수로 초기값 전달 --> constructor로 const me = new Person('Lee', 'Seoul') // Lee, Seoul이 consturctor로 전달
--> constructor에는 return문을 생략한다. -> 의도치않은 반환 발생
2. 프로토타입 메서드
생성자 함수 : 명시적으로 prototype에 메서드 추가
클래스 : 내부에서 정의한 메서드는 기본적으로 프로토타입 메서드로 작동
클래스도 역시 프로토타입 체인을 생성한다!!
3. 정적 메서드
--> 인스턴스를 생성하지 않아도 호출할 수 있는 메서드
생성자 함수 : 생성자 함수명의 메서드로 호출
클래스 : static 키워드 사용
정적 메서드는 프로토타입이 아닌 클래스에 바인딩된 메서드이다!!
또한 정적 메서드는 인스턴스로 호출이 불가능하다!! --> 체인상에 존재 x
4. 정적 메서드 vs 프로토타입 메서드
1) 이 둘은 속해 있는 프로토타입 체인이 다르다.
2) 정적 메서드는 클래스로 호출, 프로토타입 메서드는 인스턴스로 호출한다.
3) 정적 메서드는 인스턴스 프로퍼티 참조 불가능, 프로토타입 메서드는 가능
1번. 정적 메서드 --> 인스턴스 프로퍼티 참조 x
class Square { static area(width, height) { return width * height; } } console.log(Square.area(10, 10)); // 100
2번. 프로토타입 메서드 --> 인스턴스 프로퍼티 참조
class Square { constructor(width, height) { this.width = width; this.height = height; } area() { return this.width * this.height; } } const square = new Square(10, 10); console.log(square.area()); // 100
5. 클래스에서 정의한 메서드의 특징
1) function 키워드 생략한 메서드 축약 표현 사용
2) 객체 리터럴과 다르게 콤마 사용 x
3) strict mode 암묵적 실행
4) 열거 불가능
5) 내부 메서드는 non-constructor --> new 연산자와 호출 불가능
1. 인스턴스 생성과 this 바인딩
클래스 호출 시 암묵적으로 빈 객체 생성 (클래스가 생성한 인스턴스) -> 빈 객체는 this에 바인딩
2. 인스턴스 초기화
constructor의 내부 코드가 실행 -> this에 바인딩되어 있는 인스턴스 초기화
3. 인스턴스 반환
바인딩된 this가 암묵적으로 반환
과정
class Person { // 생성자 constructor(name) { // 1. 암묵적으로 인스턴스 생성, this에 바인딩 console.log(this); // Person {} // 2. this에 바인딩되어 있는 인스턴스 초기화 this.name = name; // 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환 } }
1. 인스턴스 프로퍼티
--> 인스턴스 프로퍼티는 constructor 내부에서 정의
class Person { constructor(name) { // 인스턴스 프로퍼티 this.name = name; } } const me = new Person('Lee'); console.log(me); // Person {name: 'Lee'}
--> constructor 내부에서 this에 추가한 프로퍼티는 언제나 클래스가 생성한 인스턴스의 프로퍼티가 된다.
2. 접근자 프로퍼티
--> 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } // 접근자 프로퍼티 get fullName() { return `${this.firstName} ${this.lastName}`; } // 접근자 프로퍼티 setter 함수 set fullName(name) { [this.firstName, this.lastName] = name.split(' '); } } const me = new Person('Ungmo', 'Lee');
* getter, setter 이름은 인스턴스 프로퍼티처럼 사용된다. (참조)
1) getter는 취득할 때 사용하므로 반드시 무언가를 반환해야한다.
2) setter는 무언가를 할당할 때 사용하므로 반드시 매개변수가 필요 (1개만 가능)
--> 접근자 프로퍼티 또한 인스턴스 프로퍼티가 아닌 프로토타입 프로퍼티다.
3. 클래스 필드 정의 제안
클래스 필드 : 클래스가 생성할 인스턴스의 프로퍼티
* 자바스크립트의 클래스 몸체에서는 메서드만 선언이 가능하다.
자바스크립트에서도 클래스 기반 객체지향 언어와 같이 클래스 몸체 내부에서 인스턴스 프로퍼티를 선언할 수 있다. (단, this에 바인딩하면 안 된다.)
--> this는 constructor와 메서드 내부에서만 유효하다.
4. private 필드 정의 제안
자바스크립트는 캡슐화를 완전하게 지원하지 않는다.
--> 언제나 public 이다. (constructor, 클래스 필드 정의 제안 모두)
새롭게 private 기능 제안 --> #을 추가한다.
class Person { // private 필드 정의 #name = ''; constructor(name) { //private 필드 참조 this.#name = name; } }
public vs private
public : 어디서나 참조 가능
private : 클래스 내부에서만 참조 가능
--> 클래스 외부에서 private 필드에 간접적으로만 접근이 가능하다.
--> 접근자 프로퍼티 get으로 가능!!
private 필드는 constructor가 아닌 클래스 몸체에서 정의!!
5. static 필드 정의 제안
static public, static private 필드 정의 제안
class MyMath { // static public 필드 static PI = 22 / 7; // static private 필드 static #num = 10; // static 메서드 static increment() { return ++Mymath.#num; } }
1. 클래스 상속과 생성자 함수 상속
프로토타입 기반 상속과 상속에 의한 클래스 확장은 다르다.
--> 상속에 의한 클래스 확장은 기존 클래스를 상속받아 새로운 클래스를 확장
--> 상속받은 클래스로부터의 속성 + 확장 속성 (코드의 재사용)class Animal { constructor(age, weight) { this.age = age; this.weight = weight; } eat() { return 'eat'; } // 프로토타입 메서드 eat move() { return 'move'; } // 프로토타입 메서드 move } // 상속을 통해 Animal 클래스를 확장한 Bird 클래스 class Bird extends Animal { fly() { return 'fly'; } } const bird = new Bird(1,5);
--> extends 키워드로 상속 구현 가능
2. extends 키워드
// 수퍼 클래스 class Base {} // 서브 클래스 class Derived extends Base {}
--> 수퍼, 서브클래스는 인스턴스, 클래스 프로토타입 체인을 모두 생성한다.
--> 프로토타입, 정적 메서드 모두 상속이 가능하다.
3. 동적 상속
--> extends 키워드는 클래스뿐 아니라 생성자 함수를 상속받아 클래스 확장 가능
// 생성자 함수 function Base(a) { this.a = a; } // 생성자 함수를 상속받는 클래스 class Derived extends Base {} const derived = new Derived(1); console.log(derived); // Derived {a : 1}
--> extends 키워드는 함수 객체로 평가될 수 있는 모든 표현식을 상속받을 수 있다. -> 동적 상속 가능
4. 서브클래스의 constructor
--> 수퍼, 서브클래스 모두 constructor를 생략하면 빈 객체가 생성
--> 인스턴스 프로퍼티를 소유하기 위해서는 constructor 내부에 추가
5. super 키워드
--> 호출과 참조가 모두 가능한 특수 키워드
1) 호출 : 수퍼클래스의 constructor 호출
2) 참조 : 수퍼클래스의 메서드 호출1. 호출
수퍼클래스에 constructor를 생략하지 않고, 서브클래스에 constructor를 생략하면 암묵적으로 수퍼클래스의 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}
* super 호출 시 주의사항
1) 서브클래스에서 constructor 생략하지 않는 경우 반드시 super 호출
2) 서브클래스의 constructor에서 super 호출 전까지 this 참조 불가능
3) super는 서브클래스의 constructor에서만 호출가능
2. 참조
--> 수퍼클래스의 메서드 호출 가능
// 수퍼클래스 class Base { constructor(name) { this.name = name; } sayHi() { return `Hi! ${this.name}`; } } // 서브클래스 class Derived extends Base { sayHi() { return `${super.sayHi()}. how are you doing?`; } } const derived = new Derived('Lee'); console.log(derived.sayHi()); // Hi! Lee. how are you doing?
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}`; } }
1. 서브클래스의 super 호출
서브클래스는 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임 --> 서브클래스에서 반드시 super 호출이 필요
--> 서브클래스 constructor에 super가 없으면 인스턴스 생성이 불가능하므로 에러 발생
2. 수퍼클래스의 인스턴스 생성과 this 바인딩
수퍼클래스는 빈 객체 생성 -> 수퍼클래스 constructor 내부의 this는 생성된 인스턴스를 가리킨다.
!! 서브 클래스의 인스턴스는 서브클래스가 생성한 것으로 처리 !!
--> 생성된 인스턴스의 프로토타입은 서브클래스의 prototype 프로퍼티가 가리키는 객체
3. 수퍼클래스의 인스턴스 초기화
--> 수퍼클래스의 constructor 실행, this에 바인딩되어 있는 인스턴스 초기화
4. 서브클래스 constructor로의 복귀와 this 바인딩
--> 서브클래스는 별도의 인스턴스 생성 x, super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용
5. 서브클래스의 인스턴스 초기화
6. 인스턴스 반환
7. 표준 빌트인 생성자 함수 확장
--> extends 키워드는 클래스뿐 아니라 [[Construct]] 내부 메서드를 갖는 함수 객체로 평가될 수 있는 모든 표현식에 사용 가능