Class(extends, super)
클래스(class)
원하는 구조의 객체 틀을 짜놓고, 비슷한 모양의 객체를 공장처럼 찍어낼 수 잇음
쉽게 생각해서 클래스 = 붕어빵 기계 그리고 객체 = 붕어빵으로 보면 됨
▶ 클래스를 사용하는 이유
자바스크립트는 프로토타입(prototype) 기반의 객체지향 프로그래밍 언어임
ES6부터 추가된 클래스는 직관적으로 쉽게 코드를 읽을 수 있게 만들어 줄 뿐만 아니라 작성하기도 쉽고
class 기반 언어에 익숙한 개발자가 더 빠르게 적응할 수 있음
prototype
function Me(name) {
this.name = name;
}
Me.prototype.wow = function () {
console.log("WOW!");
};
let person = new Me("Jason");
person.wow(); // WOW!
class
class Me {
constructor(name){
this.name = name;
}
wow(){
console.log("WOW!");
}
}
let person = new Me("Jason");
person.wow() // WOW!
프로토타입 문법과 클래스 문법은 같은 결과를 출력하며
문법 생김새만 다르고 내부 로직은 완전히 같은 구조(프로토타입 기반의 코드)
▶ 클래스 문법
Constructor
constructor는 클래스 내부에서 인스턴스 객체를 초기화하는 특수한 메소드
클래스 내에 한 개만 존재할 수 있으며, 만약 명시적으로 작성하지 않으면, 암시적으로 빈 constructor가 생성
따라서 초기화할 내용이 없다면, constructor를 작성하지 않아도 됨
class User {
constructor(name) {
this.name = name
}
greet() {
return `Hello, I'm ${this.name}.`
}
}
const heropy = new User('Heropy')
console.log(heropy.greet()) // 'Hello, I'm Heropy.'
const neo = new User('Neo')
console.log(neo.greet()) // 'Hello, I'm Neo.'
console.log(heropy.greet === neo.greet) // true
게터(Getter)
get 키워드를 통해, 특정한 속성의 값을 얻거나 속성을 계산해 반환하는 메소드를 정의할 수 있음
이렇게 정의한 메소드를 게터(Getter)라고 부르며, 계산된 속성(Computed property)으로 이해할 수 있음
게터는 함수지만, 호출하지 않고 속성처럼 사용
class Counter {
constructor(initialValue = 1) {
this.value = initialValue
}
get double() {
return this.value * 2
}
increase() {
this.value += 1
}
}
const counter = new Counter(1)
console.log(counter.value) // 1
console.log(counter.double) // 2
counter.increase()
console.log(counter.value) // 2
console.log(counter.double) // 4
세터(Setter)
set 키워드를 통해, 게터에 값을 할당했을 때 호출하는 메소드를 정의할 수 있음
이렇게 정의한 메소드를 세터(Setter)라고 부릅니다.
class Counter {
constructor(initialValue = 1) {
this.value = initialValue
}
get double() {
return this.value * 2
}
set double(newValue) {
this.value = newValue / 2
}
increase() {
this.value += 1
}
}
const counter = new Counter(1)
console.log(counter.value) // 1
console.log(counter.double) // 2
counter.increase()
console.log(counter.value) // 2
console.log(counter.double) // 4
counter.double = 10
console.log(counter.value) // 5
console.log(counter.double) // 10
double 속성(게터)은 value 속성을 기준으로 값을 계산해 반환함
따라서 double에 값을 할당하면, 기준이 되는 value 속성의 값을 갱신해야 게터의 반환값도 변경될 수 있음
- Getter/Setter
바로 접근하지 말고 메서드를 통해 경유해서 설정하도록 하는 기법이 바로 Getter와 Setter 개념이라고 보면 됨
Getter와 Setter를 사용하면 객체 내부 속성에 직접 접근하지 않아 객체의 정보 은닉을 가능하게 해주어 보안을 강화할 수 있고 코드의 안전성과 유지보수성을 높일 수 있으며 옳지 못한 값을 넣으려고 할때 이를 미연에 방지할 수 있다는 장점이 있음
정적 메소드
static 키워드를 통해, 클래스의 인스턴스 없이 호출하는 메소드를 정의
이렇게 정의한 메소드를 정적 메소드(Static method)라고 부름
주로, 클래스 단위로 사용하는 유틸리티(Utilities) 함수를 정의할 때 사용
class User {
constructor(name, age) {
this.name = name
this.age = age
}
greet() {
return `Hello, I'm ${this.name}.`
}
static isUser(value) {
return !!(value && typeof value.name === 'string' && typeof value.age === 'number')
}
}
const lewis = new User('Lewis', 92)
console.log(User.isUser(lewis)) // true
const neo = {
name: 'Neo',
age: 12
}
console.log(User.isUser(neo)) // true
const amy = {
name: 'Amy'
}
console.log(User.isUser(amy)) // false
대표적으로 배열 데이터에서 사용하는 Array.isArray() 메소드도 정적 메소드
배열 데이터인지 확인하는 메소드로, Array 인스턴스(배열 데이터)가 아닌 Array 클래스 자체에서 호출
const arr = [1, 2, 3]
const obj = { a: 1 }
console.log(Array.isArray(arr)) // true
console.log(Array.isArray(obj)) // false
화살표 함수 메소
화살표 함수(Arrow function)를 통해, 메소드를 정의할 수 있음
주의할 점은 화살표 함수 메소드는 prototype이 아닌 생성된 각 인스턴스의 개별 메소드로 추가됨
즉, 화살표 함수 메소드는 프로토타입 메소드가 아님
class User {
// ...
greet = () => {
return `Hello, I'm ${this.name}.`
}
}
// ...
console.log(neo.greet === evan.greet) // false
greet가 각 인스턴스의 개별 메소드로 추가되면서 서로 일치하지 않는 것을 확인할 수 있음
정보 은닉
클래스 내부에서 # 키워드를 통해 특정한 속성이나 메소드를 클래스 외부에서 접근할 수 없도록 제한할 수 있으며,
이를 비공개 필드(Private field)라고 부름
class 클래스 {
// 클래스 바디
}
class 클래스 {
#속성 = 초깃값
#메소드() {}
}
비공개 필드는 클래스 바디(Class body)에서 정의
class User {
#age = 0
constructor(name, age) {
this.name = name
this.#age = age
}
getAge() {
return this.#age
}
}
class Admin extends User {
constructor(name, age) {
super(name, age)
console.log(this.#age) // Error: Private field '#age' must be declared in an enclosing class
}
}
const neo = new User('Neo', 12)
console.log(neo.name) // 'Neo'
console.log(neo.getAge()) // 12
console.log(neo.#age) // Error: Private field '#age' must be declared in an enclosing class
#age 속성은 클래스 내부에서만 접근 가능함(자식 클래스에서도 접근할 수 없음)
익명 클래스
클래스 이름을 생략해, 익명 클래스(Anonymous class)로 정의할 수 있음
사용처에서 이름을 결정하는 모듈의 기본 내보내기(Default export) 같은 형태로 사용
export default class {
constructor(name) {
this.name = name
}
}
import MyClass from './myClass.js'
const neo = new MyClass('Neo')
console.log(neo.name) // 'Neo'
클래스 상속(extends/super)
클래스 상속(class inheritance, subclassing) 기능을 통해 한 클래스의 기능을 다른 클래스에서 재사용할 수 있음
▶ extends
extends 키워드는 클래스를 다른 클래스의 하위 클래스로 만들기 위해 사용
class Parent {
// ...
}
class Child extends Parent {
// ...
}
extends 키워드를 통해 Child 클래스가 Parent 클래스를 상속
이 관계를 보고 '부모 클래스-자식 클래스' 혹은 '슈퍼 클래스(superclass)-서브 클래스(subclass)' 관계라고 함
클래스 A가 다른 클래스 B를 상속받으면
- 자식 클래스 A를 통해 부모 클래스 B의 정적 메소드와 정적 속성을 사용할 수 있음
- 부모 클래스 B의 인스턴스 메소드와 인스턴스 속성을 자식 클래스 A의 인스턴스에서 사용할 수 있음
class Parent {
static staticProp = 'staticProp';
static staticMethod() {
return 'I\'m a static method.';
}
instanceProp = 'instanceProp';
instanceMethod() {
return 'I\'m a instance method.';
}
}
class Child extends Parent {}
// 상속하면 부모의 static요소들을 사용 가능
console.log(Child.staticProp); // staticProp
console.log(Child.staticMethod()); // I'm a static method.
// 상속하면 부모의 인스턴스를 사용 가능
const c = new Child();
console.log(c.instanceProp); // instanceProp
console.log(c.instanceMethod()); // I'm a instance method.
Child 클래스는 Parent 클래스의 정적 요소와 인스턴스 요소를 프로토타입 체인을 통해 상속받음
▶ super
super 키워드의 동작 방식
- 생성자 내부에서 super를 함수처럼 호출하면, 부모 클래스의 생성자가 호출
- 정적 메소드 내부에서는 super.prop과 같이 써서 부모 클래스의 prop 정적 속성에 접근할 수 있
- 인스턴스 메소드 내부에서는 super.prop과 같이 써서 부모 클래스의 prop 인스턴스 속성에 접근할 수 있음
super(); // 부모 생성자
super.메소드명 // 접근
class Person{
constructor(name, first, second){
this.name=name;
this.first=first;
this.second=second;
}
sum(){
return (this.first + this.second);
}
}
class Person2 extends Person{
// override Person
constructor(name, first, second, third){
super(name, first, second); //부모 생성자를 가져와서 행하게 함
this.third = third;
}
sum(){
// 오버로딩 메소드에서 온전한 부모 메소드를 사용하고 싶을때
return super.sum() + this.third; // 부모 메소드를 가져와서 사용
}
}
var kim = new Person2('kim', 10, 20, 30);
document.write(kim.sum()); // 60
super 함수는 자식 클래스의 this 키워드 사용 전에 호출해야함
▶ 인스턴스 확인
instanceof 키워드를 통해, 어떤 클래스의 인스턴스인지를 포함해 그 클래스의 상속 체인까지 확인할 수 있음
혹은, 인스턴스의 .contructor 속성으로 특정 클래스의 인스턴스인지 확인할 수 있음
class User {
constructor(name) {
this.name = name
}
}
class Admin extends User {
constructor(name) {
super(name)
this.admin = true
}
}
const neo = new Admin('Neo')
console.log(neo instanceof Admin) // true
console.log(neo instanceof User) // true
console.log(neo instanceof Object) // true
console.log(neo.constructor === Admin) // true
console.log(neo.constructor === User) // false
console.log(neo.constructor === Object) // false
console.log(neo)
Reference