안녕하세요.
이 공간은 제가 프론트엔드를 처음 접하며 배운 것을 주저리주저리 쓰는 공간입니다.
이번 내용은 javascript 13편입니다.
클래스는 객체를 더 안전하며 효율적으로 생성하기위해 만들어진 문법입니다.
어떠한 위험이 있고, 비효율적인 측면이 있기 때문에 생성된 문법이죠.
아래 예시는 직사각형을 나타내는 클래스(Rectangle)을 선언하고 사용합니다.
거기에 사각형의 둘레를 구하는 메소드 getPerimeter()와 사각형의 넓이를 구하는 메소드 getArea()라는 메소드를 추가했어요.
class Rectangle {
constructor(width, height) {
this.width = width
this.height = height
}
// 직사각형의 둘레를 구하는 메소드를 추가합니다.
getPerimeter() {
return 2 * (this.width + this.height)
}
// 직사각형의 넓이를 구하는 메소드를 추가합니다.
getArea() {
return this.width * this.height
}
}
const rectangle = new Rectangle(10, 20)
console.log(`사각형의 둘레: ${rectangle.getPerimeter()}`)
console.log(`사각형의 넓이: ${rectangle.getArea()}`)

여기에 다른 도형을 추가해볼까요?
정사각형을 나타내는 Square라는 클래스를 추가하겠습니다.
// 직사각형 클래스입니다.
class Rectangle {
constructor(width, height) {
this.width = width
this.height = height
}
// 직사각형의 둘레를 구하는 메소드를 추가합니다.
getPerimeter() {
return 2 * (this.width + this.height)
}
// 직사각형의 넓이를 구하는 메소드를 추가합니다.
getArea() {
return this.width * this.height
}
}
// 정사각형 클래스입니다.
class Square {
constructor(length) {
this.length = length
}
// 정사각형의 둘레를 구하는 메소드를 추가합니다.
getPerimeter() {
return 4 * this.length
}
// 정사각형의 넓이를 구하는 메소드를 추가합니다.
getArea() {
return this.length * this.length
}
}
const rectangle = new Rectangle(10, 20)
const square = new Square(20)
console.log(`직사각형의 둘레: ${rectangle.getPerimeter()}`)
console.log(`직사각형의 넓이: ${rectangle.getArea()}`)
console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
console.log(`정사각형의 넓이: ${square.getArea()}`)

그런데 코드를 가만히 보면
클래스간 차이가 거의 없는 모습이에요.
직사각형인지 정사각형인지에 따른 계산 방법만 조금 다를뿐,
메소드의 형태들은 비슷합니다.
물론 이렇게 분리하면 클래스를 활용하긴 편리할 수 있으나, 선언부분이 복잡해져요.
이럴때 등장하는 개념이 상속입니다.
class 클래스명 extends 부모클래스 이름 {
// 코드 내용
}
상속은 말 그대로, 어떤 클래스가 가지고 있는 자산(속성이나 메소드)를 다른 클래스에게 물려주는 형태로 사용합니다.
이때 유산을 물려주는 클래스를 부모 클래스,
유산을 물려받는 클래스는 자식 클래스라 부르기도 합니다.
그럼 위의 예시를 상속을 활용해 바꿔볼게요.
// 직사각형 클래스이자 부모 클래스가 될 Rectangle입니다.
class Rectangle {
constructor(width, height) {
this.width = width
this.height = height
}
// 직사각형의 둘레를 구하는 메소드를 추가합니다.
getPerimeter() {
return 2 * (this.width + this.height)
}
// 직사각형의 넓이를 구하는 메소드를 추가합니다.
getArea() {
return this.width * this.height
}
}
// 정사각형 클래스이자 자식 클래스인 Square입니다.
class Square extends Rectangle {
constructor(length) {
super(length, length) // super는 부모의 생성자 함수를 호출합니다.
}
}
const square = new Square(20)
// getPerimeter()와 getArea()는 부모클래스에서 상속받았으므로 사용 가능합니다.
console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
console.log(`정사각형의 넓이: ${square.getArea()}`)

super()함수는 부모의 생성자를 나타내는 함수로, super(length, length)를 호출할 시 Rectangle 클래스의 constructor(width, height)가 호출되면서 각자 자리를 찾아 들어갑니다.
보통 개발자를 나눌때 프레임워크나 엔진을 만드는 개발자와,
그를 활용하여 애플리케이션, 게임등을 개발자로 나뉩니다.
전자를 프레임워크 개발자, 후자를 애플리케이션 개발자라고도 부릅니다.
애플리케이션 개발자가 프레임워크와 엔진을 활용하는 가장 기본적인 개념이 상속입니다.
아래 예시는 먼저 프레임워크 개발자가 Square 클래스를 만든 후,
그 다음 애플리케이션 개발자가 그 클래스를 활용한다는 가정을 하고 보겠습니다.
// 정사각형 클래스입니다.
class Square {
constructor(length) {
this.length = length
}
// 정사각형의 둘레를 구하는 메소드를 추가합니다.
getPerimeter() {
return 4 * this.length
}
// 정사각형의 넓이를 구하는 메소드를 추가합니다.
getArea() {
return this.length * this.length
}
}
// ????????
const square = new Square(-20)
console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
console.log(`정사각형의 넓이: ${square.getArea()}`)

Suqare 객체를 생성할 때 매개변수가 음수입니다.
길이의 값은 음수가 나올 수 없는데 말이에요.
프레임워크 개발자 입장에서는
'에이 어떤 미친X이 길이에 마이너스를 때려박겠어?ㅋㅋ'
라고 생각하고 클래스를 만들었을 수 있습니다.
하지만 애플리케이션 개발자 입장에서는
'설명도 없고 염X 이건 뭐야 그냥 아무거나 때려박자'
라고 생각할 수 있습니다.
세상엔 정말 다양하고 개성넘치는 사람이 많잖아요?
이런 일을 방지하기 위해, 아래처럼 조건문을 활용할 수 있습니다.
// 정사각형 클래스입니다.
class Square {
constructor(length) {
if (length <= 0) {
// throw 키워드로 강제적으로 오류를 만들어버립니다.
throw "길이는 0보다 커야합니다!"
}
this.length = length
}
// 정사각형의 둘레를 구하는 메소드를 추가합니다.
getPerimeter() {
return 4 * this.length
}
// 정사각형의 넓이를 구하는 메소드를 추가합니다.
getArea() {
return this.length * this.length
}
}
const square = new Square(-20)
console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
console.log(`정사각형의 넓이: ${square.getArea()}`)

물론 이렇게 인지시켜줄 수도 있겠지만, 생성자로 객체를 생성한 이후 사용자가 length 속성을 변경하는것 까지는 막을 수 없습니다.
// 어라 음수가 안된다고 에러가 뜨네? 그러면 우선 양수를 적어놓자
const square = new Square(20)
// 그리고 여기서 바꿔버리면 되지
square.length = -20
console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
console.log(`정사각형의 넓이: ${square.getArea()}`)

결국 클래스 사용자가 클래스의 속성이나 메소드를 엉뚱한 방향으로 사용하는 것을 막아주는게 중요한데요.
이 때 희망처럼 등장하는 문법이 private 속성과 메소드입니다.
클래스 외부에서는 private 속성으로 바뀐 클래스에 접근할 수 없거든요.
class 클래스 이름 {
#속성 이름
#메소드 이름() {
}
}
속성과 메소드 앞에 #을 붙여주면 됩니다.
그러면 private 속성과 메소드로 바뀌는데요.
주의할 점은 사용하기 전에 미리 외부에 "난 이거 이거 private속성으로 쓸거임" 이라고 선언해줘야 합니다.
class Square {
// 여기에서 해당 속성을 private으로 사용하겠다 선언해줍니다.
#length
constructor(length) {
if (length <= 0) {
throw "길이는 0보다 커야합니다!"
}
this.#length = length
}
getPerimeter() {
return 4 * this.#length
}
getArea() {
return this.#length * this.#length
}
}
const square = new Square(20)
square.length = -20 // 이제 이런 변태짓도 통하지 않습니다.
console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
console.log(`정사각형의 넓이: ${square.getArea()}`)

그런데 만약 #length를 사용하면 어떻게 될까요?
class Square {
// 여기에서 해당 속성을 private으로 사용하겠다 선언해줍니다.
#length
constructor(length) {
if (length <= 0) {
throw "길이는 0보다 커야합니다!"
}
this.#length = length
}
getPerimeter() {
return 4 * this.#length
}
getArea() {
return this.#length * this.#length
}
}
const square = new Square(20)
square.#length = -20 // 머리를 좀 굴렸네요.
console.log(`정사각형의 둘레: ${square.getPerimeter()}`)
console.log(`정사각형의 넓이: ${square.getArea()}`)

클래스 외부에서 접근이 불가능하므로, 신텍스 에러가 발생합니다.