객체 지향 프로그래밍(OOP, Object Oriented Programming)
✅ 절차적 언어
- 순차적인 명령을 조합함
- 초기 프로그래밍 언어들이 주로 이런 방식(C, 포트란 등)
✅ 객체 지향 언어
- 클래스라고 부르는 데이터 모델의 청사진을 사용해 코드 작성
- 현대의 언어들 대부분 (java, C++, C# 등)
- JavaScript는 엄밀히 말해 객체 지향 언어는 아니지만, 객체 지향 패턴으로 작성 가능
✅ 객체 지향 프로그래밍
- 프로그램 설계 철학
- 모든 것은 ‘객체’로 그룹화됨
- 객체 내에는 데이터와 기능이 함께 있다라는 원칙 ⇒ 객체 내에는 메서드와 속성이 존재 ⇒ 클래스를 만들때 속성과 메서드를 함께 만들어 줬던 걸 생각하면 좋을 것 같다.
- 이를 통해 새로운 객체를 만들면서 속성에 고유한 값을 부여할 수 있음 ⇒ new 키워드로 인스턴스를 만들면서 생성자 함수 this를 통해 속성에 고유한 값을 부여할 수 있었음
- 4가지 주요 개념을 통해 재사용성을 확보하는게 핵심
✅ 클래스와 인스턴스와 관련해서 이해
- 클래스: 일종의 원형(original form), 객체 생성을 위한 아이디어, 청사진, 설계도, 붕어빵틀
- 즉, 아직 세부사항(속성)이 들어가지 않은 청사진 ⇒ 여기서 세부사항인 속성만 부여된다면 객체가 되는 것!
- 그럼 세부사항은 어떻게 넣어주나? ⇒ 생성자를 통해 함수에 인자를 넣듯이 속성을 넣어줌!
- 인스턴스: 클래스의 사례, 클래스를 바탕으로 생성된 객체
- 속성과 메서드의 구분
- 속성 : 객체가 가진 고유한 속성(예, 자동차의 색상, 가격, 마력 등)
- 메서드 : 객체가 가진 기능(예, 시작, 전진, 후진, 멈춤 등)
❓ 왜 객체 지향을 알아야 하는가?
- 좋은 설계를 하기 위해서! (정리도 절차적으로 하지 말고 객체 지향으로 해보고 싶다..)
- 그래서 알아야 하는 4가지 기본 개념 : 캡슐화, 상속, 추상화, 다형성
1️⃣ 캡슐화 (Encapsulation)
⇒ 코드를 복잡하지 않게 만들고 재사용성을 높임
- 코드와 데이터의 은닉이 포커스
- 데이터와 기능을 하나의 단위로 묶는 것
- 따로 정의하는 것이 아니라 한 객체 안에 넣어서 묶는 것(느슨하게 결합되었다고 표현)
- 은닉(hiding)화의 특징을 포함
- 내부 데이터나 내부 구현은 숨기고,
- 객체 외부에서 필요한 동작(메서드)만 노출시키는 것
- 이런 관점에서 더 엄격한 클래스의 경우 속성의 직접 접근을 막고, 설정하는 함수(setter), 불러오는 함수(getter)를 철저하게 나누기도 함
- 느슨한 결합에 유리함 : 언제든 구현을 수정할 수 있음
- 실행 순서에 따라 절차적으로 코드를 작성하는 것이 아니라
- 코드만 보고도 인스턴스 객체의 기능을 상상할 수 있게 작성하는 것
2️⃣ 추상화 (Abstraction)
⇒ 코드가 복잡하지 않게 만들고, 단순화로 변화에 대한 영향 최소화
- 내부는 복잡, 노출되는 부분은 단순하게! (애플?)
- 인터페이스 단순화
- ux,ui 관점에서도 추상화가 중요할 듯 ⇒ 사용자에게 필요하지 않은 메서드나 속성을 노출시키지 않고 단순한 이름으로 정의
- 단순화를 통해 예상치 못한 사용상의 변화를 줄일 수 있음
- 인터페이스란 클래스 정의 시, 메서드와 속성만 정의한 것, 추상화의 본질
3️⃣ 상속 (Inheritance)
⇒ 불필요한 코드를 줄여 재사용성 높임
- 부모 클래스의 특징을 자식 클래스가 물려받음
- 기본 클래스(base class)의 특징을 파생 클래스(derived class)가 상속받음
- 생각해보기 아주 좋은 예
4️⃣ 다형성 (Polymorphism)
⇒ 조건문 대신 객체의 특성에 맞게 달리 작성하는 것이 가능
- 같은 이름을 가진 메서드라도 조금씩 다르게 작동
- 다형성이 제공되지 않는다면 기본(부모) 클래스에 종류별로 분기를 시켜 하나하나 다 다르게 만들어야 할 것
if (type === select) {
renderSelect()
}
else if (type === 'text') {
renderTextBox()
}
else if (type === 'checkbox') {
renderCheckBox()
}
✅ 결론
- 객체 지향 프로그래밍은 우리가 세계를 보고 이해하는 방법과 흡사 ⇒ 평소에 사물이나 현상을 보고 객체 단위로 구분하고 속성과 메소드를 생각해보는 연습을 해보자
- 코딩, 설계할 때도 화면에 보이는 하나의 요소를 객체 단위로 구분해서 생각하고 복잡하지 않고 재사용성을 높일 수 있도록 작성하기
📌 추가내용
- 은닉화의 한계
- 은닉화를 지원하는 기능이 JS 에서는 널리 쓰이지 않음
- JS 에서는 일반적으로 은닉화를 위해 클로저 모듈 패턴 사용
- 클래스 인스턴스 형태로 만들 때는 #이라는 키워드가 도입됨 ⇒ 은닉화를 위해 보다 객체 지향적 언어인 TypeScript를 배워야 할 필요가 있음(클래스 내부에서만 쓰이는 속성, 메서드 구분, private 키워드)
- 추상화 기능 부재
- 즉 인터페이스(단순화) 기능 부재 ⇒ TS는 주요 기능으로 interface 를 구현해놓음 ⇒ 노출된 인터페이스를 통해 “이 클래스는 메서드 이름이 의도한 바대로 작동할 것이다"라는 것을 드러냄 ⇒ 이는 실질적인 구현 방법은 노출하지 않으면서 사용법은 노출시키기에도 유리함 ⇒ 이러한 인터페이스의 대표적인 예가 API(Application Programmin Interface)
프로토타입
- 익숙한 개념으로 이해해보기
- Array 는 클래스다
- 지금까지 써온 배열은 Array 클래스의 인스턴스이며, Array.prototype에는 push, pop 등 다양한 메서드가 있음
✅ 프로토타입
- 원형 객체
- 쉽게 말하면 js 에서 상속을 위한 매커니즘이라고 보면 된다(mdn에 정의된 핵심 단어)
- JS에서 객체를 상속하기 위한 방식
- 상속은 기본 클래스의 속성이나 메서드를 가져와 기존의 기능을 확장할 때 필요
💡 사람에 관한 속성과 메서드를 가진 user 라는 객체가 있는데 , user와 유사하지만 약간 차이가 있는 admin과 guest 객체를 만들어야 하는 경우. 이때 user의 메서드를 복사하거나 재구현하지 않고 user에 약간의 기능을 얹어 admin과 guest 객체를 만들고 싶을 때 프로토타입 상속을 이용할 수 있음
- JS에서 객체는 [[Prototype]] 이라는 숨은 속성이 있는데, 이것의 값은 null 이거나 다른 객체를 바라보며, 상속을 구현하는데 사용됨 ⇒ 즉 [[Prototype]] 이 참조하는 객체를 프로토타입 이라고 함 ⇒ [[Prototype]] 속성은 내부속성이면서 숨김 속성이지만 값을 설정할 수 있음 ⇒ 객체는 obj.proto 를 사용하여 자신의 프로토타입 [[Prototype]] 에 접근할 수 있음
- 프로토타입의 동작 방식
-
해당 객체에서 특정 속성을 읽으려고 하는데 그 속성이 없으면 JS는 자동으로 프로토타입에서 그 속성을 찾는다 (프로토타입 상속)
let animal = {
eats: true,
walk() {
alert("동물이 걷습니다");
}
};
let rabbit = {
jumps: true,
__proto__: animal
};
rabbit.__proto__ = animal;
alert(rabbit.eats);
rabbit.walk()
- 객체의 속성 순회 관련
- for.. in 반복문은 객체 자체에서 정의한 속성 뿐만 아니라 상속된 속성도 순회 대상에 포함
- 반면, 키-값과 관련된 내장 메서드 대부분은 상속된 속성은 제외하고 객체 자체 속성만 대상으로 동작함
✅ .__proto__
- __proto__ 프로퍼티는 [[Prototype]] 내부 슬롯이 가리키는 프로토타입 객체에 접근하기 위해 사용하는 접근자 프로퍼티이다
- 직접 접근할 수 없으며 __proto__ 접근자 프로퍼티를 통해 간접적으로 프로토타입 객체에 접근할 수 있다.
- 객체가 직접 소유하는 프로퍼티가 아니라 모든 객체의 프로토타입 객체인 Object.prototype 객체의 프로퍼티이다.
- 즉 모든 객체는 Object.prototype 의 접근자 프로퍼티 __proto__ 를 상속받아 사용할 수 있다.
- 함수도 객체이므로 __proto__ 접근자 프로퍼티를 통해 프로토타입 객체에 접근할 수 있다.
✅ .prototype
- 생성자 함수에 기본으로 세팅되는 속성(F.prototype)으로 생성자 함수로 [[Prototype]] 과는 다름
- 모든 객체는 자신의 프로토타입 객체를 가리키는 [[Prototype]] 이라는 내부 슬롯을 가지며 이는 상속을 위해 사용되는데
- 함수도 객체이므로 [[Prototype]] 을 가지지만 함수 객체는 일반 객체와는 달리 prototype 프로퍼티도 소유하게 됨
- 즉 함수 객체만이 소유하는 프로퍼티이며. 일반 객체에는 prototype 프로퍼티가 없음
- prototype 프로퍼티는 함수가 객체를 생성하는 생성자 함수로 사용될때, 생성자 함수가 생성한 인스턴스의 프로토타입 객체를 가리킴
- 즉 일반 객체와 생성자 함수로 호출할 수 없는 non-constructor 에는 prototype 프로퍼티가 없음
- 화살표 함수와 ES6 메서드 축약 표현으로 정의된 메서드는 non-constructor로 prototype 프로퍼티가 없다.
- [[Prototype]] 은 객체의 입장에서 자신의 부모 역할을 하는 프로토타입 객체를 가리키며 함수 객체의 경우 Function.prototype 을 가리킴
✅ Array(배열)로 보는 클래스, 인스턴스, 프로토타입의 관계
- 우리가 보는 arr는 실은 Array 클래스의 인스턴스이며, 프로토타입에 다양한 메서드가 있음
https://www.howdy-mj.me/javascript/prototype-and-proto/
https://ko.javascript.info/prototypes
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
https://poiemaweb.com/js-prototype
https://poiemaweb.com/js-function#65-proto-접근자-프로퍼티