25장. 클래스
1. 클래스의 정의
자바스크립트는 프로토타입기반
객체지향 언어이다
즉, 프로토타입 기반 객체지향 언어는 클래스
가 필요없는
객체지향 언어 상속을 구현할 수 있다
클래스의 특징이 몇가지가 있다.
-
new 연산자 없이 호출하면 에러가 발생하며, new 연산자 없이 호출하면 일반 함수로서 호출된다
-
클래스를 상속하기 위해서
extends
와super
키워드를 사용한다
단, 생성자 함수는 이를 지원하지 않는다 -
클래스는 호이스팅이 발생하지 않는 것처럼 동작한다
하지만,함수 선언문
으로 정의된 생성자 함수는함수 호이스팅
이
함수 표현식
으로 정의된 생성자 함수는변수 호이스팅
이 발생 -
클래스 내의 모든 코드에는 암묵적으로
strict mode
가 적용된다
생성자 함수는 그렇지 않다 -
클래스의
contructor
프로토타입 메서드
정적 메서드
모두[[Enumerable]]
값이false
이다
따라서 클래스는 새로운 객체 생성 매커니즘으로 보는 것이 좋다
클래스 키워드를 사용해 정의
//클래스 선언문 class Persoon { } // 익명 클래스 표현식 const Person = class {}; // 기명 클래스 표현식 const Person = class MyClass {};
클래스의 몸체에는 0개 이상의 메서드만 정의할 수 있다
- 생성자
- 프로토타입 메서드
- 정적 메서드
// 클래스 선언문
class Person {
// 생성자
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(); // Hi! My name is Lee
// 정적 메서드 호출
Person.sayHello(); // Hello!
2. 클래스 호이스팅
클래스는 함수로 평가된다
// 클래스 선언문
class Person {}
console.log(typeof Person); // function
클래스는 함수로 평가된다
// 클래스 선언문
class Person {}
console.log(typeof Person); // function
따라서 클래스 정의 이전에 참조 할 수 없다
console.log(Person);
// ReferenceError: Cannot access 'Person' before initialization
// 클래스 선언문
class Person {}
왜냐하면 클래스도 호이스팅이 발생하기 때문이다
const Person = '';
{
// 호이스팅이 발생하지 않는다면 ''이 출력되어야 한다.
console.log(Person);
// ReferenceError: Cannot access 'Person' before initialization
// 클래스 선언문
class Person {}
}
단, 클래스는 let, const
키워드로 선언한 변수처럼 호이스팅이 된다
따라서 클래스 선언문 이전에 일시적 사각지대
에 빠지게 되어 호이스팅이 발생하지 않는 것처럼 동작한다
모든 선언문은 런타임 이전에 실행된다
3. 인스턴스 생성
클래스는 생성자 함수
이며 new 연산자
와 함께 호출되어 인스턴스를 생성한다
class Person {}
// 인스턴스 생성
const me = new Person();
console.log(me); // Person {}
// 클래스를 new 연산자 없이 호출하면 타입 에러가 발생한다.
const other = Person();
// TypeError: Class constructor Foo cannot be invoked without 'new'
클래스는 생성자 함수
이며 new 연산자
와 함께 호출되어 인스턴스를 생성한다
class Person {}
// 인스턴스 생성
const me = new Person();
console.log(me); // Person {}
// 클래스를 new 연산자 없이 호출하면 타입 에러가 발생한다.
const other = Person();
// TypeError: Class constructor Foo cannot be invoked without '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
4. 메서드
클래스의 몸체에는 0개 이상의 메서드만 선언할 수 있다
📍 constructor 생성자
클래스의 몸체에는 0개 이상의 메서드만 선언할 수 있다
인스턴스를 생성하고 초기화하기 위한 특수한 메서드
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
}
// 인스턴스 생성
const me = new Person('Lee');
오로지 하나의 생성자만 정의할 수 있다.
class Person {
constructor() {
// 고정값으로 인스턴스 초기화
this.name = 'Lee';
this.address = 'Seoul';
}
}
// 인스턴스 프로퍼티가 추가된다.
const me = new Person();
console.log(me); // Person {name: "Lee", address: "Seoul"}
객체를 반환하는 것도 가능하긴 하다
class Person {
constructor(name) {
this.name = name;
// 명시적으로 객체를 반환하면 암묵적인 this 반환이 무시된다.
return {};
}
}
// constructor에서 명시적으로 반환한 빈 객체가 반환된다.
const me = new Person('Lee');
console.log(me); // {}
원신값을 반환하면 this가 반환된다
class Person {
constructor(name) {
this.name = name;
// 명시적으로 원시값을 반환하면 원시값 반환은 무시되고 암묵적으로 this가 반환된다.
return 100;
}
}
const me = new Person('Lee');
console.log(me); // Person { name: "Lee" }
즉, constructor 내부에서 명시적으로 this가 아닌 다른 값을 반환하는 것도 가능하긴 하지만
이는 클래스의 기본 동작을 훼손시키는 것이므로 contructor 내부에서 return문은 반드시 생략해야 한다
📍 프로토타입 메서드
생성자 함수에서 프로토타입 메서드를 추가하는 방법은 다음과 같다
// 생성자 함수
function Person(name) {
this.name = name;
}
// 프로토타입 메서드
Person.prototype.sayHi = function () {
console.log(`Hi! My name is ${this.name}`);
};
const me = new Person('Lee');
me.sayHi(); // Hi! My name is Lee
인스터스를 생성 및 초기화를 진행하면서 프로토타입 메서드를 정의할 수도 있다
class Person {
// 생성자
constructor(name) {
// 인스턴스 생성 및 초기화
this.name = name;
}
// 프로토타입 메서드
sayHi() {
console.log(`Hi! My name is ${this.name}`);
}
}
const me = new Person('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.getPrototypeOf(Person.prototype) === Object.prototype; // -> true
me instanceof Object; // -> true
// me 객체의 constructor는 Person 클래스다.
me.constructor === Person; // -> true
생성자 함수와 마찬가지고 클래스가 생성한 인스턴스는 프로토타입 체인의 일원이 된다
📍 정적 메서드
인스턴스를 생성하지 않아도 호출할 수 있는 메서드
// 생성자 함수
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!
// 인스턴스 생성
const me = new Person('Lee');
me.sayHi(); // TypeError: me.sayHi is not a function
정적 메서드가 존재하는 클래스는 인스턴스 프로토타입 체인상에 존재하지 않기에 인스턴스로 클래스의 메서드를 상속받을 수 없다
- 정적메서드와 프로토타입 메서드가 속한 프로토타입 체인이 다르다
- 정적메서드는 클래스로
프로토타입 메서드는 인스턴스로 호출 - 정적메서드는 인스턴스 프로퍼티를 참조할 수 없지만
프로토타입 메서드는 인스턴스 프로퍼티를 참조할 수 있다.
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
// 표준 빌트인 객체의 정적 메서드
Math.max(1, 2, 3); // -> 3
Number.isNaN(NaN); // -> true
JSON.stringify({ a: 1 }); // -> "{"a":1}"
Object.is({}, {}); // -> false
Reflect.has({ a: 1 }, 'a'); // -> true
5. 클래스의 인스턴스 생성 과정
- 인스턴스 생성과 this 바인딩
- 인스턴스 초기화
- 인스턴스 반환
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가 암묵적으로 반환된다.
}
}
6. 프로퍼티
📍 인스턴스 프로퍼티
- 인스턴스 생성과 this 바인딩
- 인스턴스 초기화
- 인스턴스 반환
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가 암묵적으로 반환된다.
}
}
📍 인스턴스 프로퍼티
contructor 내부에서 정의되어야 한다
class Person {
constructor(name) {
// 인스턴스 프로퍼티
this.name = name; // name 프로퍼티는 public하다.
}
}
const me = new Person('Lee');
// name은 public하다.
console.log(me.name); // Lee
📍 접근자 프로퍼티 get,set
getter
는 인스턴스 프로퍼티에 접근
할 때마다 프로퍼티의 값을 조작하거나 별도의 행위가 필요할 때 사용
setter
는 프로퍼티처럼 값을할당
하는 형식으로 사용
const person = {
// 데이터 프로퍼티
firstName: 'Ungmo',
lastName: 'Lee',
// fullName은 접근자 함수로 구성된 접근자 프로퍼티다.
// getter 함수
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set fullName(name) {
// 배열 디스트럭처링 할당: "36.1. 배열 디스트럭처링 할당" 참고
[this.firstName, this.lastName] = name.split(' ');
}
};
// 데이터 프로퍼티를 통한 프로퍼티 값의 참조.
console.log(`${person.firstName} ${person.lastName}`); // Ungmo Lee
// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
// 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출된다.
person.fullName = 'Heegun Lee';
console.log(person); // {firstName: "Heegun", lastName: "Lee"}
// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
// 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다.
console.log(person.fullName); // Heegun Lee
// fullName은 접근자 프로퍼티다.
// 접근자 프로퍼티는 get, set, enumerable, configurable 프로퍼티 어트리뷰트를 갖는다.
console.log(Object.getOwnPropertyDescriptor(person, 'fullName'));
// {get: ƒ, set: ƒ, enumerable: true, configurable: true}
📍 클래스 필드 정의 제안
클래스 필드
는 클래스 기반 객체지향 언어에서 클래스가 생성할 인스턴스의 프로퍼티를 가리키는 용어
자바스크립트의 클래스 몸체에는 메서드
만 정의할 수 있다.
클래스 몸체에 클래스 필드를 선언하면 SyntaxError
가 발생한다
class Person1 {
// 클래스 필드 정의
name = 'Lee';
}
const me = new Person1();
console.log(me); // Person {name: "Lee"}
class Person2 {
// this에 클래스 필드를 바인딩해서는 안된다.
this.name = ''; // SyntaxError: Unexpected token '.'
}
클래스 필드를 참조할 경우, 생성자 함수내에서 this를 반드시 사용해야 한다
class Person {
name;
constructor(name) {
// 클래스 필드 초기화.
this.name = name;
}
}
const me = new Person('Lee');
console.log(me); // Person {name: "Lee"}
함수는 일급 객체(다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체)이므로 클래스 필드에서 메서드를 정의할 수도 있다
class Person {
// 클래스 필드에 문자열을 할당
name = 'Lee';
// 클래스 필드에 함수를 할당
getName = function () {
return this.name;
}
// 화살표 함수로 정의할 수도 있다.
// getName = () => this.name;
}
const me = new Person();
console.log(me); // Person {name: "Lee", getName: ƒ}
console.log(me.getName()); // Lee
자바스크립트는 private public protected 키워드와 같은 접근 제한자가 지원되지 않는다
따라서 기본적으로 public하며 #으로 private
를 클래스 몸체
에서만 정의할 수 있다
class Person {
// private 필드 정의
#name = '';
constructor(name) {
// private 필드 참조
this.#name = name;
}
}
const me = new Person('Lee');
// private 필드 #name은 클래스 외부에서 참조할 수 없다.
console.log(me.#name);
// SyntaxError: Private field '#name' must be declared in an enclosing class
class Person2 {
// private 필드 정의
#name = '';
constructor(name) {
this.#name = name;
}
// name은 접근자 프로퍼티다.
get name() {
// private 필드를 참조하여 trim한 다음 반환한다.
return this.#name.trim();
}
}
const me = new Person2(' Lee ');
console.log(me.name); // Lee
7. 상속에 의한 클래스 확장
상속에 의한 클래스 확장extends
은 기존 클래스를 상속받아 새로운 클래스를 확장하여 정의하는 것이다
- 상속을 통해
확장된
클래스를 서브 클래스
- 서브 클래스에게
상속된
클래스를 수퍼 클래스
// 수퍼(베이스/부모)클래스
class Base {}
// 서브(파생/자식)클래스
class Derived extends Base {}
📍 extends
class Animal {
constructor(age, weight) {
this.age = age;
this.weight = weight;
}
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, weight: 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
은 기존 클래스를 상속받아 새로운 클래스를 확장하여 정의하는 것이다
- 상속을 통해
확장된
클래스를서브 클래스
- 서브 클래스에게
상속된
클래스를수퍼 클래스
// 수퍼(베이스/부모)클래스
class Base {}
// 서브(파생/자식)클래스
class Derived extends Base {}
class Animal {
constructor(age, weight) {
this.age = age;
this.weight = weight;
}
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, weight: 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 앞에는 반드시 클래스가 와야 한다
// 생성자 함수
function Base(a) {
this.a = a;
}
// 생성자 함수를 상속받는 서브클래스
class Derived extends Base {}
const derived = new Derived(1);
console.log(derived); // Derived {a: 1}
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
📍 super
super를 호출하면 수퍼클래스의 생성자를 호출한다
단, 서브 클래스에서 contructor를 생략하지 않는 경우,
super를 호출한 뒤에 this를 참조해야 한다
// 수퍼클래스
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를 참조하면 수퍼클래스의 메서드를 호출할 수도 있다
// 수퍼클래스
class Base {
constructor(name) {
this.name = name;
}
sayHi() {
return `Hi! ${this.name}`;
}
}
// 서브클래스
class Derived extends Base {
sayHi() {
// super.sayHi는 수퍼클래스의 프로토타입 메서드를 가리킨다.
return `${super.sayHi()}. how are you doing?`;
}
}
const derived = new Derived('Lee');
console.log(derived.sayHi()); // Hi! Lee. how are you doing?
서브 클래스의 super 호출
서브클래스는 자신이 인스턴스를 생성하지 않고 수퍼클래스에게 인스턴스 생성을 위임한다
수퍼클래스의 인스턴스 생성과 this 바인딩
수퍼클래스의 constructor 내부의 코드가 실행되기 이전에는 암묵적으로 빈 객체를 생성하고,
인스턴스는 this에 바인딩이 된다
// 수퍼클래스
class Rectangle {
constructor(width, height) {
// 암묵적으로 빈 객체, 즉 인스턴스가 생성되고 this에 바인딩된다.
console.log(this); // ColorRectangle {}
// new 연산자와 함께 호출된 함수, 즉 new.target은 ColorRectangle이다.
console.log(new.target); // ColorRectangle
// 생성된 인스턴스의 프로토타입으로 ColorRectangle.prototype이 설정된다.
console.log(Object.getPrototypeOf(this) === ColorRectangle.prototype); // true
console.log(this instanceof ColorRectangle); // true
console.log(this instanceof Rectangle); // true
...
수퍼클래스의 인스턴스 초기화
this가 바인딩 되어있는 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스 프로퍼티를 초기화 한다
// 수퍼클래스
class Rectangle {
constructor(width, height) {
// 암묵적으로 빈 객체, 즉 인스턴스가 생성되고 this에 바인딩된다.
console.log(this); // ColorRectangle {}
// new 연산자와 함께 호출된 함수, 즉 new.target은 ColorRectangle이다.
console.log(new.target); // ColorRectangle
// 생성된 인스턴스의 프로토타입으로 ColorRectangle.prototype이 설정된다.
console.log(Object.getPrototypeOf(this) === ColorRectangle.prototype); // true
console.log(this instanceof ColorRectangle); // true
console.log(this instanceof Rectangle); // true
// 인스턴스 초기화
this.width = width;
this.height = height;
console.log(this); // ColorRectangle {width: 2, height: 4}
}
...
서브클래스 constructor로의 복귀와 this 바인딩
super가 반환한 인스턴스가 this에 바인딩되고
서브클래스는 별도의 인스턴스를 생성하지 않고 super가 반환한 인스턴스를 this에 바인딩하여 그대로 사용한다
// 서브클래스
class ColorRectangle extends Rectangle {
constructor(width, height, color) {
super(width, height);
// super가 반환한 인스턴스가 this에 바인딩된다.
console.log(this); // ColorRectangle {width: 2, height: 4}
...
-
서브클래스의 인스턴스 초기화
this에 바인딩된 인스턴스에 프로퍼티를 추가하고 constructor가 인수로 전달받은 초기값으로 인스턴스 프로퍼티를 초기화 -
인스턴스 반환
그렇게 처리가 끝난 this를 반환
// 서브클래스
class ColorRectangle extends Rectangle {
constructor(width, height, color) {
super(width, height);
// super가 반환한 인스턴스가 this에 바인딩된다.
console.log(this); // ColorRectangle {width: 2, height: 4}
// 인스턴스 초기화
this.color = color;
// 완성된 인스턴스가 바인딩된 this가 암묵적으로 반환된다.
console.log(this); // ColorRectangle {width: 2, height: 4, color: "red"}
}
...
8. 표준 빌트인 생성자 함수 확장
// Array 생성자 함수를 상속받아 확장한 MyArray
class MyArray extends Array {
// 중복된 배열 요소를 제거하고 반환한다: [1, 1, 2, 3] => [1, 2, 3]
uniq() {
return this.filter((v, i, self) => self.indexOf(v) === i);
}
// 모든 배열 요소의 평균을 구한다: [1, 2, 3] => 2
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]
// MyArray.prototype.uniq 호출
console.log(myArray.uniq()); // MyArray(3) [1, 2, 3]
// MyArray.prototype.average 호출
console.log(myArray.average()); // 1.75
Author And Source
이 문제에 관하여(25장. 클래스), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@seohee0112/25장.-클래스
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
// Array 생성자 함수를 상속받아 확장한 MyArray
class MyArray extends Array {
// 중복된 배열 요소를 제거하고 반환한다: [1, 1, 2, 3] => [1, 2, 3]
uniq() {
return this.filter((v, i, self) => self.indexOf(v) === i);
}
// 모든 배열 요소의 평균을 구한다: [1, 2, 3] => 2
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]
// MyArray.prototype.uniq 호출
console.log(myArray.uniq()); // MyArray(3) [1, 2, 3]
// MyArray.prototype.average 호출
console.log(myArray.average()); // 1.75
Author And Source
이 문제에 관하여(25장. 클래스), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@seohee0112/25장.-클래스저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)