자바스크립트는 프로토타입 기반으로 객체지향 언어이고 강력한 객체 지향 프로그래밍 능력을 지니고 있다.
프로토타입 기반 객체지향 언어는 클래스가 필요없는 객체 지향 프로그래밍 언어다. ES5에서는 클래스 없이도 생성자 함수와 프로토타입을 통해 객체지향 언어의 상속을 구현 할 수있다.
ES6에서 도입된 클래스는 클래스 기반 객체지향 프로그래밍언어와 매우 흡사한 새로운 객체 생성 메커니즘이다. 사실 클래스는 함수이며 기존 프로토타입 기반 패턴을 클래스 기반 패턴처럼 사용 할 수 있도록 하는 문법적 설탕이다.
단, 클래스와 생성자 함수 모두 프로토타입 기반의 인스턴스를 생성하지만 정확히 동일하게 동작하지는 않고 클래스는 생성자 함수보다 엄격하며 생성자 함수에서는 제공하지 않는 기능도 제공한다.
new
연산자 없이 호출하면 에러가 발생한다. 하지만 생성자 함수를 new
연산자 없이 호출하면 일반 함수로서 호출된다.extends
와 super
키워드를 제공한다.strict mode
가 지정되어 실행되며 strict mode
를 해제할 수 없다. 생성자 함수는 암묵적으로 지정되지 않는다.contructor
, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumerable]]
의 값이 false
다. 다시 말해, 열거 되지 않는다. 위 동작들의 차이를 보면 클래스는 프로토타입 기반 객체 생성 패턴의 단순함 문법적 설탕이라기 보다는 새로운 객체 생성 메커니즘이라 생각할 수 있다.
class
키워드를 사용하여 정의한다.일급 객체
다.contructor
, 프로토타입 메서드
, 정적 메서드
이렇게 3가지 메서드가 있다.class Person {}
console.log(typeof Person); // function
constructor
( 생성자와 프로토타입은 쌍으로 존재하기 때문에 이때 프로토 타입도 생성됨)let, const
처럼 TDZ
개념이 존재하기 때문이다)new
연산자와 함께 호출되어 인스턴스 생성class Person {}
// 인스턴스 생성
const me = new Person ();
console.log(me); // Person {}
new
연산자 없이 호출하면 타입 에러 발생한다.const Person = class MyClass {};
// 함수 표현식과 마찬가지로 클래스를 가리키는 식별자로 인스턴스를 생성해야 한다.
const me = new Person();
// 클래스 이름 MyClass는 함수와 동일하게 클래스 몸체 내부에서만 유효한 식별자다.
console.log(MyClass); // ReferenceError: MyClass is not defined
const you = new MyClass(); // ReferenceError: MyClass is not defined
클래스 몸체에는 0개 이상의 메서드만 선언할 수 있다. 몸체 정의할 수 있는 메서드의 종류는 contructor
, 프로토타입 메서드
, 정적 메서드
의 세 가지가 있다.
contructor
는 인스턴스를 생성하고 초기화하기 위한 특수한 메서드다. contructor
는 이름을 변경할 수 없다.contructor
는 메서드로 해석되는 것이 아니라 클래스가 평가되어 생성한 함수 객체 코드의 일부가 된다. 즉, 클래스 정의가 평가되면 constructor
의 기술된 동작을 하는 함수 객체가 생성된다.constructor
메서드와 프로토타입의 constructor
프로퍼티는 직접 적인 관련 없다. 프로토타입의 constructor
프로퍼티는 모든 프로토타입이 가지고 있는 프러퍼티 이며, 생성자 함수를 가리킨다.class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
}
contructor
는 생성자 함수와 유사하지만 몇가지 차이가 있다.constructor
는 클래스 내에 최대 한 개만 존재 할 수 있다.constructor
를 생략하면 클래스에 빈 constructor
가 암묵적으로 정의된다.constructor
내부에서 this
에 인스턴스 프로퍼티를 추가한다.constructor
내부에서 반환return
을 반드시 생략한다.prototype
프로퍼티에 메서드를 추가하지 않아도 기본적으로 프로토타입 메서드가 된다.class Perosn {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
// 프로토타입 메서드
sayHi() {
console.log(`Hi ! My name is ${this.name}`);
}
}
const me = new Perosn('Lee')
me.sayHi(); // Hi ! My name is Lee
생성자 함수와 마찬가지로 클래스가 생성한 인스턴스는 프로토타입 체인의 일원이 된다.
// me 객체의 프로토타입은 Person.prototype이다.
Object.getPrototypeOf(me) === Person.prototype; // true
me instanceof Person; // true
// Person.prototype의 프로토타입은 Object.prototype이다.
Object.getProtototypeOf(Person.prototype) === Object.prototype; // true
me instanceof Object; // true
// me 객체의 contructor는 Person 클래스다.
me.constructor === Person; // true
클래스는 생성자 함수와 같이 인스턴스를 생성하는 생성자 함수라고 볼 수 있다. 즉, 클래스는 프로토타입 기반의 객체 생성 메커니즘이다.
static
키워드를 붙이면 정적 메서드(클래스 메서드)가 된다.class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
// 정적 메서드
static sayHi() {
console.log("Hi");
}
}
// 정적 메서드는 클래스로 호출된다.
// 정적 메서드는 인스턴스 없이 호출 가능 !
Person.sayHi() // Hi !
정적 메서드와 프로토타입 메서드의 차이
- 정적 메서드와 프로토타입 메서드는 자신이 속해 있는 프로토타입 체인이 다르다.
- 정적 메서드는 클래스로 호출하고 프로토타입 메서드는 인스턴스로 호출한다.
- 정적 메서드는 인스턴스 프로퍼티를 참조할 수 없지만 프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다.
this
바인딩 원리에 따라 정적 메서드는 클래스로 호출 하기 때문에 내부의 인스턴스 프로퍼티를 참조 할 수 없습니다.
클래스에서 정의한 메서드의 특징
1. function
키워드를 생략한 메서드 축약 표현을 사용한다.
2. 객체 리러털과는 다르게 클래스에 메서드를 정의할 때는 콤마가 필요없다.
3. 암묵적으로 strict mode
로 실행된다.
4. for...in
문이나 Object.key
메서드 등으로 열거할 수 없다. 즉, 프로퍼티의 열거 기능 여부를 나타내며, 불리언 값을 갖는 프로퍼티 어트리뷰트 [[Enumberable]]
의 값이 false
다.
5. 내부 메서드 [[Construct]]
를 갖지 않는 non-constructor
다. 따라서 new
연산자와 함께 호출할 수 없다.
new
연산자와 함께 클래스를 호출하면 constructor
의 내부 코드가 실행되기에 앞서 암묵적으로 빈 객체 생성된다. 이 빈 객체가 클래스가 생성한 인스턴스다.prototype
프로퍼티가 가리키는 객체가 설정된다.this
에 바인딩된다. 따라서 constructor
내부의 this
는 클래스가 생성한 인스턴스를 가리킨다.constructor
의 내부 코드가 실행 this
에 바인딩되어 있는 인스턴스를 초기화한다.this
에 바인딩되어 있는 인스턴스에 프로퍼티를 추가하고 constructor
가 인수로 전달받은 초기값으로 인스턴스의 프로퍼티 값을 초기화한다.this
가 암묵적으로 반환된다.class Person {
// 생성자
constructor(name) {
// 1. 암묵적으로 인스턴스가 생성되고 this에 바인딩된다.
console.log(this); // Perosn {}
console.log(Object.getPrototypeOf(this) === Person.prototype); // true
// 2. this에 바인딩되어 있는 인스턴스를 초기화한다.
this.name = name;
// 3. 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
}
}
constructor
가 생성자 함수의 역할을 수행한다. 그러므로 인스턴스 프로퍼티는 constructor
내부에 정의 해야한다.
ES6의 클래스의 인스턴스의 프로퍼티는 항상
public
이다.
private
프로퍼티를 정의할 수 있는 사양이 제안 중에 있다.
자체적으로 값을 갖지 않고, 다른 데이터의 값을 읽거나 저장할 때 자동으로 쓰여지는 접근자 함수이다. 접근자 프로퍼티 역시 프로토타입의 프로퍼티가 된다.
const person = {
firstName = "kyungBin",
lastName = "Park",
get fullName() {
return `${this.lastName} ${this.firstName}`
}
set fullName(name){
[this.lastName, this.firstName] = name.split(" ");
}
}
person.fullName // Park kyungBin
person.fullName = "Lee woongMo" // person.fullName에 들어감
// person.fullName() //x
// person.fullName // o
클래스 필드
는 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어이다. Java 에서 클래스 필드는 마치 클래스 내부에서 변수처럼 사용한다.
class Person {
// 값을 할당하지 않으면 undefined가 할당된다.
name = 'lee';
// this.name = ""; 이렇게 선언은 불가능하다. contructor와 메서드 내에서만 this 사용이 가능하다.
constructor(){
console.log(this.name) // 자바에서는 this를 떼도 호출되지만, js는 무조건 this를 붙여야한다.
}
}
const me = new Person();
me.name // lee
함수도 할당이 가능하다. JS 함수는 일급 객체이기 때문이다.
class Person{
name = "lee";
//클래스 필드에 함수를 할당했다. 메소드가 아니다.
getName = function() {
return this.name;
}
}
클래스 필드는 인스턴스의 프로퍼티이기 때문에 프로토타입 메서드가 되지 않기에 권장하지 않는다.
private
필드 또한 ES2019 을 통해 등장했다. private
필드는 접두사로 #
가 붙는다.
class Person {
#name = "";
constructor(name) {
this.#name = name // private 필드에 접근할 때도 # 를 붙인다.
}
}