자바스크립트: 프로토타입 기반 객체 지향 언어(클래스가 필요 없음)
- ES5
생성자 함수와 프로토타입을 통해 상속 구현 가능
var Person = (function () { //생성자 함수 function Person(name) { this.name = name; } //프로토타입 메서드 Person.prototype.sayHi = function () { console.log(`Hi I'm ${this.name}`); }; //생성자 함수 반환 return Person }()) //인스턴스 생성 var me = new Person('Shin'); me.sayHi(); //Hi I'm Shin var me2 = new Person('Nara'); me2.sayHi(); //Hi I'm Nara
- ES6
- 기존의 프로토타입 기반 객체지향 모델을 폐지하고 새롭게 클래스 기반 객체 지향 모델을 제공하는 것이 아니다
- 클래스도 함수이며 기존 프로토타입 기반 패턴의 문법적 설탕(Syntatic sugar)
- 클래스와 생성자 함수가 정확히 동일하게 동작하지는 않는다. 생성자 함수보다 엄격하며, 생성자 함수에서는 제공하지 않는 기능 제공
클래스 도입
- 클래스와 생성자 함수
클래스와 생성자 함수는 모두 프로토타입 기반의 인스턴스를 생성한다
ㅤ | 클래스 | 생성자 함수 |
new 연산자X | 에러 발생 | 일반함수로서 호출됨 |
extends, super | O | X |
호이스팅 | X | O |
strict mode | O | X |
열거 | X | O |
클래스는 새로운 객체 생성 매커니즘
클래스 정의
- 클래스 선언문
// 파스칼 케이스를 사용하자 class Person {}
- 표현식
// 익명 클래스 표현식 const Person = class {} // 기명 클래스 표현식 const Person = class MyClass {}
- 클래스는 함수다
- 무명의 리터럴로 생성할 수 있다. 런타임에 생성 가능
- 변수나 자료구조(객체 배열)에 저장할 수 있다
- 함수의 매개변수에게 전달할 수 있다
- 함수의 반환값으로 사용할 수 있다
함수 = 일급객체
//클래스 선언문 class Person { constructor(name){ //인스턴스 생성 및 초기화 this.name = name; } // 프로토타입 메서드 sayHi() { console.log(`Hi I'm ${this.name}`); } // 정적 메서드 static sayHello() { console.log('Hello'); } } //인스턴스 생성 const me = new Person('Shin'); // 인스턴스의 프로퍼티 참조 console.log(me.name); //Shin //프로토타입 메서드 호출 me.sayHi(); //Hi! I'm Shin Person.sayHello(); // Hello!
- 클래스 몸체에서 정의할 수 있는 메서드
- constructor(생성자)
- 프로토타입 메서드
- 정적 메서드
클래스 호이스팅
클래스는 함수로 평가된다
클래스 선언문으로 정의한 클래스는 함수 선언문과 같이 소스코드 평가 과정(런타임 이전)에 먼저 평가 되어 함수 객체 생성
함수 객체: 생성자 함수로서 호출할 수 있는 constructor, 프로토타입도 이 시점에 더불어 생성된다
- 클래스는 클래스 정의 이전에 참조할 수 없다
console.log(Person);// Uncaught ReferenceError: Person is not defined class Person {}
호이스팅이 발생하지 않는 것처럼 보인다
⇒ let, const키워드로 선언한 변수처럼 호이스팅된다. 클래스 선언문 이전에 일시적 사각지대에 빠지기 때문에 호이스팅이 발생하지 않는 것처럼 동작
인스턴스 생성
클래스는 생성자 함수이며 new연산자와 함께 호출되어 인스턴스를 생성한다
class Person {} const me = new Person(); console.log(me); //Person {}
클래스는 인스턴스를 생성하는 것이 유일한 존재 이유 → 반드시 new 연산자와 함께 호출
메서드
클래스 몸체에 0개 이상의 메서드만 선언 가능
- constructor
인스턴스를 생성하고 초기화 하기 위한 특수한 메서드
이름 변경X
class Person { // 생성자 constructor(name){ //인스턴스 생성 및 초기화 this.name = name; } }

클래스는 평가 되어 함수 객체가 된다
→ 함수 객체의 고유한 프로퍼티를 모두 갖고 있다
→ 함수와 동일하게 프로토타입과 연결되어 있다
→ prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor 프로퍼티는 클래스 자신을 가리키고 있다.
⇒ 클래스는 인스턴스를 생성하는 생성자 함수

constructor 내부에서 this에 추가한 프로퍼티는 인스턴스 프로퍼티가 된다
⇒ constructor 내부의 this는 클래스가 생성한 인스턴스를 가르킨다
constructor는 메서드로 해석되는 것이 아니라 클래스가 평가되어 생성한 함수 객체 코드의 일부가 된다.
⇒ 클래스 정의가 평가되면 constructor의 기술된 동작을 하는 함수 객체가 생성된다
class Person { // 생성자 constructor(name, address){ //인수로 인스턴스 초기화 this.name = name; this.address = address; } } //인수로 초기값 전달. 초기값은 constructor에 전달됨 const me = new Person('Shin', 'Daejeon'); console.log(me); //Person {name: 'Shin', address: 'Daejeon'}
- 프로토타입 메서드
클래스 몸체에서 정의한 메서드는 클래스의 prototype 프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 된다
//클래스 선언문 class Person { constructor(name){ //인스턴스 생성 및 초기화 this.name = name; } // 프로토타입 메서드 sayHi() { console.log(`Hi I'm ${this.name}`); } } //인스턴스 생성 const me = new Person('Shin'); me.sayHi(); //Hi! I'm Shin
클래스가 생성한 인스턴스는 프로토타입 체인의 일원이 된다


- 정적 메서드
인스턴스를 생성하지 않아도 호출할 수 있는 메서드
//생성자 함수 function Person(name) { this.name = name; } Person.sayHi = function () { console.log('Hi!'); }; Person.sayHi(); //Hi!
//클래스 선언문 class Person { constructor(name){ //인스턴스 생성 및 초기화 this.name = name; } static sayHi() { console.log('Hi!'); } } Person.sayHi(); //Hi!

정적메서드는 클래스에 바인딩된 메서드이다
인스턴스로 호출할 수 없다 → 인스턴스의 프로토타입 체인상에 존재하지 않기 때문에 상속 받을 수 없다
- 정적 메서드와 프로토타입 메서드의 차이
- 정적메서드와 프로토타입 메서드는 자신이 속해있는 프로토타입 체인이 다르다
- 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다
- 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다
→ 메서드 내부에서 인스턴스 프로퍼티를 참조할 필요가 있다면 프로토타입 메서드로 정의, 참조해야 할 필요가 없다면 정적메서드로 정의
- 클래스에서 정의한 메서드의 특징
- function 키워드를 생략한 메서드 축약 표현 사용
- 객체 리터럴과 다르게 클래스에서 메서드를 정의할 때는 콤마가 필요없다
- 암묵적으로 strict mode로 실행된다
- for…in 문이나 Object.keys 메서드 등으로 열거할 수 없다
- [[Constructor]]를 갖지 않는 non-constructor → new 연산자와 함께 호출할 수 없다
클래스의 인스턴스 생성 과정
생성자 함수의 인스턴스 생성 과정과 유사하다
class Person { //생성자 constructor(name) { // 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩 된다 console.log(this); // Person {} console.log(Object.getPrototypeOf(this) === Person.prototype); //true // 2. this에 바인딩 되어있는 인스턴스를 초기화 한다 this.name = name; // 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다 } }
프로퍼티
- 인스턴스 프로퍼티
constructor 내부에서 정의
인스턴스에 프로퍼티가 추가되어 인스턴스가 초기화 된다
인스턴스 프로퍼티는 언제나 public하다
//클래스 선언문 class Person { constructor(name){ //인스턴스 프로퍼티 this.name = name; // name 프로퍼티는 언제나 public } } //인스턴스 생성 const me = new Person('Shin'); console.log(me.name); //Shin
- 접근자 프로퍼티
자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티
//클래스 선언문 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 //접근자 프로퍼티를 통한 프로퍼티 값의 저장 //setter 함수가 호출된다 me.fullName = 'Nara Shin'; //getter 함수가 호출된다 console.log(me.fullName); // Nara Shin

인스턴스 프로퍼티처럼 사용 → 프로퍼티를 참조하는 형식으로 사용
클래스의 메서드는 기본적으로 프로토타입의 메서드가 된다 → 접근자 프로퍼티 또한 프로토타입의 프로퍼티가 된다

- 클래스 필드
클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어
class Person { name = 'Lee'; } const me = new Person('Lee');
자바스크립트의 클래스 body에는 메서드만 선언 가능 → 클래스 필드를 선언하면 Syntax Error
위 코드는 최신 브라우저에서 정상 동작
⇒ 자바스크립트에서도 인스턴스 프로퍼티를 마치 클래스 기반 객체지향 언어의 클래스 필드처럼 정의할 수 있는 표준 사양인 “Class field declarations”가 TC39프로세스의 stage3에 제안되어 있다

클래스 몸체에서 클래스 필드를 정의하는 경우 this에 클래스 필드 바인딩X → this는 클래스의 constructor와 메서드 내에서만 유효
class Person { // 초기값을 할당하지 않으면 undefined를 갖는다 name; constructor(name) { //클래스 필드 초기화 //클래스 필드 참조시 this사용 this.name = name; } } const me = new Person('Lee'); console.log(me) //Person {name: "Lee"}
클래스 필드에 함수할당 → 모든 클래스 필드는 인스턴스 프로퍼티가 되기 때문에 프로토타입의 메서드가 된지 않으므로 권장하지 않는다
인스턴스 프로퍼티는 인스턴스를 통해 클래스 외부에서 언제나 참조 가능 → 언제나 public
클래스필드도 기본적으로 public
class Person { //private 필드 정의 // 클래스 몸체 내부에서 정의해야 한다 #name = ''; constructor(name) { // private 필드 참조 this.#name = name; } } const me = new Person('Lee');

private 필드 #name은 클래스 외부에서 참조할 수 없다 → 클래스 내부에서만 참조 가능
접근자 프로퍼티를 통해 간접적으로 호출 가능
class Person { //private 필드 정의 #name = ''; constructor(name) { // private 필드 참조 this.#name = name; } get name() { return this.#name.trim(); } } const me = new Person(' Lee '); console.log(me.name)
class MyMath { //static public 필드 정의 static PI = 22 / 7; //static private 필드 정의 static #num = 10; //static 메서드 static increment() { return ++MyMath.#num; } } console.log(MyMath.PI); console.log(MyMath.increment());
상속에 의한 클래스 확장
- 프로토타입 기반 상속: 프로토타입 체인을 통해 다른 객체의 자산을 상속받는 것
- 클래스: 기존 클래스를 상속받아 새로운 클래스를 확장(extends)하여 정의
// 수퍼(베이스/부모)클래스 class Animal { constructor(age, weight) { this.age = age; this.weight = weigth; } 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, wieight: 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
- extends 키워드
상속을 통해 클래스를 확장하기 위해 extends 키워드를 사용하여 상속받은 클래스를 정의
→ 수퍼클래스와 서브클래스 간의 상속 관계 설정, 클래스도 프로토타입을 통해 상속관계 구현
// 수퍼(베이스/부모)클래스 class Base {} // 서브(파생/자식)클래스 class Derived extends Base {}

- 동적 상속
extends 키워드는 생성자 함수를 상속받아 클래스를 확장할 수도 있다(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
- 서브클래스의 constructor
서브클래스에서 constructor 생략시 다음과 같은 constructor가 암묵적으로 정의된다
constructor(...args) { super(...args); }
super()는 수퍼클래스의 constructor를 호출하여 인스턴스 생성
class Base { constructor() {} } class Derived extends Base { //constructor(...args) { super(...args); } } const derived = new Derived(); console.log(derived); //Derived {}
- super 키워드
- super 호출
함수처럼 호출할 수도 있고, this와 같이 식별자처럼 참조할 수 잇는 특수한 키워드
super를 호출하면 수퍼클래스의 constructor(super-constructor)를 호출한다
//수퍼클래스 class Base { constructor(a, b) { this.a = a; this.b = b; } } //서브클래스 class Derived extends Base { //암묵적으로 constructor가 정의된다 //constructor(...args) { super(...args); } } const derived = new Dervied(1, 2); console.log(derived); //Derived {a: 1, b: 2}
//수퍼클래스 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 Dervied(1, 2, 3); console.log(derived); //Derived {a: 1, b: 2, c: 3}
- 서브클래스에서 constructor를 생략하지 않을 경우 서브클래스의 constructor에는 반드시 super를 호출해야한다.
- 서브클래스의 constructor에서 super를 호출하기 전에는 this를 참조할 수 없다
- super는 반드시 서브클래스의 constructor에서만 호출한다.
메서드 내에서 super를 참조하면 수퍼클래스의 메서드를 호출할 수 있다.
//수퍼클래스 class Base { constructor(name) { this.name = name; } sayHi() { return `Hi ${this.name}`; } } //서브클래스 class Derived extends Base { // super.sayHi는 수퍼클래스의 프로토타입 메서드를 가리킨다 sayHi() { return `${super.sayHi()}. Hello~~` } } const derived = new Dervied(1, 2, 3); console.log(derived); //Derived {a: 1, b: 2, c: 3}
// 수퍼 클래스 class Circle { constructor(radius) { this.radius = radius; // 반지름 } // 원의 지름 getDiameter() { return 2 * this.radius; } // 원의 둘레 getPerimeter() { return 2 * Math.PI * this.radius; } // 원의 넓이 getArea() { return Math.PI * Math.pow(this.radius, 2); } } //서브 클래스 class Cylinder extends Circle { constructor(radius, height) { // ① super 메소드는 부모 클래스의 constructor를 호출하면서 인수를 전달한다. super(radius); this.height = height; } // 원통의 넓이: 부모 클래스의 getArea 메소드를 오버라이딩하였다. getArea() { // (원통의 높이 * 원의 둘레) + (2 * 원의 넓이) // ② super 키워드는 부모 클래스(Base Class)에 대한 참조 return (this.height * super.getPerimeter()) + (2 * super.getArea()); } // 원통의 부피 getVolume() { // ② super 키워드는 부모 클래스(Base Class)에 대한 참조 return super.getArea() * this.height; } } // 반지름이 2, 높이가 10인 원통 const cylinder = new Cylinder(2, 10);
- 상속 클래스의 인스턴스 생성 과정
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'} console.log(colorRectangle.getArea()); //8 console.log(colorRectangle.toString()); //width = 2, height = 4, color= red

- 서브클래스의 super 호출
[[ConsructorKind]] - base, derived
서브클래스는 자신이 직접 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임 → super클래스를 호출해야 하는 이유
- 수퍼클래스의 인스턴스 생성과 this 바인딩
수퍼클래스의 constructor 내부에의 코드가 실행되기 이전에 암묵적으로 빈객체 생성 → this에 바인딩
this는 생성된 인스턴스를 가리킴
인스턴스는 new.target이 가리키는 서브클래스가 생성한 것으로 처리된다
- 수퍼클래스의 인스턴스 초기화
- 서브클래스 constructor로의 복귀와 this 바인딩
super가 반환한 인스턴스가 this에 바인딩된다.
서브클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴를 this에 바인딩하여 그대로 사용
⇒ super가 호출되지 않으면 인스턴스가 생성되지 않으며, this 바인딩도 할 수 없다
- 서브클래스의 인스턴스 초기화
- 인스턴스 반환
- 표준 빌트인 생성자 함수 확장
표준 빌트인 객체도 [[Constructor]] 내부 메서드를 갖는 생성자 함수 → extends 키워드 사용 가능
class MyArray extends Array { 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] console.log(myArray.average()); //1.75