🔸 객체 지향 프로그래밍이란 프로그램을 객체 라는 독립적인 단위로 나누고, 각 객체는 데이터와 그 데이터를 조작하는 메서드를 포함함.
🔸 객체.메서드()
: 객체 내에 메서드 호출.
let counter1 = {
value: 0,
increase: function() {
this.value++ // 메서드 호출을 할 경우, this는 counter1을 가리킵니다
},
decrease: function() {
this.value--
},
getValue: function() {
return this.value
}
}
counter1.increase()
counter1.increase()
counter1.increase()
counter1.decrease()
counter1.getValue() // 2
💡 메서드 호출 방식을 이용할 때는 화살표 함수를 사용할 수 없음.
- 화살표 함수 표현식은
this
가 없기 때문에 메서드가 아닌 함수에만 사용해야 함.
🔸 똑같은 기능을 하는 함수를 클로저 모듈 패턴을 이용해 여러 개 만들 수 있음. 복사, 붙여넣기 하지 않고 같은 코드를 재사용할 수 있음.
function makeCounter() {
let value = 0; //private; 외부 함수 스코프에 있는 변수
return {
increase: function() {
value++;
},
decrease: function() {
value--;
},
getValue: function() {
return value;
}
}
}
let counter1 = makeCounter() // counter1 객체는 value와 메서드를 가짐.
counter1.increase()
counter1.getValue() // 1
let counter2 = makeCounter()
counter2.decrease()
counter2.decrease()
counter2.getValue() // -2
다음 코드는 단순 객체를 사용한 경우로, 똑같은 기능을 하는 코드가 여러개 필요할 경우 복사, 붙여넣기를 통한 번거러움이 존재 (재사용성이 떨어짐)
let ageCounter1 = { age: 20, increase: function() { this.age++; }, decrease: function() { this.age--; }, getAge: function(){ return this.age; } } ageCounter1.increase(); ageCounter1.getAge(); // 21
🔸 객체 지향 프로그래밍 : 하나의 모델이 되는 청사진(class)을 만들고, 그 청사진을 바탕으로 한 객체(instance object)를 만드는 프로그래밍 패턴.
🔸 일반적인 다른 함수와 구분하기 위해 클래스는 보통 대문자로 시작하며 일반명사로 만듬 (일반적인 함수는 동사를 사용하고 소문자로 시작)
🔸 생성자(constructor) 함수 : 인스턴스가 만들어질 때 실행되는 코드로 return 값을 만들지 않음.
- ES5. 클래스는 함수로 정의 가능
function Car(brand, name, color) { // 인스턴스가 만들어질 때 실행되는 코드 }
- ES6. class라는 키워드를 이용해서 정의 가능 (최근 주로 사용)
class Car{ contructor(brand, name, color) { // 인스턴스가 만들어질 때 실행되는 코드 } }
🔸 new
키워드 사용하여 만듦.
ES5에서
new
키워드를 붙이지 않을 경우 그냥 함수가 실행되며, 생성자 함수는 아무것도 반환하지 않기 때문에undefined
를 보임.
ES6에서new
키워드를 붙이지 않을 경우TypeError
가 뜸
🔸 즉시, 생성자 함수가 실행되며, 변수에 클래스의 설계를 가진 새로운 객체(인스턴스)가 할당됨.
new 함수명() 동작 원리
function User(name, age) { // 📍 1. 빈 객체를 만들고, this에 할당 this={} this.name=name; this.age=age; // 📍 2. 함수 본문을 실행하면서 this에 property추가 // 📍 3. return this } let user = new User() // 📍 0. 실행
🔸 각각의 인스턴스는 클래스의 고유한 속성과 메소드를 갖게 됨.
let avante = new Car('hyundai', 'avante', 'black'); let mini = new Car('bmw', 'mini', 'white'); let beetles = new Car('volkswagen', 'beetles', 'red');
- 각각의 인스턴스(avante, mini, beetls)는 Car라는 클래스의 고유한 속성과 메소드를 가짐.
🔸 클래스에 속성과 메소드를 정의하고 인스턴스에서 이용.
🔸 클래스: 속성의 정의
this
: 인스턴스 객체를 의미.
- ES5
function Car(brand, name, color) { this.brand = brand; this.name = name; this.color = color; }
- ES6
class Car { constructor(brand, name, color) { this.brand = brand; this.name = name; this.color = color; } }
- parameter로 넘어온 브랜드, 이름, 색상 등은 인스턴스 생성시 지정하는 값, this에 할당한다는 것은 만들어진 인스턴스에 해당 브랜드, 이름, 색상을 부여하겠다는 의미
🔸 클래스: 메소드의 정의
- ES5.
prototype
을 이용해 정의.function Car(brand, name, color) { /*생략*/ } Car.prototype.refuel = function() { // 연료 공급을 구현하는 코드 }
- ES6. 생성자 함수와 함께
class
키워드 안쪽에 묶어서 정의.class Car { constructor(brand, name, color) { /* 생략 */ } refuel() { } drive() { } }
🔸 인스턴스에서 사용
let avante = new Car('hyundai', 'avante', 'black'); // instance
avante.color; // 'black'
avante.drive(); // 'avante가 운전을 시작합니다'
📍 생성자 함수 정도만 객체 지향 프로그래밍에서 보편적인 개념이며, prototype이나 this는 오직 JavaScript에서만 유효한 용어.
- ES5 방식
- ES6 방식
class Car { // class constructor(brand, name, color) { // 생성자 함수 this.brand = brand; // this 객체: 이 예제에서는 avante === this this.name = name; this.color = color; } refuel() { // prototype 객체: 속성이나 메서드를 정의할 수 있음 } drive() { console.log(this.name + '가 운전을 시작합니다'); } } let avante = new Car('hyundai', 'avante', 'black'); // instance avante.color; // 'black' avante.drive(); // 'avante가 운전을 시작합니다'
🔸 배열은 전부 Array의 인스턴스로, 배열을 정의하는 것은 Array의 인스턴스를 만들어내는 것과 동일.
let arr = ['code', 'states', 'pre']
let arr = new Array('code', 'states', 'pre'); // 위 코드와 동일
arr.length; // 3
arr.push('course'); // 새 element를 추가
🔸 초기의 프로그래밍 언어(C, 포트란 등)는 순차적인 명령의 조합인 절차적 언어라고 부름 (절차 지향 프로그래밍).
🔸 현대의 언어(Java, C++, C# 등)는 단순히 별개의 변수와 함수로 순차적으로 작동하는 것을 넘어, 데이터와 기능이 별개로 취급되지 않고, 한 번에 묶여서 처리할 수 있는 객체 지향의 특징을 가짐.
🔸 자바스크립트는 엄밀히 말해 객체 지향 언어는 아니지만, 객체 지향 패턴으로 작성할 수 있음.
🔸 프로그램 설계 철학임.
🔸 모든 것은 "객체"로 그룹화됨. 이 객체는 한번 만들고 나면, 메모리상에서 반환되기 전까지 객체 내의 모든 것이 유지됨.
🔸 4가지 주요 개념을 통해 재사용성을 얻을 수 있음.
🔸 클래스는 일종의 원형(original form)으로, 세부 사항(속성)이 들어가지 않은 청사진임. 세부 사항만 넣는다면, 객체가 됨.
🔸 인스턴스는 클래스의 사례(instance object).
🔸 클래스는 객체를 만들기 위한 생성자(constructor) 함수를 포함함. 이 생성자를 통해 세부 사항(속성)을 넣어줌. 함수에 인자를 넣듯, 속성을 넣을 수 있습니다.
🔸 Encapsulation (캡슐화)
🔸 Inheritance (상속)
🔸 Abstraction (추상화)
🔸 Polymorphism (다형성)
🔸 데이터(속성)와 기능(메서드)을 하나의 단위로 묶는 것으로, 데이터(속성)와 기능(메서드)들이 느슨하게 결합됨.
🔸 느슨한 결합(Loose Coupling)에 유리 : 언제든 구현을 수정할 수 있음
🔸 은닉(hiding) : 구현은 숨기고, 동작은 노출시킴
🔸 내부 구현은 아주 복잡한데, 실제로 노출되는 부분은 단순하게 만든다는 개념으로, 인터페이스가 단순해짐.
🔸 캡슐화가 코드나 데이터의 은닉에 포커스가 맞춰져있다면, 추상화는 클래스를 사용하는 사람이 필요하지 않은 메서드 등을 노출시키지 않고, 단순한 이름으로 정의하는 것에 포커스가 맞춰짐.
🔸 클래스 정의 시, 메서드와 속성만 정의한 것을 인터페이스라고 부르며, 이것이 추상화의 본질.
🔸 부모 클래스의 특징을 자식 클래스가 물려받는 것. 다시 말해, 기본 클래스(base class)의 특징을 파생 클래스(derived class)가 상속받는 것.
🔸 다양한 형태를 가질 수 있음. 객체 역시 똑같은 메서드라 하더라도, 다른 방식으로 구현될 수 있음.
🔸 같은 이름을 가진 메서드라도 조금씩 다르게 작동.
🔸 만일 언어 자체에서 다형성을 제공하지 않는다면, 기본(부모) 클래스에 종류별로 분기를 시켜서 하나하나 다르게 만들어야함.
if/else if
와 같은 조건문 대신 객체의 특성에 맞게 달리 작성하는 것이 가능해짐.TypeScript는 JavaScript에 더 많은 기능을 붙인 새로운 언어로, 보다 객체 지향적으로 디자인된 언어.
private
키워드)의 한계🔸 Java나 TypeScript라는 프로그래밍 언어는 클래스 내부에서만 쓰이는 속성 및 메서드를 구분시키기 위해 private
이라는 키워드를 제공.
🔸 이러한 은닉화를 도와주는 기능이 JavaScript에서는 지원하는 브라우저가 적어, 널리 쓰이지 않음.
TypeScript 문법
class Animal { private name: string; // name이라는 속성이 존재. 그러나 private 키워드가 붙어 있어서, 클래스 내부에서만 사용 가능 constructor(theName: string) { this.name = theName; } } new Animal("Cat").name; // 사용 불가 // Property 'name' is private and only accessible within class 'Animal'.
🔸 JavaScript에서는 은닉화를 돕기 위해서 일반적으로 클로저 모듈 패턴을 사용. 클래스/인스턴스 형태로 만들 때에는 ES2019부터 #
이라는 키워드가 도입됨.
interface
키워드) 기능의 부재🔸 추상화는, 속성과 메서드의 이름만 노출시켜서 사용을 단순화한다는 의미를 가지며, 인터페이스(interface)의 단순화를 의미함.
🔸 그러나 Java나 TypeScript 언어는 언어의 주요 기능으로 interface를 구현해 놓았지만, 이러한 부분은 JavaScript에는 존재하지 않는 기능임.
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) {} }
- 인터페이스와 클래스 구현이 따로 정의되어 있음.
🔸 인터페이스의 이점은, 인터페이스가 일종의 규약처럼 간주되어, 인터페이스를 클래스로 구현하는 사람들이 이에 맞게 작성할 수 있게 도움. (인터페이스는 다양한 구현이 있을 수 있음)
🔸 이는 클래스를 이용하는 입장에서 노출된 인터페이스를 통해 "이 클래스는 메서드 이름이 의도한 바대로 작동할 것이다"라는 것을 명백히 드러나게 해 줌.
🔸 또한 실질적인 구현 방법을 공개하지 않고, 사용법을 노출시키기에도 유리.
🔸 어떤 클래스가 외부 공개용으로 모듈처럼 작동할 때에 인터페이스는 빛을 발하며, 이러한 인터페이스 사용의 대표적인 예가 API(Application Programming Interface)임.
🔸 JavaScript는 프로토타입(Prototype) 기반 언어로, 여기서 프로토 타입은 원형 객체를 의미함. (mdn 문서)
class Human {
constructor(name, age) {
this.name = name;
this.age = age;
}
sleep() {
console.log(`${this.name}은 잠에 들었습니다`);
}
}
let kimcoding = new Human('김코딩', 30);
Human.prototype.constructor === Human; // true
Human.prototype === kimcoding.__proto__; // true
Human.prototype.sleep === kimcoding.sleep; // true
🔸 __proto__
: 부모 클래스의 프로토타입, 혹은 '부모의 부모 클래스'의 프로토타입을 탐색할 수 있음.
🔸 배열(arr)은 Array
클래스의 인스턴스이며, 프로토타입에는 다양한 메서드가 존재
🔸 부모 클래스가 자식 클래스로 속성과 메서드를 물려주는 과정을 상속이라하며, JavaScript에서 구현할 때에는 프로토타입 체인을 사용함.
🔸 상위 프로토타입 객체로부터 메소드와 속성을 상속 받을 수 있고 그 상위 프로토타입 객체도 마찬가지. 이를 프로토타입 체인이라 부름.
🔸 JavaScript에서에서는 extends
와 super
키워드를 이용해서 상속을 구현할 수 있음. (mdn 문서)
🔸 class ChildClass extends ParentClass { ... }
: ParentClass를 ChildClass로 상속.
🔸 super([arguments])
: 상위 클래스의 생성자를 호출해 super()
의 매개변수를 통해 상위 클래스의 멤버를 상속받을 수 있음.
- DOM을 이용해,
document.createElement('div')
로 새로운div
엘리먼트를 생성하고, 이렇게 생성된div
는HTMLDivElement
라는 클래스의 인스턴스임.- DOM 엘리먼트는
innerHTML
과 같은 속성, 또는append()
와 같은 메서드가 있음. 각각의 엘리먼트가 해당 메서드나 속성이 있다는 것을 통해,Element
라는 공통의 부모가 있음을 알 수 있음.
🔸 (화살표 방향은 부모를 가리킴) EventTarget의 부모로는, 모든 클래스의 조상인 Object가 존재.
let div = document.createElement('div');
div.__proto__ // HTMLDivElement.prototype
div.__proto__.__proto__ // HTMLElement.prototype
div.__proto__.__proto__.__proto__ // Element.prototype
div.__proto__.__proto__.__proto__.__proto__ // Node.prototype
div.__proto__.__proto__.__proto__.__proto__.__proto__ // EentTarget.prototype
div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__ // Object.prototype
div.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__.__proto__ // null
🔸 클래스의 인스턴스 객체를 생성하고 초기화하는 메서드
constructor() { ... } constructor(argument0) { ... }
- 다른 모든 메서드 호출보다 앞선 시점인, 인스턴스 객체를 초기화할 때 수행할 초기화 코드를 정의할 수 있음.
constructor
라는 이름의 메서드는 하나의 클래스에 오직 하나만 존재할 수 있음. (SyntaxError
)
🔸 클래스에 생성자를 정의하지 않으면 기본 생성자를 사용하며, 아무것도 상속하지 않는 기본 클래스일 때의 기본 생성자는 빈 메서드.
constructor() {}
🔸 다른 클래스를 상속하는 경우, 기본 생성자는 자신의 매개변수를 부모 클래스의 생성자로 전달.
constructor(...args) { super(...args); }
class ValidationError extends Error { // 기본 내장 클래스인 Error 클래스를 확장
printCustomerMessage() {
return `Validation failed :-( (details: ${this.message})`;
}
}
try {
throw new ValidationError("Not a valid phone number");
} catch (error) { // error 객체를 확인하여 어떤 종류의 예외인지 판별
if (error instanceof ValidationError) { // error 객체가 ValidationError 클래스의 인스턴스인지 확인
console.log(error.name); // ValidationError가 아니라 Error!, Error 클래스의 인스턴스로 캡처되었기 때문에 출력 결과는 "Error"가 됩
console.log(error.printCustomerMessage());
// ValidationError 클래스의 인스턴스라면, 해당 객체의 속성과 메서드를 사용할 수 있습니다. 여기서는 name 속성과 printCustomerMessage() 메서드를 호출하여 예외 정보를 출력
} else {
console.log("Unknown error", error);
throw error;
}
}
ValidationError
클래스는 아무런 초기화 동작도 필요하지 않으므로 생성자를 별도로 명시하지 않았으며, 대신 기본 생성자가 매개변수로 부모 Error
클래스의 초기화를 처리하고 있습니다.🔸 그러나, 파생 클래스에 직접 생성자를 정의할 경우, super()
를 통해 부모 클래스의 생성자를 호출해야 함. (ReferenceError
)
super()
🔸 객체 리터럴 또는 클래스의 [[Prototype]] 속성에 접근하거나 슈퍼클래스의 생성자를 호출하는 데 사용.
🔸 파생 클래스에서는{}
빈 객체가 만들어지고,this
에 할당하는 작업을 건너뛰기 때문에 Error가 발생함. 따라서 먼저super()
를 통해 부모constructor
를 실행해줌.
class ValidationError extends Error {
constructor(message) {
super(message); // 부모 클래스의 생성자 호출
this.name = "ValidationError";
this.code = "42";
}
printCustomerMessage() {
return `Validation failed :-( (details: ${this.message}, code: ${this.code})`;
}
}
try {
throw new ValidationError("Not a valid phone number");
} catch (error) {
if (error instanceof ValidationError) {
console.log(error.name); // 이제 ValidationError!
console.log(error.printCustomerMessage());
} else {
console.log("Unknown error", error);
throw error;
}
}
🔸 파생 클래스에서, this
를 사용하기 전에 반드시 super()
를 먼저 호출해야 함. (ReferenceError
)
🔸 필요한 경우 : 상속을 받는 하위 클래스(subclass)가 상위 클래스(superclass)의 특성을 상속받을 때, 하위 클래스가 별도의 초기화 작업을 수행해야 하는 경우가 있음.
🔸 이 경우에는 생성자 함수에 매개변수가 필요. 하위에서 전달받은 변수를 상위클래스로 넘겨줘야 전달인자로 넘겨줄 수 있음
class Parent {
constructor(color) {
this.color = color;
}
}
class Child extends Parent {
constructor(color) {
super(color);
}
}
const C = new Child("red")
// 📍 C를 출력할 시 color : "red" 가 들어감
// 📍 매개변수를 작성하지 않으면, 전달인자를 넘겨줘도 undefined가 나옴.
🔸 object instanceof constructor
: object
의 프로토타입 체인에 constructor.prototype
이 존재하는지 판별
🔸 클래스 내부에서 선언된 메서드는 __proto__
에 들어가 있음