class
라는 이름으로 포장하여 다른 언어의 클래스와 비슷한 문법의 형태로 만들었다.자바스크립트 클래스는 프로토타입 기반의 자바스크립트 객체지향을 다른 언어의 객체지향과 비슷한 형태로 표현하는 최신문법이다.
⚠️ 그러나 클래스와 생성자 함수의 동작이 동일하지는 않다!
const inst = new Person('Tom', 25);
// ⛔Uncaught ReferenceError: Person is not defined
class Person{
constructor(name, age){
this.name = name;
this.age = age;
}
}
new
를 빼고 생성을 시도하면 오류가 발생한다.new
를 빼고 호출하면 오류없이 undefined
를 반환하던 생성자 함수와 다르다.class Person{
constructor(name, age){
this.name = name;
this.age = age;
}
}
const inst = Person('Tom', 25);
// ⛔Uncaught TypeError:
// Class constructor Person cannot be invoked without 'new'
constructor
메서드class Person{
constructor(name, age, married = false){
this.name = name;
this.age = age;
this.married = married; // 기본값을 사용 할 수 있다.
// 인스턴스 생성시 전달되지 않은 인자는 기본값으로 세팅
}
}
const person1 = new Person('Tom', 25, true);
const person2 = new Person('Mike', 15);
클래스는 객체에서와 같이 프로퍼티로써의 함수가 아니라 메서드
를 사용할 수 있다.
아래 코드를 보자
class Dog {
bark () {
return '멍멍';
}
}
const badugi = new Dog();
console.log(badugi, badugi.bark());
/*
Dog {} '멍멍'
[[Prototype]]: Object
bark: ƒ bark()
constructor: class Dog
[[Prototype]]: Object
*/
function Dog2 () {
this.bark = function () {
return '멍멍';
}
}
const badugi = new Dog2();
console.log(badugi, badugi.bark());
/*
Dog2 {bark: ƒ} '멍멍'
bark: ƒ ()
[[Prototype]]: Object
*/
Dog
로 생성한 인스턴스를 출력해보면 해당 인스턴스는 bark라는 메서드를 가지고 있지 않다. 메서드
는 prototype
에 들어가 있다.객체의 메서드는 인스턴스 내에 들어가 있다. 객체는 메서드를 프로퍼티로 취급한다면, 클래스는 자신으로부터 파생된 모든 인스턴스들이 공통적으로 갖는 기능이라는 의미로서 프로토타입에 메서드가 저장되는 것 같다.
constructor
메서드를 사용하여 프로퍼티를 정의했었다.constructor
밖에서 this
를 사용할 필요 없이 인스턴스의 프로퍼티를 정의할 수 있는 문법을 필드라고 부른다.// 필드값이 지정되어 있으므로 constructor 메서드 필요없음
// 필드값으로 객체의 프로퍼티 기본 값을 설정
class Person {
name = 'Tom';
age = 25;
hello () {
console.log(`Hello my name is ${this.name}, ${this.age} years old`);
}
}
const person1 = new Person();
console.log(person1); // Person {name: 'Tom', age: 25}
person1.hello(); // Hello my name is Tom, 25 years old
class Chicken {
no = 0;
menu = { '후라이드': 10000, '양념치킨': 12000 };
constructor (name, no) {
this.name = name;
if (no) this.no = no;
}
introduce () {
return `안녕하세요, ${this.no}호 ${this.name}점입니다!`;
}
order (name) {
return `${this.menu[name]}원입니다.`
}
}
const chain0 = new Chicken('(미정)');
console.log(chain0, chain0.introduce());
/*
Chicken {no: 0, menu: {…}, name: '(미정)'}
'안녕하세요, 0호 (미정)점입니다!'
*/
const chain1 = new Chicken('판교', 3);
console.log(chain1, chain1.introduce());
/*
Chicken {no: 0, menu: {…}, name: '(미정)'}
'안녕하세요, 3호 판교점입니다!'
*/
no
필드: 생성자 함수에서 사용했던 기본 값을 필드로 대체할 수 있다.menu
필드: 생성할 때 전달하는 인자의 영향을 받지 않기 때문에 필드로 지정했다.name
과 no
를 인자로 받는데 만약 인스턴스를 생성 할 때 name
만 인자로 전달 했다면 no
는 기본 값으로 설정된다.chain1.menu['양념치킨'] = 13000;
console.log(chain0.order('양념치킨'), chain1.order('양념치킨'));
// 12000원입니다. 13000원입니다.
class Chicken {
// 정적 변수와 메서드
static brand = 'JS치킨';
static contact () {
return `${this.brand}입니다. 무엇을 도와드릴까요?`;
}
constructor (name, no) {
this.name = name;
this.no = no;
}
introduce () {
return `안녕하세요, ${this.no}호 ${this.name}점입니다!`;
}
}
console.log(Chicken);
console.log(Chicken.contact());
💡 클래스도 함수
class Dog {
bark () {
return '멍멍';
}
}
console.log(typeof Dog); //function
const dog = Dog; // 할당될 수 있는 일급 객체
const pome = new dog();
console.log(pome);
getter, setter
함수라고도 부른다.get
, set
을 앞에 붙여서 사용한다.const person1 = {
age: 17,
get koreanAge () {
return this.age + 1;
},
set koreanAge (krAge) {
this.age = krAge - 1;
}
}
()사용
을 사용하면 오류가 발생한다.console.log(person1, person1.koreanAge); // {age: 17} 18
/*
age: 19
koreanAge: (...)
get koreanAge: ƒ koreanAge()
set koreanAge: ƒ koreanAge(krAge)
[[Prototype]]: Object
*/
person1.koreanAge = 20;
console.log(person1, person1.koreanAge); // {age: 19} 20
person1.koreanAge();
// ⛔Uncaught TypeError: person1.koreanAge is not a function
⭐ 클래스에서도 사용할 수 있다.
class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
get info() {
return `${this.name}, ${this.age}`;
}
set personName(newName) {
if (typeof newName !== 'string') return;
this.name = newName;
}
}
const person1 = new Person('Tom',25);
console.log(person1.info); //Tom, 25
person1.personName = 'Mike';
console.log(person1); // Person {name: 'Mike', age: 25}
person1.personName = 1;
console.log(person1); // Person {name: 'Mike', age: 25}
// 이름을 바꾸는데 숫자가 인자로 들어와서 아무일도 일어나지 않았다.
⚠️ 주의할 점! 필드 이름과 setter의 이름이 같지 않도록 해야한다.
class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
get info() {
return `${this.name}, ${this.age}`;
}
set name(newName) {
if (typeof newName !== 'string') return;
this.name = newName;
}
}
const person1 = new Person('Tom',25);
//⛔ Uncaught RangeError: Maximum call stack size exceeded
- 인스턴스를 생성 할 때 생성자 함수가 실행된다.
- 생성자 함수에서
this.name = name
부분은 name에 값을 할당하는 것이기 때문에 setter를 호출하게 된다.- setter안에서
this.name = name
을 실행하게 되는데 이 코드 역시 setter를 호출하게 된다!!!!!- setter가 자기자신을 무한 호출하게 된다.
class Person {
constructor (name, age) {
this.name = name;
this.age = age;
}
get info() {
return `${this.name}, ${this.age}`;
}
set name(newName) {
if (typeof newName !== 'string') return;
this._name = newName;
}
}
const person1 = new Person('Tom',25);
this._name
으로 설정해주면 생성자에서 호출한 this.name
은 접근자 프로퍼티를 가리키게 되고, setter는 _name
데이터 프로퍼티에 값을 저장하게 된다.console.log(person1); //Person {_name: 'Tom', age: 25}
person1._name
과 같은 형식으로 접근 할 수 있어 오류 발생의 가능성이 있다.class Person {
//은닉 필드
#name = '';
#age = 0;
constructor (name, age) {
this.#name = name;
this.#age = age;
}
}
const person1 = new Person('김복동', 32);
console.log(person1.#name); //오류발생
#
을 붙여 은닉할 수 있다.#필드
를 하나의 프로퍼티로 인식함....뭔소리야?
예를 들어 계산기를 보도록 하자.
계산기에는 여러가지 버튼이 있다. 버튼에는 숫자를 입력하는 버튼, 연산자 버튼, 계산 버튼 등 여러가지 종류의 버튼이 존재한다. 하지만 버튼의 모양, 클릭시 반응, 차지하는 공간 등 다른 종류의 버튼이지만 같은 속성을 지니고 있다.
그래서 숫자 버튼과 계산 버튼은 다른 Class에서 파생되지만 그 두 Class가 Button이라는 클래스에서 NumberButton, FunctionButton과 같이 파생된다고 생각할 수 있다.
NumberButton, FunctionButton은 Button 클래스에서 상속된 것이다.
class Bird {
wings = 2;
}
class Eagle extends Bird {
claws = 2;
}
class Penguin extends Bird {
swim () { console.log('수영중...'); }
}
class EmperorPenguin extends Penguin {
size = 'XXXL';
}
Bird class는 기본 값으로 2인 wings필드만을 가지고 있다.
(모든 새는 날개가 2개다.)
Eagle은 Bird에서 확장extends
한다. 객체지향의 언어로 표현한다면 Eagle class는 Bird class에서 파생된 것이다.
Bird class를 부모 클래스, Eagle을 자식 클래스라고 부른다. 그리고 Eagle이라는 class는 claws라는 자신만의 필드값을 가지고 있다.
(독수리는 발톱이 있다.)
const birdy = new Bird();
const eaglee = new Eagle();
const pengu = new Penguin();
const pengdol = new EmperorPenguin();
console.log(birdy, eaglee, pengu, pengdol);
/*
Bird {wings: 2}
Eagle {wings: 2, claws: 2}
Penguin {wings: 2}
EmperorPenguin {wings: 2, size: 'XXXL'}
*/
자식 클래스에 상속받은 부모 클래스의 데이터가 포함되어 있는 것을 확인 할 수 있다.
그런데 EmperorPenguin의 부모인 Penguin에서 상속받은 메서드는 어디에 있는 걸까?
EmperorPenguin의 데이터를 자세히 살펴보자
EmperorPenguin {wings: 2, size: 'XXXL'}
size: "XXXL"
wings: 2
[[Prototype]]: Penguin
constructor: class EmperorPenguin
[[Prototype]]: Bird
constructor: class Penguin
swim: ƒ swim()
[[Prototype]]: Object
부모 클래스의 메서드는 인스턴스의 부모 class 프로토타입으로 저장되어 있는 것을 확인 할 수 있다.
자바스크립트 클래스에서는 상속마저 프로토타입을 기반으로한다.
따라서 Penguin class에서 파생된 class는 수영을 할 수 있지만 다른 class에서 파생된 class는 수영기능이 없다.
(독수리는 수영을 못하니까)
만약 같은 클래스에서 상속을 받아도 서로 다른 기능을 가질 수 있다는 것을 의미한다.
표현력이 말도 안되게 상승한다.
... ❓
class Bird {
wings = 2;
canFly = true;
travel () { console.log('비행중...') }
}
class Eagle extends Bird {
claws = 2;
}
class Penguin extends Bird {
canFly = false;
travel () { console.log('수영중...') }
}
Bird Class는 두 개의 wings
, 날 수 있는 능력, 비행 메서드를 가지고 있다.
Eagle class는 Bird class를 단순히 상속하지만 Penguin class는 다르다.
부모 클래스가 가지고 있는 canFly
의 값을 False
로 변경했다.
(독수리는 날 수 있지만 펭귄은 날 수 없으니까)
이것이 오버라이딩이다!
super
라는 기능을 사용하면 된다.그런데 굳이 데이터를 더 많이 담고 있는 자식 클래스가 부모 클래스에 접근 할 이유가 있을까?
class Person{
name = ''
age = 0
constructor(name, age){
this.name = name;
this.age = age;
}
introduce(){
return `안녕하세요 ${this.age}세, ${this.name}입니다.`
}
}
class White extends Person{
height = '170';
constructor(name, age, height){
this.name = name;
this.age = age;
this.height = height;
}
whiteIntroduce(){
return `안녕하세요 ${this.age}세, ${this.name}, ${this.height}cm입니다.`
}
}
Person 클래스는 필드로 name과 age를 가지고 있고, White 클래스는 Person을 상속받아 height이라는 필드를 추가적으로 가지고 있다.
그런데 생성자와 메서드 부분을 보자. 같은 작업이 반복해서 일어나고 있다!!!! 개발자가 피해야 할 것들 중 하나인 불필요한 반복이 생겼다.
이러면 우리가 시간을 내어 객체지향을 공부한 이유가 사라진다.
부모의 기능을 이용해서 자식의 기능을 만드려면 어떻게 해야 할까?
다음과 같이 바꿔 볼 수 있다.
class White extends Person{
height = '170';
constructor(name, age, height){
super(name, age);
this.height = height;
}
whiteIntroduce(){
return super.introduce() + `${this.height}cm입니다.`
}
}
super
는 부모 클래스의 constructor를 가리킨다.super
는 부모 클래스를 가리킨다.(이해를 돕기 위한 사진일 뿐 정확한 메모리 구조와는 다르다.)