클래스는 보통
자바스크립트는
프로토타입 기반 객체지향 언어는 클래스가 필요 없다. 그래서 앞에서 우리는 클래스가 필요없는 생성자 함수와 프로토타입을 통해 객체 지향 언어의 상속을 배웠다.
var Person = (function (){
function Person(name){
this.name = name;
}
Person.prototype.sayHi = function (){
console.log('Hi! My name is ' + this.name);
};
return Person;
}());
var me = new Person("Lee");
me.sayHi(); // Hi my name is Lee
자바스크립트에 여러가지 이유로 클래스 개념이 도입되었지만, 클래스도
단, 클래스와 생성자 함수는
클래스는 생성자함수보다 엄격하다.
클래스를
클래스는 상속 지원하는
클래스는
클래스 내 모든 코드에는 strict mode 지정. strictmode 해제 불가. 그러나 생성자 모드는 strict mode X
클래스는 constructor, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 [[Enumerable]] 값이 false임. 열거되지 않는다.
class Person {}
클래스 몸체에는 0개 이상의 메서드만 정의할 수 있다. 정의할 수 있는 메서드는
class Person {
constructor(name){
this.name = name;
}
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();
p.420 생성자 함수와 클래스 비교 코드
클래스도 함수이므로
클래스 선언문
프로토타입도 함께 생성된다.
console.log(Person); // Reference Error
class Person {}
클래스도 호이스팅이 일어날까 ? 위 코드에서 Reference 에러가 발생하는것으로 보아 호이스팅이 일어나지 않는 것처럼 보인다.
const Person = '';
{
console.log(Person); // 만약 호이스팅이 안일어난다면 ''이 출력되어야 한다. 그러나 ReferenceError
class Person {}
}
즉, 클래스도 호이스팅이 발생한다. 클래스는 let, const 로 선언한 변수처럼 호이스팅이 된다.
따라서, 클래스 선언문 이전에
모든 선언문은 런타임 이전에 먼저 실행
클래스는 인스턴스 생성 외에는 존재 이유가 없다. 따라서 꼭 new 연산자 붙여 호출한다.
class Person{}
const me = new Person();
console.log(me);
constructor
class Person {
constructor(name){
this.name = name; // constructor 내부의 this는 생성자 함수와 마찬가지로 클래스가 생성한 인스턴스를 가리킨다.
}
}
클래스도 함수이기 때문에 함수 객체 고유 프로퍼티 다 가지고 있다. 함수랑 동일하게 프로토타입과 연결되어 있으며 자신의 스코프 체인을 구성한다.
모든 함수가 가지고 있는 prototype 프로퍼티가 가리키는 프로토타입 객체의 constructor 프로퍼티는 클래스 자신을 가리키고 있다. 즉, 클래스는 인스턴스를 생성하는 생성자 함수이다.
constructor는 클래스 몸체에서 정의한 메서드이다. 그런데 클래스가 생성한 인스턴스에는 constructor 메서드가 안보인다.
constructor는 단순한 메서드가 아니라, 클래스가 평가되어 생성한 함수 객체 코드의 일부라고 생각하면 된다.
즉,
constructor는
constructor 는 return문 무조건 생략해야 한다. 원시값 return시에는 무시되지만,
다른 객체 return하면 this, 즉 생성한 인스턴스를 반환 하지 못하고
다른 객체를 return에 혼돈을 주기 때문이다.
클래스는 그냥 메서드 추가하면
class Person {
constructor(name){
this.name = name;
}
// 프로토타입 메서드
sayHi() {
console.log(`Hi my name is ${this.name});
}
}
const me = new Person('Lee');
me.sayHi();
클래스가 생성한 인스턴스는 프로토타입 체인의 일원이 된다.
클래스에서의 정적 메서드는 static 키워드 붙이면 된다.
class Person {
constructor(name){
this.name = name;
}
// 프로토타입 메서드
sayHi() {
console.log(`Hi my name is ${this.name});
}
static sayHello() {
console.log('hello');
}
}
const me = new Person('Lee');
me.sayHi();
정적 메서드는 Person 클래스에 바인딩된다.
클래스도 함수객체이므로 자신의 프로퍼티/메서드를 소유할 수 있다.
따라서 정적 메서드는 인스턴스 생성 안해도 클래스 정의 이후 호출 가능하다.
반대로 인스턴스는 정적 메서드 사용 못한다.
인스턴스의 프로토타입 체인 상에 정적메서드가 없기 때문이다.
프로토타입 메서드의 this는 생성한 인스턴스를 가리키고, 정적 메서드의 this는 클래스를 가리킨다.
즉, 인스턴스의 프로퍼티를 참조해야 한다면 프로토타입 메서드를 사용해야 한다.
class Person {
constructor(firstName, lastName) {
(this.firstName = firstName), (this.lastName = lastName);
}
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
set fullName(name) {
[this.firstName, this.lastName] = name.split(" ");
}
}
const me = new Person("Seonyeong", "Kim");
console.log(`${me.firstName} ${me.lastName}`); // Seonyeong Kim
me.fullName = "Seonyeong Yun";
console.log(me); // Person { firstName: 'Seonyeong', lastName: 'Yun' }
console.log(me.fullName); // Seonyeong Yun
Java랑 다르게 Javascript에서는 클래스 몸체에서는 메서드만 정의하고, 인스턴스 프로퍼티는 constructor 내부에서 this를 이용해 추가할 수 있었다.
최신 브라우저, Node.js 버전에서는 클래스 몸체에서 this없이 필드 정의하는 것이 가능하다.
class Person {
name = "lee"
}
const me = new Person();
console.log(me); // Person {name: "lee"}
그러나 클래스 필드를 참조하려면 this를 사용해야된다.
class Person {
name = "Lee"
constructor(){
console.log(name); // ReferenceError
}
}
new Person();
클래스 필드 초기화 안하면 undefined
class Person {
name;
constructor(name){
this.name = name;
}
}
const me = new Person('Lee');
constructor에서 초기화할 수 있긴 함. 근데 걍 굳이 constructor 가 정의,
초기화 다 해주기 때문에 밖에서 정의 해줄 필욘 없다.
private 필드는 최신 브라우저, 최신 Node.js에서만 지원한다.
‘#’ 을 붙여주면 된다.
class Person {
#name = '';
constructor(name) {
this.#name = name;
}
}
const me = new Person('Lee');
console.log(me.#name); // SyntaxError: Private field name must be declared in ~
private 필드는 클래스 내부에서만 참조할 수 있다.
private필드는 constructor에서 정의하면 안된다. 몸체에서 해줘야 함.
static 도 최신 버전에서만 지원
인스턴스 생성 안하고 참조 가능
상속에 의한 클래스 확장:
프로토타입 기반 상속:
class Animal {
constructor(age, weight){
this.age = age;
this.weight = weight
}
eat(){
return 'eat';
}
move(){
return 'move';
}
}
class Bird extends Animal {
fly(){
return 'fly'
}
}
const bird = new Bird(1,5); // 메서드들은 프로토타입 메서드임. 인스턴스 메서드 x
console.log(bird); // Bird {age: 1, weight: 5}
class
확장된 클래스를 subclass 자식클래스
상속된 클래스를 superclass 부모클래스
인스턴스 사이 프로토타입 체인과 클래스 간 프로토타입 체인 생성한다.
즉,
서브 클래스의 constructor 생략 시 암묵적으로 아래와 같은 constructor 가 정의됨
super()는
class Base{
constructor(){}
}
class Derived extends Base {
// 암묵적으로 정의
// constructor(...args) {super(...args);}
}
const derived = new Derived();
console.log(derived); // Derived {}
만약 수퍼 클래스의 프로퍼티를 그대로 갖는 인스턴스를 만든다면, 서브클래스에서 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}
Derived
super
인스턴스 초기화를 위해 전달한 인수는
주의 사항, super 사용 예제코드 중 몇 개 가져옴
super 사용해 수퍼클래스의 메서드 참조 가능
class Base{
constructor(name){
this.name = name;
}
sayHi(){
return `Hi my name is ${this.name}`;
}
}
class Derived extends Base {
sayHi(){
return `${super.sayHi()}. How are u doing?`;
}
}
const derived = new Derived('yun');
console.log(derived.sayHi()); // hi my name is yun. How are u doing?
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
그림을 보면 Rectangle 클래스의 프로토타입은 getArea, toString 을 프로퍼티로 가지고 있고,
ColorRectangle 클래스의 프로토타입은 오버라이딩한 toString 만 프로퍼티로 가지고 있다.
그리고 ColorRectangle의 인스턴스인 colorRectangle은 수퍼 클래스와 서브클래스의 constructor 모두를 거치는데, 수퍼 클래스의 this와 서브 클래스의 this 모두 colorRectangle이라는 인스턴스를 바인딩하므로
width, height, color 을 갖는다.
자바스크립트 엔진은
서브클래스가 super를 호출할 때 서브클래스는
수퍼 클래스의 constructor가 호출되면 빈 객체를 생성한다. 이는
그리고 이
new 연산자로 서브 클래스를 호출하였고, super를 통해 인스턴스는 수퍼클래스가 생성했다. 라고 생각하면 된다.
new 연산자로 호출한 것은 서브 클래스이기 때문에 생성된
수퍼클래스의 constructor 내부 코드가 실행되면, 바인딩된 인스턴스를
super가 종료되면, 다시
그 때,
그래서 서브 클래스는 별도의 인스턴스 생성
따라서, super 키워드를 호출하지 않으면 인스턴스가 생성되지 않아 this바인딩도 안된다.
즉 서브 클래스 내 constructor 내부의 인스턴스 초기화는 반드시
extends를 통해 확장할 때에는, 클래스 뿐 아니라 String, Number, Array같은 표준 빌트인 객체들도 [[Construct]] 내부 메서드를 갖는 생성자 함수이므로 확장이 가능하다.
class MyArray extends Array {
uniq() {
return this.filter((v, i, self) => self.indexOf(v) == i);
}
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.uniq()); // MyArray(3)[(1, 2, 3)]
console.log(myArray.average()); // 1.75
// 메서드 체이닝
console.log(myArray.filter(v => v % 2).uniq().average()); // 2
인스턴스 myArray는 Array.prototype 과 MyArray.prototype 의 모든 메서드를 사용할 수 있다.
이 때, Array.prototype 의 메서드 중, map, filter 와 같이 새로운 배열을 반환하는 메서드는 MyArray 클래스의 인스턴스를 반환한다. 그렇기 때문에 uniq, average 와 같은 MyArray의 메서드를 연이어 호출할 수 있다.