오늘은 객체 지향 프로그래밍(OOP, Object-oriented programming)에 대해 배워보겠다.
- Encapsulation (캡슐화)
- Inheritance (상속)
- Abstraction (추상화)
- Polymorphism (다형성)
+ 보강책으로 TS도 나왔으니 함 보기!
private
키워드 : 클래스 내부에만 쓰이는 속성, 메소드를 구별짓기 위해 사용되는 키워드// TypeScript 문법
class Animal {
private name: string; // JS에선 #이 있지만 지원 브라우저 적음
constructor(theName: string) {
this.name = theName;
}
}
new Animal("Cat").name; // 사용 불가
// Property 'name' is private and only accessible within class 'Animal'.
interface
키워드 : 인터페이스 키워드를 따로 써 인터페이스만의 클래스 구축을 용이하게 만드는 키워드// TypeScript 문법
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) {}
}
단순 객체를 사용한 클로저 모듈은 이렇다.
⭐️주의 : 메소드 호출 방식을 쓸 땐 화살표 함수 금지 - this, 인수, new 등을 쓸땐 쓰면 안됨
let counter1 = {
value: 0,
increase: function() {
this.value++ // 메서드 호출을 할 경우, this는 counter1을 가리킵니다
},
decrease: function() {
this.value--
},
getValue: function() {
return this.value
}
}
counter1.increase()
counter1.decrease()
counter1.getValue() // 0
문제점은 이제 똑같은 기능을 쓰기위함과 다른 결괏값을 지정하고 싶을 때 counter2
라는 게 생길 수도 있다. 그러면 또 2에 같은 기능을 또 써줘야한다. 여기서 객체 지향이 나온다.
function makeCounter() {
let value = 0;
return {
increase: function() {
value++;
},
decrease: function() {
value--;
},
getValue: function() {
return value;
}
}
}
let counter1 = makeCounter()
counter1.increase()
counter1.getValue() // 1
let counter2 = makeCounter()
counter2.decrease()
counter2.decrease()
counter2.getValue() // -2
그 전 코드와 다르게 makeCounter
라는 클로저가 담긴 모듈 패턴 함수를 써서 변수 선언 및 할당만 해주면 쓸 수 있는 간편함이 생겼다!
위의 사진에서 청사진(원형 객체, original form)은 클래스, 만들어낸 객체를 인스턴스라고 부른다. 이제 클래스 문법에 대해 공부해보겠다!파스칼 케이스 : 일반 명사로 대문자를 이용해 만듦
참고 : ES5까지는 class 문법이 따로 없고 함수 선언식을 통해 OOP를 했음, ES6부터 클래스, 생성자, new
등 직접적인 문법이 개발됐음
class
키워드 : class
를 통해 기본적인 '생성자 함수'를 만듦, 참고로 함수는 리턴값이 없음this.property
: 함수가 실행될 때, 해당 스코프마다 생성되는 고유한 실행(execution) contextconstructor
: '생성자 함수'가 실행될 때의 초깃값class Car {
// 기본 생성자 함수, 필수임, 없어도 나중에 추가하면 컴파일러가 추가 해줌
constructor(brand, name, color) {
this.brand = brand;
this.name = name;
this.color = color;
}
// 메소드 정의
refuel(~) {
// 부족한 연료 채워 넣는 기능
}
drive(~) {
// 운전 시작하는 기능
}
}
new
키워드 : class
의 사례임, class
(함수)를 만들고 변수에 할당해 인스턴스를 만드는 과정에서 new
를 써서 새 인스턴스를 만듦 new
로 인스턴스를 생성했다면 그게 this
값이 됨let avante = new Car("hyundai", "avante", "black");
avante.color; // 'black'
avante.drive(); // (메소드) 아반떼가 운전 시작
let mini = new Car("BMW", "mini", "white");
mini.brand; // 'BMW'
mini.refuel(); // 미니에 연료를 공급
사실 우리는 배열을 하며 객체 지향 개념을 마스터했다!
// 배열 정의 - let arr = ['rhino', 'yoon', 'zzang'] : Array의 인스턴스 창조와 동일
let arr = new Array('rhino', 'yoon', 'zzang')
arr.length; // 3
// 새 엘리멘트 추가 : push-메소드의 정의 앞에 Array.prototype이 붙어있음, push는 Array의 객체라면 쓸 수 있음
arr.push('baby')
+ 정리
function Car(brand, name, color) { // class, constructor 無
this.brand = brand;
this.name = name;
this.color = color;
}
// prototype을 통해 원형 객체를 만들어 메소드 정의
Car.prototype.refuel = function() {
// 연료 공급을 구현하는 코드
}
Car.prototype.drive = function() {
// 운전을 구현하는 코드
}
// 인스턴스에도 new 無
class Counter {
// 생성자 호출을 할 경우, this는 new 키워드로 생성한 Counter의 인스턴스임
constructor() {
this.value = 0; // let value = 0;
}
increase() {
this.value++
}
decrease() {
this.value--
}
getValue() {
return this.value
}
}
let counter1 = new Counter() // 생성자 호출
counter1.increase()
counter1.getValue() // 1
.__proto__
(prototype Link)라는 메소드와 .prototype
메소드로 참조 후 복사.__proto__
는 조상의 유전자를 찾아 떠나는 link를 발견하게함, 부모 속성을 조회.prototype
는 복제된 자신과 후손을 만드는 느낌? 아 이거 좀 헷갈려서 영상으로 개념 잡고 가자class Human {
constructor(name, age) {
this.name = name;
this.age = age;
}
sleep() {
console.log(`${this.name}은 잠에 들었습니다`);
}
}
let kimcoding = new Human('김코딩', 30);
// 실습 : .__proto__와 .prototype의 구조
Human.prototype.constructor === Human; // 1. true
Human.prototype === kimcoding.__proto__; // 2. true
Human.prototype.sleep === kimcoding.sleep; // 3. true
__proto__
__proto__
가 가리키는 링크를 따라서 자신의 부모 역할을 하는 프로토타입 객체의 프로퍼티나 메소드를 접근__proto__
로 검색하여 부모 역할의 프로토타입 객체의 속성, 메소드를 나타낸 역사를 뜻함!Object.prototype
__proto__
으로 구조를 알고, 그 부모의 자식을 만들고 싶다면 extends
을 써서 복사, super
로 호출(메소드, 속성 불러오기)을 쓰는 것이다!배웠던 DOM 구조에도 프로토타입이 존재함!
let div = document.createElement('div');
의 구조를 살펴보자HTMLDivElement
- ... - Node
- EventTarget
으로 나온다!(이벤트 수신기, 이벤트를 받아주는 그릇이라는 뜻, 이벤트 핸들러, 리스너 비슷)div
를 만든 DOM인 EventTarget
부터 HTMLDivElement
까지 조상들은 Object
이고 __proto__
는 그 조상들을 찾을 수 있다!Beesbeesbees라는 페어 과제를 수행
├── Grub
│ └── Bee
│ ├── HoneyMakerBee
│ └── ForagerBee
인 구조로 되어있음! 즉, 체인을 경험해보고, 각각을 캡슐화하고 상속받아 그 다음 벌에게 주고 기능을 또 추가하는 그런 과정을 그림
class Grub {
// constructor() {
// this.age = 0;
// this.color = "pink";
// this.food = "jelly";
// 디폴트 파라미터로 테스트 + 클래스 문법을 다 지킬 수 있음(원래 생성자에 파라미터 없고 age에 할당된 모습이어서 이상했음)
constructor(age = 0, color = "pink", food = "jelly") {
this.age = age;
this.color = color;
this.food = food;
}
eat() {
return `Mmmmmmmmm ${this.food}`;
}
}
class Bee extends Grub {
constructor(age = 5, color = "yellow", food, job = "Keep on growing") {
super();
this.age = age;
this.color = color;
this.job = job;
}
}
class ForagerBee extends Bee {
constructor(age = 10, job = "find pollen", canFly = true, treasureChest) {
super();
this.age = age;
this.job = job;
this.canFly = canFly;
this.treasureChest = [];
}
forage(treasure) {
return this.treasureChest.push(treasure);
}
} // Test에 foragerBee.forage('pollen') ... 등으로 인수를 줌
extends
는 아들을 만들었으니 프로토타입 기반의 키워드구나! 그래서 복사된 객체의 메소드를 다 가져온다!extends
로 Bee
가 Grub
의 아들로 만듦 : 아하! 이제 ForagerBee
에 .__proto__
를 붙이면 부모인 Bee가 나오겠구나!super()
로 요소를 데려오는 구나, 거기에 재할당(Overriding)도 가능하구나!super()
는 자식의 클래스에서 쓰이고, 그 말은 즉슨 extends
로 불러왔으면 super()
는 필수구나! 그래서 super()
이전의 this
를 쓰면 생성자 함수를 호출할 수 없구나!@super()
인자를 넣으면 순서에 맞춰 인자들을 배치해줘야한다. 그런데 상속을 많이 하게 되면 중첩되는 프로토타입들이 많아져 순서를 맞추는 게 힘들 수도 있다. super()
로 일단 다 불러 온 다음에 덮어 쓰는 번거로움이 있어도 super()
안에 필요한 것만 쏙쏙 골라서 this
로 배정해주자! 오히려 더 순서 맞추는 게 일이니까 일단 가져온 다음에 원하는 값은 다시 this
이용해서 덮어 씌워라!this
는 인스턴스를 만들 때 그 값을 불러오고 덮어쓸려고 쓰는 경우구나! class Human {
constructor(name, age) {
this.name = name;
this.age = age;
}
sleep() {
return `${this.name}이(가) 잠을 잡니다.`;
}
}
class Student extends Human {
constructor(name, age, grade) {
super(name, age);
this.grade = grade;
}
study(num) {
this.grade = this.grade + num;
return `${this.name}의 성적이 ${num}만큼 올라 ${this.grade}이 되었습니다.`;
}
}
class ForeignStudent extends Student {
constructor(name, age, grade, country) {
super(name, age, grade);
this.country = country;
}
speak() {
return `${this.country} 언어로 수업을 진행합니다.`;
}
}
let americanStudent = new ForeignStudent("jake", 23, 80, "미국");
americanStudent.study(20) // 'jake의 성적이 20만큼 올라 100이 되었습니다.'
americanStudent.sleep() // 'jake이(가) 잠을 잡니다.'
americanStudent.speak() // '미국 언어로 수업을 진행합니다.'
(Object.prototype) - Human(name
, age
, sleep()
) - Student(super(name, age)
, grade
, study(num)
) - ForeignStudent(super(name, age, grade)
, country
, speak()
)구조로 Class가 상속 후 americanStudent
가 인스턴스로 발행!
엄청 어렵다. 객체지향이 이래서 핫하구나,, 가히 프론트 언어의 꽃이라봐도 무방하다. 객체 지향을 이용한 모듈화가 핵심인 거 같으니 공부 열심히 해서 마스터 해야겠다. 특히 나중에 프레임워크도 배울 텐데 알아놔야 그때 잘 할 수 있을 거 같다.